diff --git a/pkg/dart2native/lib/dart2native.dart b/pkg/dart2native/lib/dart2native.dart index 1609f6a6239b..53e23edaef5e 100644 --- a/pkg/dart2native/lib/dart2native.dart +++ b/pkg/dart2native/lib/dart2native.dart @@ -112,6 +112,7 @@ Future generateKernelHelper({ String? packages, List defines = const [], String enableExperiment = '', + String? targetArch, String? targetOS, List extraGenKernelOptions = const [], String? nativeAssets, @@ -129,6 +130,7 @@ Future generateKernelHelper({ '--platform=${product ? productPlatformDill : platformDill}', if (product) '-Ddart.vm.product=true', if (enableExperiment.isNotEmpty) '--enable-experiment=$enableExperiment', + if (targetArch != null) '--target-arch=$targetArch', if (targetOS != null) '--target-os=$targetOS', if (fromDill) '--from-dill=$sourceFile', if (aot) '--aot', diff --git a/pkg/dart2native/lib/generate.dart b/pkg/dart2native/lib/generate.dart index d6210392df41..00b9c503f60a 100644 --- a/pkg/dart2native/lib/generate.dart +++ b/pkg/dart2native/lib/generate.dart @@ -40,6 +40,7 @@ extension type KernelGenerator._(_Generator _generator) { String? outputFile, String? debugFile, String? packages, + String? targetArch, String? targetOS, String? depFile, String enableExperiment = '', @@ -57,6 +58,7 @@ extension type KernelGenerator._(_Generator _generator) { kind: kind, outputFile: outputFile, packages: packages, + targetArch: targetArch, targetOS: targetOS, verbose: verbose, verbosity: verbosity, @@ -120,6 +122,11 @@ class _Generator { /// Specifies the file debugging information should be written to. final String? _debugFile; + /// Specifies the CPU architecture the executable is being generated for. This + /// must be provided when [_kind] is [Kind.exe], and it must match the current + /// CPU architecture. + final String? _targetArch; + /// Specifies the operating system the executable is being generated for. This /// must be provided when [_kind] is [Kind.exe], and it must match the current /// operating system. @@ -160,6 +167,7 @@ class _Generator { String? outputFile, String? debugFile, String? packages, + String? targetArch, String? targetOS, String? depFile, required String enableExperiment, @@ -173,6 +181,7 @@ class _Generator { _verbosity = verbosity, _enableAsserts = enableAsserts, _enableExperiment = enableExperiment, + _targetArch = targetArch, _targetOS = targetOS, _debugFile = debugFile, _outputFile = outputFile, @@ -182,6 +191,13 @@ class _Generator { _sourcePath = _normalize(sourceFile)!, _packages = _normalize(packages) { if (_kind == Kind.exe) { + if (_targetArch == null) { + throw ArgumentError('targetArch must be specified for executables.'); + } else if (_targetArch != Platform.architecture) { + throw UnsupportedError( + 'Cross compilation not supported for executables.'); + } + if (_targetOS == null) { throw ArgumentError('targetOS must be specified for executables.'); } else if (_targetOS != Platform.operatingSystem) { @@ -196,6 +212,10 @@ class _Generator { List? extraOptions, }) async { if (_verbose) { + if (_targetArch != null) { + print('Specializing Platform getters for target arch $_targetArch.'); + } + if (_targetOS != null) { print('Specializing Platform getters for target OS $_targetOS.'); } @@ -212,6 +232,7 @@ class _Generator { fromDill: await isKernelFile(_sourcePath), enableAsserts: _enableAsserts, enableExperiment: _enableExperiment, + targetArch: _targetArch, targetOS: _targetOS, extraGenKernelOptions: [ '--invocation-modes=compile', @@ -308,6 +329,7 @@ class _Generator { defines: _defines, enableAsserts: _enableAsserts, enableExperiment: _enableExperiment, + targetArch: _targetArch, targetOS: _targetOS, extraGenKernelOptions: [ '--invocation-modes=compile', diff --git a/pkg/dartdev/lib/src/commands/build.dart b/pkg/dartdev/lib/src/commands/build.dart index d909325367fa..43759b268abb 100644 --- a/pkg/dartdev/lib/src/commands/build.dart +++ b/pkg/dartdev/lib/src/commands/build.dart @@ -15,6 +15,7 @@ import 'package:front_end/src/api_prototype/compiler_options.dart' import 'package:native_assets_builder/native_assets_builder.dart'; import 'package:native_assets_cli/native_assets_cli_internal.dart'; import 'package:path/path.dart' as path; +import 'package:vm/target_arch.dart'; // For possible --target-arch values. import 'package:vm/target_os.dart'; // For possible --target-os values. import '../core.dart'; @@ -48,6 +49,9 @@ class BuildCommand extends DartdevCommand { allowed: ['exe', 'aot'], defaultsTo: 'exe', ) + ..addOption('target-arch', + help: 'Compile to a specific target CPU architecture.', + allowed: TargetArch.names) ..addOption('target-os', help: 'Compile to a specific target operating system.', allowed: TargetOS.names) @@ -108,6 +112,22 @@ class BuildCommand extends DartdevCommand { sourceUri.pathSegments.last.split('.').first, ), ); + String? targetArch = args['target-arch']; + if (format != Kind.exe) { + assert(format == Kind.aot); + // If we're generating an AOT snapshot and not an executable, then + // targetArch is allowed to be null for a platform-independent snapshot + // or a different platform than the host. + } else if (targetArch == null) { + targetArch = Platform.architecture; + } else if (targetArch != Platform.architecture) { + stderr.writeln( + "'dart build -f ${format.name}' does not support cross-arch compilation."); + stderr.writeln('Host arch: ${Platform.architecture}'); + stderr.writeln('Target arch: $targetArch'); + return 128; + } + String? targetOS = args['target-os']; if (format != Kind.exe) { assert(format == Kind.aot); @@ -181,6 +201,7 @@ class BuildCommand extends DartdevCommand { verbosity: args.option('verbosity')!, defines: [], packages: packageConfig?.toFilePath(), + targetArch: targetArch, targetOS: targetOS, enableExperiment: args.enabledExperiments.join(','), tempDir: tempDir, diff --git a/pkg/dartdev/lib/src/commands/compile.dart b/pkg/dartdev/lib/src/commands/compile.dart index 23ad8d601954..fcb4da646bf7 100644 --- a/pkg/dartdev/lib/src/commands/compile.dart +++ b/pkg/dartdev/lib/src/commands/compile.dart @@ -10,6 +10,7 @@ import 'package:dart2native/generate.dart'; import 'package:front_end/src/api_prototype/compiler_options.dart' show Verbosity; import 'package:path/path.dart' as path; +import 'package:vm/target_arch.dart'; // For possible --target-arch values. import 'package:vm/target_os.dart'; // For possible --target-os values. import '../core.dart'; @@ -455,6 +456,9 @@ Remove debugging information from the output and save it separately to the speci hide: true, valueHelp: 'opt1,opt2,...', ) + ..addOption('target-arch', + help: 'Compile to a specific target CPU architecture.', + allowed: TargetArch.names) ..addOption('target-os', help: 'Compile to a specific target operating system.', allowed: TargetOS.names) @@ -512,6 +516,22 @@ Remove debugging information from the output and save it separately to the speci } } + String? targetArch = args.option('target-arch'); + if (format != Kind.exe) { + assert(format == Kind.aot); + // If we're generating an AOT snapshot and not an executable, then + // targetOS is allowed to be null for a platform-independent snapshot + // or a different platform than the host. + } else if (targetArch == null) { + targetArch = Platform.architecture; + } else if (targetArch != Platform.architecture) { + stderr.writeln( + "'dart compile $commandName' does not support cross-arch compilation."); + stderr.writeln('Host arch: ${Platform.architecture}'); + stderr.writeln('Target arch: $targetArch'); + return 128; + } + String? targetOS = args.option('target-os'); if (format != Kind.exe) { assert(format == Kind.aot); @@ -527,6 +547,7 @@ Remove debugging information from the output and save it separately to the speci stderr.writeln('Target OS: $targetOS'); return 128; } + final tempDir = Directory.systemTemp.createTempSync(); try { final kernelGenerator = KernelGenerator( @@ -540,6 +561,7 @@ Remove debugging information from the output and save it separately to the speci debugFile: args.option('save-debugging-info'), verbose: verbose, verbosity: args.option('verbosity')!, + targetArch: targetArch, targetOS: targetOS, tempDir: tempDir, depFile: args.option('depfile'), diff --git a/pkg/dartdev/test/commands/compile_test.dart b/pkg/dartdev/test/commands/compile_test.dart index 6a4a33fd4604..fcc9c611214b 100644 --- a/pkg/dartdev/test/commands/compile_test.dart +++ b/pkg/dartdev/test/commands/compile_test.dart @@ -417,6 +417,7 @@ void defineCompileTests() { mainSrc: 'void main() {print(const String.fromEnvironment("cross"));}'); final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); final outFile = path.canonicalize(path.join(p.dirPath, 'myexe')); + final targetArch = Platform.architecture; final targetOS = Platform.isLinux ? 'macos' : 'linux'; final result = await p.run( @@ -424,6 +425,8 @@ void defineCompileTests() { 'compile', 'exe', '-v', + '--target-arch', + targetArch, '--target-os', targetOS, '-o', @@ -474,6 +477,7 @@ void defineCompileTests() { }, skip: isRunningOnIA32); test('Compile aot snapshot can compile to host platform', () async { + final targetArch = Platform.architecture; final targetOS = Platform.operatingSystem; final p = project(mainSrc: 'void main() { print("I love $targetOS"); }'); final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); @@ -484,6 +488,8 @@ void defineCompileTests() { 'compile', 'aot-snapshot', '-v', + '--target-arch', + targetArch, '--target-os', targetOS, '-o', diff --git a/pkg/frontend_server/lib/frontend_server.dart b/pkg/frontend_server/lib/frontend_server.dart index 8ce4e82c6bd3..c5285b5e6d3c 100644 --- a/pkg/frontend_server/lib/frontend_server.dart +++ b/pkg/frontend_server/lib/frontend_server.dart @@ -32,6 +32,7 @@ import 'package:kernel/target/targets.dart' show targets, TargetFlags; import 'package:package_config/package_config.dart'; import 'package:vm/incremental_compiler.dart' show IncrementalCompiler; import 'package:vm/kernel_front_end.dart'; +import 'package:vm/target_arch.dart'; // For possible --target-arch values. import 'package:vm/target_os.dart'; // For possible --target-os values. import 'src/javascript_bundle.dart'; @@ -50,6 +51,9 @@ ArgParser argParser = new ArgParser(allowTrailingOptions: true) ..addFlag('aot', help: 'Run compiler in AOT mode (enables whole-program transformations)', defaultsTo: false) + ..addOption('target-arch', + help: 'Compile to a specific target CPU architecture.', + allowed: TargetArch.names) ..addOption('target-os', help: 'Compile to a specific target operating system.', allowed: TargetOS.names) @@ -572,6 +576,13 @@ class FrontendCompiler implements CompilerInterface { } } + if (options['target-arch'] != null) { + if (!options['aot']) { + print('Error: --target-arch option must be used with --aot'); + return false; + } + } + if (options['target-os'] != null) { if (!options['aot']) { print('Error: --target-os option must be used with --aot'); @@ -673,6 +684,7 @@ class FrontendCompiler implements CompilerInterface { options['keep-class-names-implementing'], dynamicInterface: dynamicInterfaceUri, aot: options['aot'], + targetArch: options['target-arch'], targetOS: options['target-os'], useGlobalTypeFlowAnalysis: options['tfa'], useRapidTypeAnalysis: options['rta'], diff --git a/pkg/vm/lib/kernel_front_end.dart b/pkg/vm/lib/kernel_front_end.dart index a85e49239ccd..c81d4ed6b1a7 100644 --- a/pkg/vm/lib/kernel_front_end.dart +++ b/pkg/vm/lib/kernel_front_end.dart @@ -49,6 +49,7 @@ import 'modular/target/install.dart' show installAdditionalTargets; import 'modular/transformations/call_site_annotator.dart' as call_site_annotator; import 'native_assets/synthesizer.dart'; +import 'target_arch.dart'; import 'target_os.dart'; import 'transformations/deferred_loading.dart' as deferred_loading; import 'transformations/devirtualization.dart' as devirtualization @@ -123,6 +124,9 @@ void declareCompilerOptions(ArgParser args) { help: 'Enable global type flow analysis and related transformations in AOT mode.', defaultsTo: true); + args.addOption('target-arch', + help: 'Compile for a specific target CPU architecture when in AOT mode.', + allowed: TargetArch.names); args.addOption('target-os', help: 'Compile for a specific target operating system when in AOT mode.', allowed: TargetOS.names); @@ -222,6 +226,7 @@ Future runCompiler(ArgResults options, String usage) async { final String? depfileTarget = options['depfile-target']; final String? fromDillFile = options['from-dill']; final List? fileSystemRoots = options['filesystem-root']; + final String? targetArch = options['target-arch']; final String? targetOS = options['target-os']; final bool aot = options['aot']; final bool tfa = options['tfa']; @@ -353,6 +358,7 @@ Future runCompiler(ArgResults options, String usage) async { useProtobufTreeShakerV2: useProtobufTreeShakerV2, minimalKernel: minimalKernel, treeShakeWriteOnlyFields: treeShakeWriteOnlyFields, + targetArch: targetArch, targetOS: targetOS, fromDillFile: fromDillFile)); @@ -463,6 +469,7 @@ class KernelCompilationArguments { final bool treeShakeWriteOnlyFields; final bool useProtobufTreeShakerV2; final bool minimalKernel; + final String? targetArch; final String? targetOS; final String? fromDillFile; @@ -485,6 +492,7 @@ class KernelCompilationArguments { this.treeShakeWriteOnlyFields = false, this.useProtobufTreeShakerV2 = false, this.minimalKernel = false, + this.targetArch, this.targetOS, this.fromDillFile, }) : environmentDefines = environmentDefines ?? {}; @@ -625,10 +633,12 @@ Future runGlobalTransformations(Target target, Component component, // Perform unreachable code elimination, which should be performed before // type flow analysis so TFA won't take unreachable code into account. + final targetArch = args.targetArch; + final arch = targetArch != null ? TargetArch.fromString(targetArch)! : null; final targetOS = args.targetOS; final os = targetOS != null ? TargetOS.fromString(targetOS)! : null; final evaluator = vm_constant_evaluator.VMConstantEvaluator.create( - target, component, os, + target, component, arch, os, enableAsserts: args.enableAsserts, environmentDefines: args.environmentDefines, coreTypes: coreTypes); diff --git a/pkg/vm/lib/target_arch.dart b/pkg/vm/lib/target_arch.dart new file mode 100644 index 000000000000..a4d3bb193673 --- /dev/null +++ b/pkg/vm/lib/target_arch.dart @@ -0,0 +1,27 @@ +// Copyright (c) 2023, 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. + +enum TargetArch { + arm64('arm64'), + ia32('ia32'), + riscv32('riscv32'), + riscv64('riscv64'), + x64('x64'); + + final String name; + + const TargetArch(this.name); + + static final Iterable names = values.map((v) => v.name); + + static TargetArch? fromString(String s) { + for (final arch in values) { + if (arch.name == s) return arch; + } + return null; + } + + @override + String toString() => name; +} diff --git a/pkg/vm/lib/transformations/vm_constant_evaluator.dart b/pkg/vm/lib/transformations/vm_constant_evaluator.dart index ac1418a18e64..0d3e59503933 100644 --- a/pkg/vm/lib/transformations/vm_constant_evaluator.dart +++ b/pkg/vm/lib/transformations/vm_constant_evaluator.dart @@ -12,6 +12,7 @@ import 'package:kernel/type_environment.dart'; import 'package:front_end/src/api_prototype/constant_evaluator.dart' show ConstantEvaluator, ErrorReporter, EvaluationMode, SimpleErrorReporter; +import '../target_arch.dart'; import '../target_os.dart'; import 'pragma.dart'; @@ -26,6 +27,7 @@ import 'pragma.dart'; /// immediately-invoked closures wrapping complex initializing code in field /// initializers, we enable constant evaluation of functions. class VMConstantEvaluator extends ConstantEvaluator { + final TargetArch? _targetArch; final TargetOS? _targetOS; final Map _constantFields = {}; @@ -39,6 +41,7 @@ class VMConstantEvaluator extends ConstantEvaluator { Map? environmentDefines, TypeEnvironment typeEnvironment, ErrorReporter errorReporter, + this._targetArch, this._targetOS, this._pragmaParser, {bool enableTripleShift = false, @@ -56,15 +59,22 @@ class VMConstantEvaluator extends ConstantEvaluator { evaluationMode: EvaluationMode.strong) { // Only add Platform fields if the Platform class is part of the component // being evaluated. - if (_targetOS != null && _platformClass != null) { - _constantFields['operatingSystem'] = StringConstant(_targetOS.name); - _constantFields['pathSeparator'] = - StringConstant(_targetOS.pathSeparator); + if (_platformClass != null) { + if (_targetArch != null) { + _constantFields['architecture'] = StringConstant(_targetArch.name); + } + + if (_targetOS != null) { + _constantFields['operatingSystem'] = StringConstant(_targetOS.name); + _constantFields['pathSeparator'] = + StringConstant(_targetOS.pathSeparator); + } } } static VMConstantEvaluator create( - Target target, Component component, TargetOS? targetOS, + Target target, Component component, + TargetArch? targetArch, TargetOS? targetOS, {bool evaluateAnnotations = true, bool enableTripleShift = false, bool enableConstructorTearOff = false, @@ -90,6 +100,7 @@ class VMConstantEvaluator extends ConstantEvaluator { environmentDefines, typeEnvironment, errorReporter ?? const SimpleErrorReporter(), + targetArch, targetOS, ConstantPragmaAnnotationParser(coreTypes, target), enableTripleShift: enableTripleShift,