1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// 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),
);
}
}