| // 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({super.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({super.key}); |
| |
| @override |
| Widget build(BuildContext context) { |
| final int myStackSize = getStackPointer(); |
| return ChildWidget(parentStackSize: myStackSize); |
| } |
| } |
| |
| class ChildWidget extends StatelessWidget { |
| const ChildWidget({required this.parentStackSize, super.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), |
| ); |
| } |
| } |