// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:ffi' as ffi; import 'dart:io' as io; import 'package:flutter/material.dart'; import '../common.dart'; typedef GetStackPointerCallback = int Function(); // c interop function: // void* mmap(void* addr, size_t len, int prot, int flags, int fd, off_t offset); typedef CMmap = ffi.Pointer<ffi.Void> Function( ffi.Pointer<ffi.Void>, ffi.IntPtr, ffi.Int32, ffi.Int32, ffi.Int32, ffi.IntPtr); typedef DartMmap = ffi.Pointer<ffi.Void> Function( ffi.Pointer<ffi.Void>, int, int, int, int, int); final DartMmap mmap = ffi.DynamicLibrary.process().lookupFunction<CMmap, DartMmap>('mmap'); // c interop function: // int mprotect(void* addr, size_t len, int prot); typedef CMprotect = ffi.Int32 Function(ffi.Pointer<ffi.Void>, ffi.IntPtr, ffi.Int32); typedef DartMprotect = int Function(ffi.Pointer<ffi.Void>, int, int); final DartMprotect mprotect = ffi.DynamicLibrary.process() .lookupFunction<CMprotect, DartMprotect>('mprotect'); const int kProtRead = 1; const int kProtWrite = 2; const int kProtExec = 4; const int kMapPrivate = 0x02; const int kMapJit = 0x0; const int kMapAnon = 0x20; const int kMemorySize = 16; const int kInvalidFileDescriptor = -1; const int kkFileMappingOffset = 0; const int kMemoryStartingIndex = 0; const int kExitCodeSuccess = 0; final GetStackPointerCallback getStackPointer = () { // Makes sure we are running on an Android arm64 device. if (!io.Platform.isAndroid) throw 'This benchmark test can only be run on Android arm devices.'; final io.ProcessResult result = io.Process.runSync('getprop', <String>['ro.product.cpu.abi']); if (result.exitCode != 0) throw 'Failed to retrieve CPU information.'; if (!result.stdout.toString().contains('armeabi')) throw 'This benchmark test can only be run on Android arm devices.'; // Creates a block of memory to store the assembly code. final ffi.Pointer<ffi.Void> region = mmap(ffi.nullptr, kMemorySize, kProtRead | kProtWrite, kMapPrivate | kMapAnon | kMapJit, kInvalidFileDescriptor, kkFileMappingOffset); if (region == ffi.nullptr) { throw 'Failed to acquire memory for the test.'; } // Writes the assembly code into the memory block. This assembly code returns // the memory address of the stack pointer. region.cast<ffi.Uint8>().asTypedList(kMemorySize).setAll( kMemoryStartingIndex, <int>[ // "mov r0, sp" in machine code: 0D00A0E1. 0x0d, 0x00, 0xa0, 0xe1, // "bx lr" in machine code: 1EFF2FE1. 0x1e, 0xff, 0x2f, 0xe1 ] ); // Makes sure the memory block is executable. if (mprotect(region, kMemorySize, kProtRead | kProtExec) != kExitCodeSuccess) { throw 'Failed to write executable code to the memory.'; } return region .cast<ffi.NativeFunction<ffi.IntPtr Function()>>() .asFunction<int Function()>(); }(); class StackSizePage extends StatelessWidget { const StackSizePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Material( child: Column( children: const <Widget>[ SizedBox( width: 200, height: 100, child: ParentWidget(), ), ], ), ); } } class ParentWidget extends StatelessWidget { const ParentWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final int myStackSize = getStackPointer(); return ChildWidget(parentStackSize: myStackSize); } } class ChildWidget extends StatelessWidget { const ChildWidget({required this.parentStackSize, Key? key}) : super(key: key); final int parentStackSize; @override Widget build(BuildContext context) { final int myStackSize = getStackPointer(); // Captures the stack size difference between parent widget and child widget // during the rendering pipeline, i.e. one layer of stateless widget. return Text( '${parentStackSize - myStackSize}', key: const ValueKey<String>(kStackSizeKey), ); } }