blob: fcbbe5c18dc53dbd95ba5721c95f3d432c50dc77 [file] [log] [blame]
// 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 const Material(
child: Column(
children: <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),
);
}
}