Skip to content

Commit 7afad0b

Browse files
authored
[native_toolchain_c] Use 16 kb pages for Android by default (#1740)
Closes: #1611
1 parent ee2215e commit 7afad0b

File tree

4 files changed

+80
-2
lines changed

4 files changed

+80
-2
lines changed

pkgs/native_toolchain_c/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
## 0.6.1-wip
22

3+
- For Android, produce dylibs with page-size set to 16kb by default.
4+
https://github.com/dart-lang/native/issues/1611
5+
36
## 0.6.0
47

58
- Address analyzer info diagnostic about multi-line if requiring a block body.

pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,9 @@ class RunCBuilder {
276276
cppLinkStdLib ?? defaultCppLinkStdLib[config.targetOS]!
277277
],
278278
...linkerOptions?.preSourcesFlags(toolInstance.tool, sourceFiles) ?? [],
279+
// Support Android 15 page size by default, can be overridden by
280+
// passing [flags].
281+
if (config.targetOS == OS.android) '-Wl,-z,max-page-size=16384',
279282
...flags,
280283
for (final MapEntry(key: name, :value) in defines.entries)
281284
if (value == null) '-D$name' else '-D$name=$value',

pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_android_test.dart

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ void main() {
6767
.firstWhere((e) => e.contains('file format'));
6868
expect(machine, contains(objdumpFileFormat[target]));
6969
}
70+
if (linkMode == DynamicLoadingBundled()) {
71+
await expectPageSize(libUri, 16 * 1024);
72+
}
7073
});
7174
}
7275
}
@@ -95,14 +98,37 @@ void main() {
9598
// Identical API levels should lead to an identical binary.
9699
expect(bytes2, bytes3);
97100
});
101+
102+
test('page size override', () async {
103+
const target = Architecture.arm64;
104+
final linkMode = DynamicLoadingBundled();
105+
const apiLevel1 = flutterAndroidNdkVersionLowestSupported;
106+
final tempUri = await tempDirForTest();
107+
final outUri = tempUri.resolve('out1/');
108+
await Directory.fromUri(outUri).create();
109+
const pageSize = 4 * 1024;
110+
final libUri = await buildLib(
111+
outUri,
112+
target,
113+
apiLevel1,
114+
linkMode,
115+
flags: ['-Wl,-z,max-page-size=$pageSize'],
116+
);
117+
if (Platform.isMacOS || Platform.isLinux) {
118+
final address = await textSectionAddress(libUri);
119+
expect(address, greaterThanOrEqualTo(pageSize));
120+
expect(address, isNot(greaterThanOrEqualTo(pageSize * 4)));
121+
}
122+
});
98123
}
99124

100125
Future<Uri> buildLib(
101126
Uri tempUri,
102127
Architecture targetArchitecture,
103128
int androidNdkApi,
104-
LinkMode linkMode,
105-
) async {
129+
LinkMode linkMode, {
130+
List<String> flags = const [],
131+
}) async {
106132
final addCUri = packageUri.resolve('test/cbuilder/testfiles/add/src/add.c');
107133
const name = 'add';
108134

@@ -140,6 +166,7 @@ Future<Uri> buildLib(
140166
name: name,
141167
assetName: name,
142168
sources: [addCUri.toFilePath()],
169+
flags: flags,
143170
);
144171
await cbuilder.run(
145172
config: buildConfig,

pkgs/native_toolchain_c/test/helpers.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,3 +255,48 @@ Future<void> expectSymbols({
255255
throw UnimplementedError();
256256
}
257257
}
258+
259+
Future<int> textSectionAddress(Uri dylib) async {
260+
if (Platform.isMacOS) {
261+
// Find the line in the objdump output that looks like:
262+
// 11 .text 00000046 00000000000045a0 TEXT
263+
final result = await runProcess(
264+
executable: Uri.file('objdump'),
265+
arguments: ['--headers', dylib.toFilePath()],
266+
logger: logger,
267+
);
268+
expect(result.exitCode, 0);
269+
final textSection =
270+
result.stdout.split('\n').firstWhere((e) => e.contains('.text'));
271+
final parsed = textSection.split(' ').where((e) => e.isNotEmpty).toList();
272+
expect(parsed[1], '.text');
273+
expect(parsed[4], 'TEXT');
274+
final vma = int.parse(parsed[3], radix: 16);
275+
return vma;
276+
}
277+
if (Platform.isLinux) {
278+
// Find the line in the readelf output that looks like:
279+
// [11] .text PROGBITS 00004328 000328 000064 00 AX 0 0 4
280+
final result = await readelf(dylib.toFilePath(), 'S');
281+
final textSection =
282+
result.split('\n').firstWhere((e) => e.contains('.text'));
283+
final parsed = textSection.split(' ').where((e) => e.isNotEmpty).toList();
284+
expect(parsed[1], '.text');
285+
expect(parsed[2], 'PROGBITS');
286+
final addr = int.parse(parsed[3], radix: 16);
287+
return addr;
288+
}
289+
throw UnimplementedError();
290+
}
291+
292+
Future<void> expectPageSize(
293+
Uri dylib,
294+
int pageSize,
295+
) async {
296+
if (Platform.isMacOS || Platform.isLinux) {
297+
// If page size is 16kb, the `.text` section address should be
298+
// above 0x4000. With smaller page sizes it's above 0x1000.
299+
final vma = await textSectionAddress(dylib);
300+
expect(vma, greaterThanOrEqualTo(pageSize));
301+
}
302+
}

0 commit comments

Comments
 (0)