Files
sdk/pkg/dart2bytecode/lib/dart2bytecode.dart
Sigmund Cherem 782c4555b4 [dyn_modules] Expose flag in dart2bytecode to enable dcalls
This change introduces flags to allow the use of dynamic calls now that
they have their corresponding validation and runtime check. This will
enable us to add end-to-end tests next.

TEST=none yet - will be added in subsequent CL (see CL chain)

Bug: b/448095881
Change-Id: I82448824e94dd940ce5346f79554d34847ecb610
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/498320
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Sigmund Cherem <sigmund@google.com>
2026-05-13 10:36:05 -07:00

383 lines
12 KiB
Dart

// 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.
import 'dart:async';
import 'dart:io' as io show exitCode, File, IOSink;
import 'package:args/args.dart' show ArgParser, ArgResults;
import 'package:front_end/src/api_unstable/vm.dart'
show
CompilerOptions,
InvocationMode,
CfeDiagnosticMessage,
Verbosity,
parseExperimentalArguments,
parseExperimentalFlags,
resolveInputUri;
import 'package:kernel/ast.dart' show Component;
import 'package:vm/kernel_front_end.dart'
show
badUsageExitCode,
compileTimeErrorExitCode,
compileToKernel,
convertToPackageUri,
createFrontEndFileSystem,
createFrontEndTarget,
ErrorDetector,
ErrorPrinter,
KernelCompilationArguments,
parseCommandLineDefines,
successExitCode,
writeDepfile;
import 'package:vm/transformations/prefix_library_uris.dart'
as prefix_library_uris;
import 'bytecode_serialization.dart' show BytecodeSizeStatistics;
import 'bytecode_generator.dart' show generateBytecode;
import 'options.dart' show BytecodeOptions;
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 bytecode 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',
)
..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(
'bytecode-options',
help: 'Specify options for bytecode generation:',
valueHelp: 'opt1,opt2,...',
allowed: BytecodeOptions.commandLineFlags.keys,
allowedHelp: BytecodeOptions.commandLineFlags,
)
..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-creation-locations',
help:
'Run a kernel transformer to track creation locations for'
' classes annotated with @pragma(\'track-creation-locations\').',
defaultsTo: false,
aliases: [
// TODO(http://dartbug.com/63225): Remove this once flutter is migrated
// to the new flag.
'track-widget-creation',
],
)
..addOption(
'invocation-modes',
help: 'Provides information to the front end about how it is invoked.',
defaultsTo: '',
)
..addOption(
'validate',
help:
'Validate dynamic module against specified dynamic interface YAML file',
defaultsTo: null,
)
..addOption(
'verbosity',
help:
'Sets the verbosity level used for filtering messages during '
'compilation.',
defaultsTo: Verbosity.defaultValue,
)
..addOption(
'prefix-library-uris',
help: 'Slash-separated prefix to add to all library uris',
defaultsTo: '',
)
..addFlag(
'allow-dynamic-calls-in-dynamic-modules',
help: 'Allow dynamic calls in dynamic modules',
defaultsTo: false,
)
..addMultiOption(
'extra-selectors-allowed-in-dynamic-calls',
help:
'Selector names that weren\'t exposed by the host, but are allowed in '
'dynamic calls within the dynamic module',
);
final String _usage =
'''
Usage: dart2bytecode --platform vm_platform.dill [--import-dill host_app.dill] [--validate dynamic_interface.yaml] [options] input.dart
Compiles Dart sources to Dart bytecode.
Options:
${_argParser.usage}
''';
Future<void> main(List<String> arguments) async {
io.exitCode = await runCompilerWithCommandLineArguments(arguments);
}
/// Run bytecode compiler tool 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.bytecode";
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? validateDynamicInterface = options['validate'];
final String messageVerbosity = options['verbosity'];
final String cfeInvocationModes = options['invocation-modes'];
final bool trackCreationLocations = options['track-creation-locations'];
final List<String>? bytecodeGeneratorOptions = options['bytecode-options'];
final String libraryUrisPrefix = options['prefix-library-uris']!;
final bool allowDynamicCallsInDynamicModules =
options['allow-dynamic-calls-in-dynamic-modules'];
final List<String> dynamicCallsSelectorAllowList =
options['extra-selectors-allowed-in-dynamic-calls'];
return await runCompilerWithOptions(
input: input,
platformKernel: platformKernel,
outputFileName: outputFileName,
targetName: targetName,
packages: packages,
importDill: importDill,
validateDynamicInterface: validateDynamicInterface,
enableAsserts: enableAsserts,
experimentalFlags: experimentalFlags,
environmentDefines: environmentDefines,
fileSystemScheme: fileSystemScheme,
fileSystemRoots: fileSystemRoots,
messageVerbosity: messageVerbosity,
cfeInvocationModes: cfeInvocationModes,
trackCreationLocations: trackCreationLocations,
bytecodeGeneratorOptions: bytecodeGeneratorOptions,
depfile: depfile,
depfileTarget: depfileTarget,
libraryUrisPrefix: libraryUrisPrefix,
allowDynamicCallsInDynamicModules: allowDynamicCallsInDynamicModules,
dynamicCallsSelectorAllowList: dynamicCallsSelectorAllowList,
);
}
/// Run bytecode compiler tool with given options
/// and return exit code (0 on success, non-zero on failure).
Future<int> runCompilerWithOptions({
required String input,
required String platformKernel,
required String outputFileName,
required String targetName,
String? packages,
String? importDill,
String? validateDynamicInterface,
bool enableAsserts = false,
List<String>? experimentalFlags,
Map<String, String> environmentDefines = const {},
String? fileSystemScheme,
List<String>? fileSystemRoots,
String messageVerbosity = Verbosity.defaultValue,
void Function(String) printMessage = print,
String cfeInvocationModes = '',
bool trackCreationLocations = false,
List<String>? bytecodeGeneratorOptions,
String? depfile,
String? depfileTarget,
required String libraryUrisPrefix,
bool allowDynamicCallsInDynamicModules = false,
List<String> dynamicCallsSelectorAllowList = const [],
}) async {
final fileSystem = createFrontEndFileSystem(
fileSystemScheme,
fileSystemRoots,
);
final Uri? packagesUri = packages != null ? resolveInputUri(packages) : null;
final platformKernelUri = Uri.base.resolveUri(new Uri.file(platformKernel));
final List<Uri> additionalDillModules = <Uri>[];
if (importDill != null) {
additionalDillModules.add(Uri.base.resolveUri(new Uri.file(importDill)));
}
final Uri? dynamicInterfaceSpecificationUri =
(validateDynamicInterface != null)
? resolveInputUri(validateDynamicInterface)
: null;
final verbosity = Verbosity.parseArgument(messageVerbosity);
final errorPrinter = ErrorPrinter(verbosity, println: printMessage);
final errorDetector = ErrorDetector(previousErrorHandler: errorPrinter.call);
Uri mainUri = resolveInputUri(input);
if (packagesUri != null) {
mainUri = await convertToPackageUri(fileSystem, mainUri, packagesUri);
}
final BytecodeOptions bytecodeOptions = BytecodeOptions(
enableAsserts: enableAsserts,
)..parseCommandLineFlags(bytecodeGeneratorOptions);
final CompilerOptions compilerOptions = CompilerOptions()
..sdkSummary = platformKernelUri
..fileSystem = fileSystem
..additionalDillModules = additionalDillModules
..packagesFileUri = packagesUri
..dynamicInterfaceSpecificationUri = dynamicInterfaceSpecificationUri
..allowDynamicCallsInDynamicModules = allowDynamicCallsInDynamicModules
..dynamicCallsSelectorAllowList = dynamicCallsSelectorAllowList
..explicitExperimentalFlags = parseExperimentalFlags(
parseExperimentalArguments(experimentalFlags),
onError: printMessage,
)
..onDiagnostic = (CfeDiagnosticMessage m) {
errorDetector(m);
}
..embedSourceText = bytecodeOptions.embedSourceText
..invocationModes = InvocationMode.parseArguments(cfeInvocationModes)
..verbosity = verbosity
..target = createFrontEndTarget(
targetName,
trackCreationLocations: trackCreationLocations,
supportMirrors: false,
isClosureContextLoweringEnabled:
bytecodeOptions.isClosureContextLoweringEnabled,
);
if (compilerOptions.target == null) {
printMessage('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();
Component? component = results.component;
if (errorDetector.hasCompilationErrors || component == null) {
return compileTimeErrorExitCode;
}
component = prefix_library_uris.prefixLibraryUris(
component,
results.loadedLibraries,
libraryUrisPrefix,
);
if (bytecodeOptions.showBytecodeSizeStatistics) {
BytecodeSizeStatistics.reset();
}
final io.IOSink sink = io.File(outputFileName).openWrite();
generateBytecode(
component,
sink,
libraries: component.libraries
.where((lib) => !results.loadedLibraries.contains(lib))
.toList(),
hierarchy: results.classHierarchy!,
coreTypes: results.coreTypes!,
options: bytecodeOptions,
target: compilerOptions.target!,
extraLoadedLibraries: results.loadedLibraries,
);
await sink.close();
if (bytecodeOptions.showBytecodeSizeStatistics) {
BytecodeSizeStatistics.dump();
}
if (depfile != null) {
await writeDepfile(
fileSystem,
results.compiledSources!,
depfileTarget ?? outputFileName,
depfile,
);
}
return successExitCode;
}