From 579688bb4566801018b30b47126c7b842a4ed4d2 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Mon, 25 Nov 2024 10:58:12 +0100 Subject: [PATCH] [native_toolchain_c] Compile with `-Os` by default (#1744) Closes: https://github.com/dart-lang/native/issues/1267 --- pkgs/native_toolchain_c/CHANGELOG.md | 2 + .../lib/native_toolchain_c.dart | 1 + .../lib/src/cbuilder/cbuilder.dart | 4 ++ .../lib/src/cbuilder/clinker.dart | 3 + .../lib/src/cbuilder/ctool.dart | 5 ++ .../lib/src/cbuilder/optimization_level.dart | 56 +++++++++++++++++++ .../lib/src/cbuilder/run_cbuilder.dart | 7 +++ .../cbuilder/cbuilder_cross_android_test.dart | 14 ++++- .../cbuilder/cbuilder_cross_ios_test.dart | 12 +++- .../cbuilder_cross_linux_host_test.dart | 10 +++- .../cbuilder_cross_macos_host_test.dart | 12 +++- .../cbuilder_cross_windows_host_test.dart | 10 +++- 12 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 pkgs/native_toolchain_c/lib/src/cbuilder/optimization_level.dart diff --git a/pkgs/native_toolchain_c/CHANGELOG.md b/pkgs/native_toolchain_c/CHANGELOG.md index f23a38722..c3c04d05c 100644 --- a/pkgs/native_toolchain_c/CHANGELOG.md +++ b/pkgs/native_toolchain_c/CHANGELOG.md @@ -2,6 +2,8 @@ - For Android, produce dylibs with page-size set to 16kb by default. https://github.com/dart-lang/native/issues/1611 +- Make optimization level configurable. Defaults to `-Os` and `/Os`. + https://github.com/dart-lang/native/issues/1267 ## 0.6.0 diff --git a/pkgs/native_toolchain_c/lib/native_toolchain_c.dart b/pkgs/native_toolchain_c/lib/native_toolchain_c.dart index fa001799d..2d4580f1d 100644 --- a/pkgs/native_toolchain_c/lib/native_toolchain_c.dart +++ b/pkgs/native_toolchain_c/lib/native_toolchain_c.dart @@ -9,5 +9,6 @@ export 'src/cbuilder/cbuilder.dart' show CBuilder; export 'src/cbuilder/clinker.dart' show CLinker; export 'src/cbuilder/language.dart' show Language; export 'src/cbuilder/linker_options.dart' show LinkerOptions; +export 'src/cbuilder/optimization_level.dart'; export 'src/cbuilder/output_type.dart' show OutputType; export 'src/utils/env_from_bat.dart'; diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart index d8994f315..c3a97f8b7 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart @@ -11,6 +11,7 @@ import 'package:native_assets_cli/code_assets_builder.dart'; import 'ctool.dart'; import 'language.dart'; import 'linkmode.dart'; +import 'optimization_level.dart'; import 'output_type.dart'; import 'run_cbuilder.dart'; @@ -67,6 +68,7 @@ class CBuilder extends CTool implements Builder { super.language = Language.c, super.cppLinkStdLib, super.linkModePreference, + super.optimizationLevel = OptimizationLevel.oS, }) : super(type: OutputType.library); CBuilder.executable({ @@ -87,6 +89,7 @@ class CBuilder extends CTool implements Builder { super.std, super.language = Language.c, super.cppLinkStdLib, + super.optimizationLevel = OptimizationLevel.oS, }) : super( type: OutputType.executable, assetName: null, @@ -158,6 +161,7 @@ class CBuilder extends CTool implements Builder { std: std, language: language, cppLinkStdLib: cppLinkStdLib, + optimizationLevel: optimizationLevel, ); await task.run(); } diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/clinker.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/clinker.dart index 5283603d9..9a10f504a 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/clinker.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/clinker.dart @@ -12,6 +12,7 @@ import 'ctool.dart'; import 'language.dart'; import 'linker_options.dart'; import 'linkmode.dart'; +import 'optimization_level.dart'; import 'output_type.dart'; import 'run_cbuilder.dart'; @@ -36,6 +37,7 @@ class CLinker extends CTool implements Linker { super.language = Language.c, super.cppLinkStdLib, super.linkModePreference, + super.optimizationLevel = OptimizationLevel.oS, }) : super(type: OutputType.library); /// Runs the C Linker with on this C build spec. @@ -84,6 +86,7 @@ class CLinker extends CTool implements Linker { std: std, language: language, cppLinkStdLib: cppLinkStdLib, + optimizationLevel: optimizationLevel, ); await task.run(); diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/ctool.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/ctool.dart index 47e8c7456..1a977e106 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/ctool.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/ctool.dart @@ -7,6 +7,7 @@ import 'package:native_assets_cli/code_assets.dart'; import 'cbuilder.dart'; import 'language.dart'; +import 'optimization_level.dart'; import 'output_type.dart'; abstract class CTool { @@ -120,6 +121,9 @@ abstract class CTool { /// the value is instead retrieved from the [LinkConfig]. final LinkModePreference? linkModePreference; + /// What optimization level should be used for compiling. + final OptimizationLevel optimizationLevel; + CTool({ required this.name, required this.assetName, @@ -135,5 +139,6 @@ abstract class CTool { required this.cppLinkStdLib, required this.linkModePreference, required this.type, + required this.optimizationLevel, }); } diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/optimization_level.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/optimization_level.dart new file mode 100644 index 000000000..2c8398f89 --- /dev/null +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/optimization_level.dart @@ -0,0 +1,56 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +/// Optimization level for code compilation. +/// +/// For more information refer to compiler documentation: +/// * https://clang.llvm.org/docs/CommandGuide/clang.html#code-generation-options +/// * https://learn.microsoft.com/en-us/cpp/build/reference/o-options-optimize-code?view=msvc-170 +final class OptimizationLevel { + /// The optimization level. + final String _level; + + const OptimizationLevel._(this._level); + + /// No optimization; prioritize fast compilation. + static const OptimizationLevel o0 = OptimizationLevel._('O0'); + + /// Basic optimizations; balance compilation speed and code size. + static const OptimizationLevel o1 = OptimizationLevel._('O1'); + + /// More aggressive optimizations; prioritize code size reduction. + static const OptimizationLevel o2 = OptimizationLevel._('O2'); + + /// The most aggressive optimizations; prioritize runtime performance. + /// + /// Not supported in MSVC, defaults to [o2] for MSVC. + static const OptimizationLevel o3 = OptimizationLevel._('O3'); + + /// Optimize for code size, even if it impacts runtime performance. + static const OptimizationLevel oS = OptimizationLevel._('Os'); + + /// Unspecified optimization level; the default or compiler-chosen level. + static const OptimizationLevel unspecified = + OptimizationLevel._('unspecified'); + + /// Returns the string representation of the optimization level. + @override + String toString() => _level; + + String clangFlag() => '-$_level'; + + String msvcFlag() => switch (this) { + o3 => o2.msvcFlag(), + _ => '/$_level', + }; + + static const List values = [ + o0, + o1, + o2, + o3, + oS, + unspecified, + ]; +} diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart index 3b3327059..8342f109c 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart @@ -16,6 +16,7 @@ import '../utils/run_process.dart'; import 'compiler_resolver.dart'; import 'language.dart'; import 'linker_options.dart'; +import 'optimization_level.dart'; class RunCBuilder { /// The options are for linking only, so this will be non-null iff a linker @@ -45,6 +46,7 @@ class RunCBuilder { final String? std; final Language language; final String? cppLinkStdLib; + final OptimizationLevel optimizationLevel; RunCBuilder({ required this.config, @@ -64,6 +66,7 @@ class RunCBuilder { this.std, this.language = Language.c, this.cppLinkStdLib, + required this.optimizationLevel, }) : outDir = config.outputDirectory, assert([executable, dynamicLibrary, staticLibrary] .whereType() @@ -275,6 +278,8 @@ class RunCBuilder { '-l', cppLinkStdLib ?? defaultCppLinkStdLib[config.targetOS]! ], + if (optimizationLevel != OptimizationLevel.unspecified) + optimizationLevel.clangFlag(), ...linkerOptions?.preSourcesFlags(toolInstance.tool, sourceFiles) ?? [], // Support Android 15 page size by default, can be overridden by // passing [flags]. @@ -327,6 +332,8 @@ class RunCBuilder { final result = await runProcess( executable: tool.uri, arguments: [ + if (optimizationLevel != OptimizationLevel.unspecified) + optimizationLevel.msvcFlag(), if (std != null) '/std:$std', if (language == Language.cpp) '/TP', ...flags, diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_android_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_android_test.dart index e44305e44..f785bbfbc 100644 --- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_android_test.dart +++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_android_test.dart @@ -36,6 +36,9 @@ void main() { /// From https://docs.flutter.dev/reference/supported-platforms. const flutterAndroidNdkVersionHighestSupported = 34; + const optimizationLevels = OptimizationLevel.values; + var selectOptimizationLevel = 0; + for (final linkMode in [DynamicLoadingBundled(), StaticLinking()]) { for (final target in targets) { for (final apiLevel in [ @@ -43,14 +46,20 @@ void main() { flutterAndroidNdkVersionLowestSupported, flutterAndroidNdkVersionHighestSupported, ]) { - test('CBuilder $linkMode library $target minSdkVersion $apiLevel', - () async { + // Cycle through all optimization levels. + final optimizationLevel = optimizationLevels[selectOptimizationLevel]; + selectOptimizationLevel = + (selectOptimizationLevel + 1) % optimizationLevels.length; + test( + 'CBuilder $linkMode library $target minSdkVersion $apiLevel ' + '$optimizationLevel', () async { final tempUri = await tempDirForTest(); final libUri = await buildLib( tempUri, target, apiLevel, linkMode, + optimizationLevel: optimizationLevel, ); if (Platform.isLinux) { final machine = await readelfMachine(libUri.path); @@ -128,6 +137,7 @@ Future buildLib( int androidNdkApi, LinkMode linkMode, { List flags = const [], + OptimizationLevel optimizationLevel = OptimizationLevel.o3, }) async { final addCUri = packageUri.resolve('test/cbuilder/testfiles/add/src/add.c'); const name = 'add'; diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_ios_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_ios_test.dart index 006f5c262..ad765d690 100644 --- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_ios_test.dart +++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_ios_test.dart @@ -35,6 +35,9 @@ void main() { const name = 'add'; + const optimizationLevels = OptimizationLevel.values; + var selectOptimizationLevel = 0; + for (final language in [Language.c, Language.objectiveC]) { for (final linkMode in [DynamicLoadingBundled(), StaticLinking()]) { for (final targetIOSSdk in IOSSdk.values) { @@ -42,16 +45,20 @@ void main() { if (target == Architecture.x64 && targetIOSSdk == IOSSdk.iPhoneOS) { continue; } - final libName = OS.iOS.libraryFileName(name, linkMode); for (final installName in [ null, if (linkMode == DynamicLoadingBundled()) Uri.file('@executable_path/Frameworks/$libName'), ]) { + // Cycle through all optimization levels. + final optimizationLevel = + optimizationLevels[selectOptimizationLevel]; + selectOptimizationLevel = + (selectOptimizationLevel + 1) % optimizationLevels.length; test( 'CBuilder $linkMode $language library $targetIOSSdk $target' - ' ${installName ?? ''}' + ' ${installName ?? ''} $optimizationLevel' .trim(), () async { final tempUri = await tempDirForTest(); final tempUri2 = await tempDirForTest(); @@ -97,6 +104,7 @@ void main() { sources: [sourceUri.toFilePath()], installName: installName, language: language, + optimizationLevel: optimizationLevel, ); await cbuilder.run( config: buildConfig, diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_linux_host_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_linux_host_test.dart index 48a08286a..5e2e4a74a 100644 --- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_linux_host_test.dart +++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_linux_host_test.dart @@ -26,9 +26,16 @@ void main() { Architecture.riscv64, ]; + const optimizationLevels = OptimizationLevel.values; + var selectOptimizationLevel = 0; + for (final linkMode in [DynamicLoadingBundled(), StaticLinking()]) { for (final target in targets) { - test('CBuilder $linkMode library $target', () async { + // Cycle through all optimization levels. + final optimizationLevel = optimizationLevels[selectOptimizationLevel]; + selectOptimizationLevel = + (selectOptimizationLevel + 1) % optimizationLevels.length; + test('CBuilder $linkMode library $target $optimizationLevel', () async { final tempUri = await tempDirForTest(); final tempUri2 = await tempDirForTest(); final addCUri = @@ -66,6 +73,7 @@ void main() { name: name, assetName: name, sources: [addCUri.toFilePath()], + optimizationLevel: optimizationLevel, ); await cbuilder.run( config: buildConfig, diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_macos_host_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_macos_host_test.dart index 4c66cbacc..a698efb0d 100644 --- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_macos_host_test.dart +++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_macos_host_test.dart @@ -33,10 +33,19 @@ void main() { Architecture.x64: '64-bit x86-64', }; + const optimizationLevels = OptimizationLevel.values; + var selectOptimizationLevel = 0; + for (final language in [Language.c, Language.objectiveC]) { for (final linkMode in [DynamicLoadingBundled(), StaticLinking()]) { for (final target in targets) { - test('CBuilder $linkMode $language library $target', () async { + // Cycle through all optimization levels. + final optimizationLevel = optimizationLevels[selectOptimizationLevel]; + selectOptimizationLevel = + (selectOptimizationLevel + 1) % optimizationLevels.length; + + test('CBuilder $linkMode $language library $target $optimizationLevel', + () async { final tempUri = await tempDirForTest(); final tempUri2 = await tempDirForTest(); final sourceUri = switch (language) { @@ -79,6 +88,7 @@ void main() { assetName: name, sources: [sourceUri.toFilePath()], language: language, + optimizationLevel: optimizationLevel, ); await cbuilder.run( config: buildConfig, diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_windows_host_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_windows_host_test.dart index 60b509844..6058db4e5 100644 --- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_windows_host_test.dart +++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_windows_host_test.dart @@ -40,6 +40,9 @@ void main() { Architecture.x64: 'x64', }; + const optimizationLevels = OptimizationLevel.values; + var selectOptimizationLevel = 0; + final dumpbinFileType = { DynamicLoadingBundled(): 'DLL', StaticLinking(): 'LIBRARY', @@ -47,7 +50,11 @@ void main() { for (final linkMode in [DynamicLoadingBundled(), StaticLinking()]) { for (final target in targets) { - test('CBuilder $linkMode library $target', () async { + // Cycle through all optimization levels. + final optimizationLevel = optimizationLevels[selectOptimizationLevel]; + selectOptimizationLevel = + (selectOptimizationLevel + 1) % optimizationLevels.length; + test('CBuilder $linkMode library $target $optimizationLevel', () async { final tempUri = await tempDirForTest(); final tempUri2 = await tempDirForTest(); final addCUri = @@ -85,6 +92,7 @@ void main() { name: name, assetName: name, sources: [addCUri.toFilePath()], + optimizationLevel: optimizationLevel, ); await cbuilder.run( config: buildConfig,