Modular AOT compiler main

Issue: https://github.com/dart-lang/sdk/issues/61635
Change-Id: I55eaf5267c63d75b317276dfeab4d27e93139db3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/475407
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Alexander Markov
2026-01-26 09:25:01 -08:00
committed by Commit Queue
parent 19607c4a59
commit ca604cf41a
7 changed files with 565 additions and 2 deletions
+1
View File
@@ -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" ]
}
}
@@ -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<String> args) => modular_aot_compiler.main(args);
@@ -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<ast.Library> libraries;
final Configuration config;
final FunctionRegistry functionRegistry = FunctionRegistry();
final RecognizedMethods recognizedMethods = CommonRecognizedMethods();
final List<CFunction> _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<List<int>> sink) {
_snapshot.writeModuleSnapshot();
_imageWriter.addReadOnlyData(
_snapshot.out.getContents(),
_snapshot.out.position,
);
_imageWriter.writeTo(sink);
}
}
@@ -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<String> 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<String> 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),
]);
}
}
@@ -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: <String>['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<void> main(List<String> arguments) async {
io.exitCode = await runCompilerWithCommandLineArguments(arguments);
}
/// Run compiler with given [arguments]
/// and return exit code (0 on success, non-zero on failure).
Future<int> runCompilerWithCommandLineArguments(List<String> 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<String>? fileSystemRoots = options['filesystem-root'];
final bool enableAsserts = options['enable-asserts'];
final List<String>? experimentalFlags = options['enable-experiment'];
final Map<String, String> 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 = <Uri>[];
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';
}
+4 -2
View File
@@ -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
+24
View File
@@ -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
}