diff --git a/BUILD.gn b/BUILD.gn index 54217b0b854..16415b2a893 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -129,6 +129,7 @@ group("runtime_precompiled") { } if (dart_dynamic_modules) { deps += [ "utils/dart2bytecode:dart2bytecode_snapshot" ] + deps += [ "utils/modular_aot_compiler:modular_aot_compiler_snapshot" ] deps += [ "utils/dynamic_module_runner:dynamic_module_runner_snapshot" ] } } diff --git a/pkg/native_compiler/bin/modular_aot_compiler.dart b/pkg/native_compiler/bin/modular_aot_compiler.dart new file mode 100644 index 00000000000..51832e9fee0 --- /dev/null +++ b/pkg/native_compiler/bin/modular_aot_compiler.dart @@ -0,0 +1,8 @@ +// Copyright (c) 2026, 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. + +import 'package:native_compiler/modular_aot_compiler.dart' + as modular_aot_compiler; + +void main(List args) => modular_aot_compiler.main(args); diff --git a/pkg/native_compiler/lib/compilation_set.dart b/pkg/native_compiler/lib/compilation_set.dart new file mode 100644 index 00000000000..01668d3f301 --- /dev/null +++ b/pkg/native_compiler/lib/compilation_set.dart @@ -0,0 +1,132 @@ +// Copyright (c) 2026, 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. + +import 'package:cfg/front_end/ast_to_ir.dart'; +import 'package:cfg/front_end/recognized_methods.dart'; +import 'package:cfg/ir/flow_graph.dart'; +import 'package:cfg/ir/functions.dart'; +import 'package:kernel/ast.dart' as ast; +import 'package:native_compiler/back_end/code_generator.dart'; +import 'package:native_compiler/configuration.dart'; +import 'package:native_compiler/snapshot/image_writer.dart'; +import 'package:native_compiler/snapshot/snapshot.dart'; + +/// Accumulates contents of the whole compilation set. +class CompilationSet { + final List libraries; + final Configuration config; + final FunctionRegistry functionRegistry = FunctionRegistry(); + final RecognizedMethods recognizedMethods = CommonRecognizedMethods(); + final List _pendingFunctions = []; + final ImageWriter _imageWriter; + late final SnapshotSerializer _snapshot; + + CompilationSet(this.libraries, this.config) + : _imageWriter = config.createImageWriter() { + _snapshot = SnapshotSerializer(config.targetCPU, functionRegistry); + } + + /// Add [function] to be compiled. + /// + /// Can be used to queue nested local functions discovered + /// during compilation. + void addFunction(CFunction function) { + _pendingFunctions.add(function); + } + + /// Compile all functions from [libraries]. + void compileAllFunctions() { + for (final lib in libraries) { + for (final cls in lib.classes) { + for (final field in cls.fields) { + _compileFieldFunctions(field); + _compilePendingFunctions(); + } + for (final constr in cls.constructors) { + compileFunction(functionRegistry.getFunction(constr)); + _compilePendingFunctions(); + } + for (final proc in cls.procedures) { + compileFunction( + functionRegistry.getFunction( + proc, + isGetter: proc.isGetter, + isSetter: proc.isSetter, + ), + ); + _compilePendingFunctions(); + } + } + for (final field in lib.fields) { + _compileFieldFunctions(field); + _compilePendingFunctions(); + } + for (final proc in lib.procedures) { + compileFunction( + functionRegistry.getFunction( + proc, + isGetter: proc.isGetter, + isSetter: proc.isSetter, + ), + ); + _compilePendingFunctions(); + } + } + } + + void _compileFieldFunctions(ast.Field field) { + if (field.hasGetter && !field.isStatic) { + compileFunction(functionRegistry.getFunction(field, isGetter: true)); + } + if (field.hasSetter && !field.isStatic) { + compileFunction(functionRegistry.getFunction(field, isSetter: true)); + } + if ((field.isStatic || field.isLate) && field.initializer != null) { + compileFunction(functionRegistry.getFunction(field, isInitializer: true)); + } + } + + void _compilePendingFunctions() { + // [_pendingFunctions] can grow over time as local functions are + // discovered during compilation. + for (var i = 0; i < _pendingFunctions.length; ++i) { + compileFunction(_pendingFunctions[i]); + } + _pendingFunctions.clear(); + } + + /// Compile [function] to native code. + void compileFunction(CFunction function) { + FlowGraph graph; + try { + graph = AstToIr( + function, + functionRegistry, + recognizedMethods, + enableAsserts: config.enableAsserts, + ).buildFlowGraph(); + } catch (_) { + print('Compiler crashed while compiling $function'); + rethrow; + } + + config.createPipeline(functionRegistry, _consumeGeneratedCode).run(graph); + } + + void _consumeGeneratedCode(Code code) { + code.instructionsImageOffset = _imageWriter.addInstructions( + code.instructions, + ); + _snapshot.addRoot(code); + } + + void writeSnapshot(Sink> sink) { + _snapshot.writeModuleSnapshot(); + _imageWriter.addReadOnlyData( + _snapshot.out.getContents(), + _snapshot.out.position, + ); + _imageWriter.writeTo(sink); + } +} diff --git a/pkg/native_compiler/lib/configuration.dart b/pkg/native_compiler/lib/configuration.dart index 95c52dd6cda..dc082b7870b 100644 --- a/pkg/native_compiler/lib/configuration.dart +++ b/pkg/native_compiler/lib/configuration.dart @@ -2,6 +2,26 @@ // 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. +import 'package:cfg/ir/functions.dart'; +import 'package:cfg/ir/ssa_computation.dart'; +import 'package:cfg/passes/constant_propagation.dart'; +import 'package:cfg/passes/control_flow_optimizations.dart'; +import 'package:cfg/passes/pass.dart'; +import 'package:cfg/passes/simplification.dart'; +import 'package:cfg/passes/value_numbering.dart'; +import 'package:native_compiler/back_end/arm64/code_generator.dart'; +import 'package:native_compiler/back_end/arm64/constraints.dart'; +import 'package:native_compiler/back_end/back_end_state.dart'; +import 'package:native_compiler/back_end/code_generator.dart'; +import 'package:native_compiler/back_end/constraints.dart'; +import 'package:native_compiler/back_end/regalloc_checker.dart'; +import 'package:native_compiler/back_end/register_allocator.dart'; +import 'package:native_compiler/passes/lowering.dart'; +import 'package:native_compiler/passes/reorder_blocks.dart'; +import 'package:native_compiler/runtime/vm_defs.dart'; +import 'package:native_compiler/snapshot/image_writer.dart'; +import 'package:native_compiler/snapshot/macho/macho_image_writer.dart'; + enum TargetCPU { arm64; @@ -9,3 +29,78 @@ enum TargetCPU { static final List allowedNames = [for (final v in values) v.name]; static TargetCPU fromName(String name) => values.byName(name); } + +enum ImageFormat { + macho; + + static final String defaultName = macho.name; + static final List allowedNames = [for (final v in values) v.name]; + static ImageFormat fromName(String name) => values.byName(name); +} + +abstract base class Configuration { + final TargetCPU targetCPU; + final ImageFormat imageFormat; + final bool enableAsserts; + final String outputLibraryName; + + Configuration( + this.targetCPU, + this.imageFormat, { + required this.enableAsserts, + required this.outputLibraryName, + }); + + Pipeline createPipeline( + FunctionRegistry functionRegistry, + CodeConsumer consumeGeneratedCode, + ); + + Constraints createConstraints() => switch (targetCPU) { + TargetCPU.arm64 => Arm64Constraints(), + }; + + CodeGenerator createCodeGenerator(BackEndState backEndState) => + switch (targetCPU) { + TargetCPU.arm64 => Arm64CodeGenerator(backEndState), + }; + + ImageWriter createImageWriter() => switch (imageFormat) { + ImageFormat.macho => MachoImageWriter(targetCPU, outputLibraryName), + }; +} + +final class DevelopmentCompilerConfiguration extends Configuration { + DevelopmentCompilerConfiguration( + super.targetCPU, + super.imageFormat, { + required super.enableAsserts, + required super.outputLibraryName, + }); + + VMOffsets createVMOffsets() => switch (targetCPU) { + TargetCPU.arm64 => Arm64VMOffsets(), + }; + + @override + Pipeline createPipeline( + FunctionRegistry functionRegistry, + CodeConsumer consumeGeneratedCode, + ) { + final backEndState = BackEndState(); + backEndState.vmOffsets = createVMOffsets(); + backEndState.consumeGeneratedCode = consumeGeneratedCode; + final constraints = createConstraints(); + return Pipeline([ + SSAComputation(), + ValueNumbering(simplification: Simplification()), + ConstantPropagation(), + ControlFlowOptimizations(), + Lowering(functionRegistry), + ReorderBlocks(backEndState), + LinearScanRegisterAllocator(backEndState, constraints), + RegisterAllocationChecker(backEndState, constraints), + createCodeGenerator(backEndState), + ]); + } +} diff --git a/pkg/native_compiler/lib/modular_aot_compiler.dart b/pkg/native_compiler/lib/modular_aot_compiler.dart new file mode 100644 index 00000000000..ddceb45e187 --- /dev/null +++ b/pkg/native_compiler/lib/modular_aot_compiler.dart @@ -0,0 +1,301 @@ +// Copyright (c) 2026, 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. + +import 'dart:async'; +import 'dart:io' as io show exitCode, File, Platform; + +import 'package:args/args.dart' show ArgParser, ArgResults; +import 'package:cfg/ir/global_context.dart'; +import 'package:front_end/src/api_unstable/vm.dart' + show + CompilerOptions, + InvocationMode, + CfeDiagnosticMessage, + Verbosity, + parseExperimentalArguments, + parseExperimentalFlags, + resolveInputUri; +import 'package:kernel/ast.dart' as ast show Component; +import 'package:kernel/type_environment.dart' show TypeEnvironment; +import 'package:native_compiler/compilation_set.dart'; +import 'package:native_compiler/configuration.dart'; +import 'package:path/path.dart' as path; +import 'package:vm/kernel_front_end.dart' + show + badUsageExitCode, + compileTimeErrorExitCode, + compileToKernel, + convertToPackageUri, + createFrontEndFileSystem, + createFrontEndTarget, + ErrorDetector, + ErrorPrinter, + KernelCompilationArguments, + parseCommandLineDefines, + successExitCode, + writeDepfile; + +final ArgParser _argParser = ArgParser(allowTrailingOptions: true) + ..addOption( + 'platform', + help: 'Path to vm_platform.dill file', + defaultsTo: null, + ) + ..addOption( + 'packages', + help: 'Path to .dart_tool/package_config.json file', + defaultsTo: null, + ) + ..addOption( + 'output', + abbr: 'o', + help: 'Path to resulting snapshot file', + defaultsTo: null, + ) + ..addOption('depfile', help: 'Path to output Ninja depfile') + ..addOption( + 'depfile-target', + help: 'Override the target in the generated depfile', + hide: true, + ) + ..addMultiOption( + 'filesystem-root', + help: + 'A base path for the multi-root virtual file system.' + ' If multi-root file system is used, the input script and .dart_tool/package_config.json file should be specified using URI.', + ) + ..addOption( + 'filesystem-scheme', + help: 'The URI scheme for the multi-root virtual filesystem.', + ) + ..addOption( + 'target', + help: 'Target model that determines what core libraries are available', + allowed: ['vm', 'flutter', 'flutter_runner', 'dart_runner'], + defaultsTo: 'vm', + ) + ..addOption( + 'target-arch', + abbr: 'a', + help: 'Target CPU architecture.', + allowed: TargetCPU.allowedNames, + defaultsTo: TargetCPU.defaultName, + ) + ..addOption( + 'image-format', + help: 'Image format of the output snapshot.', + allowed: ImageFormat.allowedNames, + defaultsTo: ImageFormat.defaultName, + ) + ..addMultiOption( + 'define', + abbr: 'D', + help: 'The values for the environment constants (e.g. -Dkey=value).', + ) + ..addOption( + 'import-dill', + help: 'Import libraries from existing dill file', + defaultsTo: null, + ) + ..addFlag( + 'enable-asserts', + help: 'Whether asserts will be enabled.', + defaultsTo: false, + ) + ..addMultiOption( + 'enable-experiment', + help: 'Comma separated list of experimental features to enable.', + ) + ..addFlag( + 'help', + abbr: 'h', + negatable: false, + help: 'Print this help message.', + ) + ..addFlag( + 'track-widget-creation', + help: 'Run a kernel transformer to track creation locations for widgets.', + defaultsTo: false, + ) + ..addOption( + 'invocation-modes', + help: 'Provides information to the front end about how it is invoked.', + defaultsTo: '', + ) + ..addOption( + 'verbosity', + help: + 'Sets the verbosity level used for filtering messages during ' + 'compilation.', + defaultsTo: Verbosity.defaultValue, + ); + +final String _usage = + ''' +Usage: modular_aot_compiler --platform vm_platform.dill [--import-dill other.dill] [options] input.dart +Compiles Dart sources to modular snapshot with native code. + +Options: +${_argParser.usage} +'''; + +Future main(List arguments) async { + io.exitCode = await runCompilerWithCommandLineArguments(arguments); +} + +/// Run compiler with given [arguments] +/// and return exit code (0 on success, non-zero on failure). +Future runCompilerWithCommandLineArguments(List arguments) async { + final ArgResults options = _argParser.parse(arguments); + final String? platformKernel = options['platform']; + + if (options['help']) { + print(_usage); + return successExitCode; + } + + final String? input = options.rest.singleOrNull; + if (input == null || platformKernel == null) { + print(_usage); + return badUsageExitCode; + } + + final String outputFileName = + options['output'] ?? "$input.$snapshotExtension"; + final String? packages = options['packages']; + final String targetName = options['target']; + final String? fileSystemScheme = options['filesystem-scheme']; + final String? depfile = options['depfile']; + final String? depfileTarget = options['depfile-target']; + final List? fileSystemRoots = options['filesystem-root']; + final bool enableAsserts = options['enable-asserts']; + final List? experimentalFlags = options['enable-experiment']; + final Map environmentDefines = {}; + + if (!parseCommandLineDefines(options['define'], environmentDefines, _usage)) { + return badUsageExitCode; + } + + final String? importDill = options['import-dill']; + final String messageVerbosity = options['verbosity']; + final String cfeInvocationModes = options['invocation-modes']; + final bool trackWidgetCreation = options['track-widget-creation']; + + final TargetCPU targetCPU = TargetCPU.fromName(options['target-arch']); + final ImageFormat imageFormat = ImageFormat.fromName(options['image-format']); + + final fileSystem = createFrontEndFileSystem( + fileSystemScheme, + fileSystemRoots, + ); + + final Uri? packagesUri = packages != null ? resolveInputUri(packages) : null; + + final platformKernelUri = Uri.base.resolveUri(new Uri.file(platformKernel)); + + final additionalDills = []; + if (importDill != null) { + additionalDills.add(Uri.base.resolveUri(new Uri.file(importDill))); + } + + final verbosity = Verbosity.parseArgument(messageVerbosity); + final errorPrinter = ErrorPrinter(verbosity, println: print); + final errorDetector = ErrorDetector(previousErrorHandler: errorPrinter.call); + + Uri mainUri = resolveInputUri(input); + if (packagesUri != null) { + mainUri = await convertToPackageUri(fileSystem, mainUri, packagesUri); + } + + final compilerOptions = CompilerOptions() + ..sdkSummary = platformKernelUri + ..fileSystem = fileSystem + ..additionalDills = additionalDills + ..packagesFileUri = packagesUri + ..explicitExperimentalFlags = parseExperimentalFlags( + parseExperimentalArguments(experimentalFlags), + onError: print, + ) + ..onDiagnostic = (CfeDiagnosticMessage m) { + errorDetector(m); + } + ..embedSourceText = false + ..invocationModes = InvocationMode.parseArguments(cfeInvocationModes) + ..verbosity = verbosity + ..target = createFrontEndTarget( + targetName, + trackWidgetCreation: trackWidgetCreation, + supportMirrors: false, + isClosureContextLoweringEnabled: false, + ); + + if (compilerOptions.target == null) { + print('Failed to create front-end target $targetName.'); + return badUsageExitCode; + } + + final results = await compileToKernel( + KernelCompilationArguments( + source: mainUri, + options: compilerOptions, + requireMain: false, + includePlatform: false, + environmentDefines: Map.of(environmentDefines), + enableAsserts: enableAsserts, + ), + ); + + errorPrinter.printCompilationMessages(); + + final ast.Component? component = results.component; + if (errorDetector.hasCompilationErrors || component == null) { + return compileTimeErrorExitCode; + } + + final libraries = component.libraries + .where((lib) => !results.loadedLibraries.contains(lib)) + .toList(); + final typeEnvironment = TypeEnvironment( + results.coreTypes!, + results.classHierarchy!, + ); + final config = DevelopmentCompilerConfiguration( + targetCPU, + imageFormat, + enableAsserts: enableAsserts, + outputLibraryName: path.basename(outputFileName), + ); + final context = GlobalContext(typeEnvironment: typeEnvironment); + await GlobalContext.withContext(context, () { + final compilationSet = CompilationSet(libraries, config); + compilationSet.compileAllFunctions(); + final sink = io.File(outputFileName).openWrite(); + compilationSet.writeSnapshot(sink); + return sink.close(); + }); + + if (depfile != null) { + await writeDepfile( + fileSystem, + results.compiledSources!, + depfileTarget ?? outputFileName, + depfile, + ); + } + + return successExitCode; +} + +String snapshotExtension(String name) { + if (io.Platform.isLinux || io.Platform.isAndroid || io.Platform.isFuchsia) { + return ".so"; + } + if (io.Platform.isMacOS) { + return ".dylib"; + } + if (io.Platform.isWindows) { + return ".dll"; + } + throw 'Platform is not supported'; +} diff --git a/pkg/native_compiler/pubspec.yaml b/pkg/native_compiler/pubspec.yaml index fe45f8df890..5de9a4c07c9 100644 --- a/pkg/native_compiler/pubspec.yaml +++ b/pkg/native_compiler/pubspec.yaml @@ -10,11 +10,13 @@ resolution: workspace # Use 'any' constraints here; we get our versions from the DEPS file. dependencies: + args: any cfg: any crypto: any + front_end: any kernel: any + path: any + vm: any dev_dependencies: - front_end: any test: any - vm: any diff --git a/utils/modular_aot_compiler/BUILD.gn b/utils/modular_aot_compiler/BUILD.gn new file mode 100644 index 00000000000..a90151252c2 --- /dev/null +++ b/utils/modular_aot_compiler/BUILD.gn @@ -0,0 +1,24 @@ +# Copyright (c) 2026, 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. + +import("../../runtime/runtime_args.gni") +import("../aot_snapshot.gni") + +group("modular_aot_compiler") { + public_deps = [ ":modular_aot_compiler_snapshot" ] +} + +aot_snapshot("modular_aot_compiler_snapshot") { + main_dart = "../../pkg/native_compiler/bin/modular_aot_compiler.dart" + name = "modular_aot_compiler" + + output = "$root_gen_dir/modular_aot_compiler.dart.snapshot" + + # dartaotruntime has dart_product_config applied to it, so it is built in + # product mode in both release and product builds, and is only built in debug + # mode in debug builds. The following line ensures that the dartaotruntime and + # modular_aot_compiler.dart.snapshot in an SDK build are always compatible with + # each other. + force_product_mode = !dart_debug +}