diff --git a/pkg/dart2native/lib/dart2native.dart b/pkg/dart2native/lib/dart2native.dart index d89c33de5fa..75b08598e96 100644 --- a/pkg/dart2native/lib/dart2native.dart +++ b/pkg/dart2native/lib/dart2native.dart @@ -58,12 +58,14 @@ Future generateAotKernel( String? packages, List defines, {String enableExperiment = '', + String? targetOS, List extraGenKernelOptions = const []}) { return Process.run(dart, [ genKernel, '--platform', platformDill, if (enableExperiment.isNotEmpty) '--enable-experiment=$enableExperiment', + if (targetOS != null) '--target-os=$targetOS', '--aot', '-Ddart.vm.product=true', ...(defines.map((d) => '-D$d')), diff --git a/pkg/dart2native/lib/generate.dart b/pkg/dart2native/lib/generate.dart index 20a36017238..8e9e1816c64 100644 --- a/pkg/dart2native/lib/generate.dart +++ b/pkg/dart2native/lib/generate.dart @@ -25,6 +25,7 @@ Future generateNative({ String? outputFile, String? debugFile, String? packages, + String? targetOS, required List defines, String enableExperiment = '', bool enableAsserts = false, @@ -53,6 +54,9 @@ Future generateNative({ debugFile != null ? path.canonicalize(path.normalize(debugFile)) : null; if (verbose) { + if (targetOS != null) { + print('Specializing Platform getters for target OS $targetOS.'); + } print('Compiling $sourcePath to $outputPath using format $kind:'); print('Generating AOT kernel dill.'); } @@ -61,6 +65,7 @@ Future generateNative({ final kernelResult = await generateAotKernel(Platform.executable, genKernel, productPlatformDill, sourcePath, kernelFile, packages, defines, enableExperiment: enableExperiment, + targetOS: targetOS, extraGenKernelOptions: [ '--invocation-modes=compile', '--verbosity=$verbosity', diff --git a/pkg/dartdev/lib/src/commands/compile.dart b/pkg/dartdev/lib/src/commands/compile.dart index 42e6b4e3d19..2eb3bd7e6a3 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_os.dart'; // For possible --target-os values. import '../core.dart'; import '../experiments.dart'; @@ -281,6 +282,9 @@ Remove debugging information from the output and save it separately to the speci hide: true, valueHelp: 'opt1,opt2,...', ) + ..addOption('target-os', + help: 'Compile to a specific target operating system.', + allowed: TargetOS.names) ..addExperimentalFlags(verbose: verbose); } @@ -327,6 +331,7 @@ Remove debugging information from the output and save it separately to the speci verbose: verbose, verbosity: args['verbosity'], extraOptions: args['extra-gen-snapshot-options'], + targetOS: args['target-os'], ); return 0; } catch (e, st) { diff --git a/pkg/dartdev/pubspec.yaml b/pkg/dartdev/pubspec.yaml index e60858d6202..2a0701030fe 100644 --- a/pkg/dartdev/pubspec.yaml +++ b/pkg/dartdev/pubspec.yaml @@ -25,6 +25,7 @@ dependencies: pub: any telemetry: any usage: any + vm: any yaml: any # Use 'any' constraints here; we get our versions from the DEPS file. diff --git a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart index 60d944e71a7..7c539c10e10 100644 --- a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart +++ b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart @@ -35,6 +35,7 @@ import 'package:_fe_analyzer_shared/src/exhaustiveness/exhaustive.dart'; import 'package:_fe_analyzer_shared/src/exhaustiveness/space.dart'; import 'package:_fe_analyzer_shared/src/exhaustiveness/static_type.dart'; +import '../../base/nnbd_mode.dart'; import '../fasta_codes.dart'; import 'constant_int_folder.dart'; @@ -177,7 +178,18 @@ void transformProcedure( enum EvaluationMode { weak, agnostic, - strong, + strong; + + static EvaluationMode fromNnbdMode(NnbdMode nnbdMode) { + switch (nnbdMode) { + case NnbdMode.Weak: + return EvaluationMode.weak; + case NnbdMode.Strong: + return EvaluationMode.strong; + case NnbdMode.Agnostic: + return EvaluationMode.agnostic; + } + } } class ConstantWeakener extends ComputeOnceConstantVisitor { @@ -3144,7 +3156,7 @@ class ConstantEvaluator implements ExpressionVisitor { /// This compute the constant as seen in the current evaluation mode even when /// the constant is defined in a library compiled with the agnostic evaluation /// mode. - Constant _evaluateExpressionInContext(Member member, Expression expression) { + Constant evaluateExpressionInContext(Member member, Expression expression) { StaticTypeContext? oldStaticTypeContext = _staticTypeContext; _staticTypeContext = new StaticTypeContext(member, typeEnvironment); Constant constant = _evaluateSubexpression(expression); @@ -3166,7 +3178,7 @@ class ConstantEvaluator implements ExpressionVisitor { visitedLibraries.add(target.enclosingLibrary); if (target is Field) { if (target.isConst) { - return _evaluateExpressionInContext(target, target.initializer!); + return evaluateExpressionInContext(target, target.initializer!); } return createEvaluationErrorConstant( node, @@ -3241,7 +3253,7 @@ class ConstantEvaluator implements ExpressionVisitor { Constant _getFromEnvironmentDefaultValue(Procedure target) { VariableDeclaration variable = target.function.namedParameters .singleWhere((v) => v.name == 'defaultValue'); - return _evaluateExpressionInContext(target, variable.initializer!); + return evaluateExpressionInContext(target, variable.initializer!); } Constant _handleFromEnvironment( diff --git a/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart b/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart index bfb93ed9e7b..19927457cec 100644 --- a/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart +++ b/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart @@ -1618,7 +1618,6 @@ class KernelTarget extends TargetImplementation { _getConstantEvaluationMode(); constants.EvaluationMode _getConstantEvaluationMode() { - constants.EvaluationMode evaluationMode; // If nnbd is not enabled we will use weak evaluation mode. This is needed // because the SDK might be agnostic and therefore needs to be weakened // for legacy mode. @@ -1627,18 +1626,7 @@ class KernelTarget extends TargetImplementation { loader.nnbdMode == NnbdMode.Weak, "Non-weak nnbd mode found without experiment enabled: " "${loader.nnbdMode}."); - switch (loader.nnbdMode) { - case NnbdMode.Weak: - evaluationMode = constants.EvaluationMode.weak; - break; - case NnbdMode.Strong: - evaluationMode = constants.EvaluationMode.strong; - break; - case NnbdMode.Agnostic: - evaluationMode = constants.EvaluationMode.agnostic; - break; - } - return evaluationMode; + return constants.EvaluationMode.fromNnbdMode(loader.nnbdMode); } void verify() { diff --git a/pkg/front_end/pubspec.yaml b/pkg/front_end/pubspec.yaml index aedd3beb589..b6fbfbd544e 100644 --- a/pkg/front_end/pubspec.yaml +++ b/pkg/front_end/pubspec.yaml @@ -6,7 +6,7 @@ name: front_end publish_to: none environment: - sdk: '>=2.13.0 <3.0.0' + sdk: '>=2.17.0 <3.0.0' # Use 'any' constraints here; we get our versions from the DEPS file. dependencies: diff --git a/pkg/frontend_server/lib/frontend_server.dart b/pkg/frontend_server/lib/frontend_server.dart index a15c50bf3ec..daeeef69f2e 100644 --- a/pkg/frontend_server/lib/frontend_server.dart +++ b/pkg/frontend_server/lib/frontend_server.dart @@ -35,6 +35,7 @@ import 'package:usage/uuid/uuid.dart'; import 'package:vm/incremental_compiler.dart' show IncrementalCompiler; import 'package:vm/kernel_front_end.dart'; +import 'package:vm/target_os.dart'; // For possible --target-os values. import 'src/javascript_bundle.dart'; @@ -51,6 +52,9 @@ ArgParser argParser = ArgParser(allowTrailingOptions: true) ..addFlag('aot', help: 'Run compiler in AOT mode (enables whole-program transformations)', defaultsTo: false) + ..addOption('target-os', + help: 'Compile to a specific target operating system.', + allowed: TargetOS.names) ..addFlag('support-mirrors', help: 'Whether dart:mirrors is supported. By default dart:mirrors is ' 'supported when --aot and --minimal-kernel are not used.', @@ -513,6 +517,13 @@ class FrontendCompiler implements CompilerInterface { } } + if (options['target-os'] != null) { + if (!options['aot']) { + print('Error: --target-os option must be used with --aot'); + return false; + } + } + if (options['support-mirrors'] == true) { if (options['aot']) { print('Error: --support-mirrors option cannot be used with --aot'); @@ -592,6 +603,7 @@ class FrontendCompiler implements CompilerInterface { includePlatform: options['link-platform'], deleteToStringPackageUris: options['delete-tostring-package-uri'], aot: options['aot'], + targetOS: options['target-os'], useGlobalTypeFlowAnalysis: options['tfa'], useRapidTypeAnalysis: options['rta'], environmentDefines: environmentDefines, diff --git a/pkg/kernel/lib/core_types.dart b/pkg/kernel/lib/core_types.dart index 12799ae1b2e..68f8adb7652 100644 --- a/pkg/kernel/lib/core_types.dart +++ b/pkg/kernel/lib/core_types.dart @@ -214,6 +214,8 @@ class CoreTypes { late final Procedure objectEquals = index.getProcedure('dart:core', 'Object', '=='); + late final Class? platformClass = index.tryGetClass('dart:io', 'Platform'); + late final Class pragmaClass = index.getClass('dart:core', 'pragma'); late final Field pragmaName = index.getField('dart:core', 'pragma', 'name'); diff --git a/pkg/vm/lib/kernel_front_end.dart b/pkg/vm/lib/kernel_front_end.dart index c13aed64eca..80937276a0b 100644 --- a/pkg/vm/lib/kernel_front_end.dart +++ b/pkg/vm/lib/kernel_front_end.dart @@ -46,6 +46,7 @@ import 'package:kernel/target/targets.dart' show Target, TargetFlags, getTarget; import 'package:package_config/package_config.dart' show loadPackageConfigUri; import 'http_filesystem.dart' show HttpAwareFileSystem; +import 'target_os.dart'; import 'native_assets/synthesizer.dart'; import 'target/install.dart' show installAdditionalTargets; import 'transformations/devirtualization.dart' as devirtualization @@ -61,6 +62,7 @@ import 'transformations/obfuscation_prohibitions_annotator.dart' import 'transformations/call_site_annotator.dart' as call_site_annotator; import 'transformations/unreachable_code_elimination.dart' as unreachable_code_elimination; +import 'transformations/vm_constant_evaluator.dart' as vm_constant_evaluator; import 'transformations/deferred_loading.dart' as deferred_loading; import 'transformations/to_string_transformer.dart' as to_string_transformer; @@ -111,6 +113,9 @@ void declareCompilerOptions(ArgParser args) { help: 'Enable global type flow analysis and related transformations in AOT mode.', defaultsTo: true); + args.addOption('target-os', + help: 'Compile for a specific target operating system when in AOT mode.', + allowed: TargetOS.names); args.addFlag('rta', help: 'Use rapid type analysis for faster compilation in AOT mode.', defaultsTo: true); @@ -193,6 +198,7 @@ Future runCompiler(ArgResults options, String usage) async { final String? depfile = options['depfile']; final String? fromDillFile = options['from-dill']; final List? fileSystemRoots = options['filesystem-root']; + final String? targetOS = options['target-os']; final bool aot = options['aot']; final bool tfa = options['tfa']; final bool rta = options['rta']; @@ -305,6 +311,7 @@ Future runCompiler(ArgResults options, String usage) async { useProtobufTreeShakerV2: useProtobufTreeShakerV2, minimalKernel: minimalKernel, treeShakeWriteOnlyFields: treeShakeWriteOnlyFields, + targetOS: targetOS, fromDillFile: fromDillFile); errorPrinter.printCompilationMessages(); @@ -406,6 +413,7 @@ Future compileToKernel( bool useProtobufTreeShakerV2 = false, bool minimalKernel = false, bool treeShakeWriteOnlyFields = false, + String? targetOS = null, String? fromDillFile = null, }) async { // Replace error handler to detect if there are compilation errors. @@ -450,6 +458,9 @@ Future compileToKernel( if ((aot || minimalKernel) && component != null) { await runGlobalTransformations(target, component, useGlobalTypeFlowAnalysis, enableAsserts, useProtobufTreeShakerV2, errorDetector, + environmentDefines: options.environmentDefines, + nnbdMode: options.nnbdMode, + targetOS: targetOS, minimalKernel: minimalKernel, treeShakeWriteOnlyFields: treeShakeWriteOnlyFields, useRapidTypeAnalysis: useRapidTypeAnalysis); @@ -512,7 +523,10 @@ Future runGlobalTransformations( ErrorDetector errorDetector, {bool minimalKernel = false, bool treeShakeWriteOnlyFields = false, - bool useRapidTypeAnalysis = true}) async { + bool useRapidTypeAnalysis = true, + NnbdMode nnbdMode = NnbdMode.Weak, + Map? environmentDefines, + String? targetOS}) async { assert(!target.flags.supportMirrors); if (errorDetector.hasCompilationErrors) return; @@ -526,9 +540,14 @@ Future runGlobalTransformations( // when building a platform dill file for VM/JIT case. mixin_deduplication.transformComponent(component); - // Unreachable code elimination transformation should be performed - // before type flow analysis so TFA won't take unreachable code into account. - unreachable_code_elimination.transformComponent(component, enableAsserts); + // Perform unreachable code elimination, which should be performed before + // type flow analysis so TFA won't take unreachable code into account. + final os = targetOS != null ? TargetOS.fromString(targetOS)! : null; + final evaluator = vm_constant_evaluator.VMConstantEvaluator.create( + target, component, os, nnbdMode, + environmentDefines: environmentDefines, coreTypes: coreTypes); + unreachable_code_elimination.transformComponent( + component, enableAsserts, evaluator); if (useGlobalTypeFlowAnalysis) { globalTypeFlow.transformComponent(target, coreTypes, component, diff --git a/pkg/vm/lib/target_os.dart b/pkg/vm/lib/target_os.dart new file mode 100644 index 00000000000..741c090aba4 --- /dev/null +++ b/pkg/vm/lib/target_os.dart @@ -0,0 +1,29 @@ +// 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 TargetOS { + android('android', '/'), + fuchsia('fuchsia', '/'), + iOS('ios', '/'), + linux('linux', '/'), + macOS('macos', '/'), + windows('windows', '\\'); + + final String name; + final String pathSeparator; + + const TargetOS(this.name, this.pathSeparator); + + static final Iterable names = values.map((v) => v.name); + + static TargetOS? fromString(String s) { + for (final os in values) { + if (os.name == s) return os; + } + return null; + } + + @override + String toString() => name; +} diff --git a/pkg/vm/lib/transformations/unreachable_code_elimination.dart b/pkg/vm/lib/transformations/unreachable_code_elimination.dart index eb0526bae83..4eb51030749 100644 --- a/pkg/vm/lib/transformations/unreachable_code_elimination.dart +++ b/pkg/vm/lib/transformations/unreachable_code_elimination.dart @@ -3,20 +3,38 @@ // BSD-style license that can be found in the LICENSE file. import 'package:kernel/ast.dart'; +import 'package:kernel/type_environment.dart' show StaticTypeContext; + +import 'vm_constant_evaluator.dart' show VMConstantEvaluator; /// Simple unreachable code elimination: removes asserts and if statements /// with constant conditions. Does a very limited constant folding of /// logical expressions. -Component transformComponent(Component component, bool enableAsserts) { - new SimpleUnreachableCodeElimination(enableAsserts) +/// +/// Also performs some additional constant evaluation via [evaluator], which is +/// applied to certain types of expressions (currently only StaticGet). +Component transformComponent( + Component component, bool enableAsserts, VMConstantEvaluator evaluator) { + SimpleUnreachableCodeElimination(enableAsserts, evaluator) .visitComponent(component, null); return component; } class SimpleUnreachableCodeElimination extends RemovingTransformer { final bool enableAsserts; + final VMConstantEvaluator constantEvaluator; + StaticTypeContext? _staticTypeContext; - SimpleUnreachableCodeElimination(this.enableAsserts); + SimpleUnreachableCodeElimination(this.enableAsserts, this.constantEvaluator); + + @override + TreeNode defaultMember(Member node, TreeNode? removalSentinel) { + _staticTypeContext = + StaticTypeContext(node, constantEvaluator.typeEnvironment); + final result = super.defaultMember(node, removalSentinel); + _staticTypeContext = null; + return result; + } bool _isBoolConstant(Expression node) => node is BoolLiteral || @@ -115,6 +133,13 @@ class SimpleUnreachableCodeElimination extends RemovingTransformer { if (target is Field && target.isConst) { throw 'StaticGet from const field $target should be evaluated by front-end: $node'; } + if (constantEvaluator.transformerShouldEvaluateExpression(node)) { + final context = _staticTypeContext!; + final result = constantEvaluator.evaluate(context, node); + assert(result is! UnevaluatedConstant); + return new ConstantExpression(result, node.getStaticType(context)) + ..fileOffset = node.fileOffset; + } return node; } diff --git a/pkg/vm/lib/transformations/vm_constant_evaluator.dart b/pkg/vm/lib/transformations/vm_constant_evaluator.dart new file mode 100644 index 00000000000..73275eec160 --- /dev/null +++ b/pkg/vm/lib/transformations/vm_constant_evaluator.dart @@ -0,0 +1,179 @@ +// 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. + +import 'package:kernel/ast.dart'; +import 'package:kernel/class_hierarchy.dart' show ClassHierarchy; +import 'package:kernel/core_types.dart' show CoreTypes; +import 'package:kernel/target/targets.dart' + show ConstantsBackend, DartLibrarySupport, Target; +import 'package:kernel/type_environment.dart'; + +import 'package:front_end/src/base/nnbd_mode.dart'; +import 'package:front_end/src/fasta/kernel/constant_evaluator.dart' + show + AbortConstant, + ConstantEvaluator, + ErrorReporter, + EvaluationMode, + SimpleErrorReporter; + +import '../target_os.dart'; + +/// Evaluates uses of static fields and getters using VM-specific and +/// platform-specific knowledge. +/// +/// [targetOS] represents the target operating system and is used when +/// evaluating static fields and getters annotated with "vm:platform-const". +/// +/// If [enableConstFunctions] is false, then only getters that return the +/// result of a single expression can be evaluated. +class VMConstantEvaluator extends ConstantEvaluator { + final TargetOS? _targetOS; + final Map _constantFields = {}; + + final Class? _platformClass; + final Class _pragmaClass; + final Field _pragmaName; + + VMConstantEvaluator( + DartLibrarySupport dartLibrarySupport, + ConstantsBackend backend, + Component component, + Map? environmentDefines, + TypeEnvironment typeEnvironment, + ErrorReporter errorReporter, + this._targetOS, + {bool enableTripleShift = false, + bool enableConstFunctions = false, + bool errorOnUnevaluatedConstant = false, + EvaluationMode evaluationMode = EvaluationMode.weak}) + : _platformClass = typeEnvironment.coreTypes.platformClass, + _pragmaClass = typeEnvironment.coreTypes.pragmaClass, + _pragmaName = typeEnvironment.coreTypes.pragmaName, + super(dartLibrarySupport, backend, component, environmentDefines, + typeEnvironment, errorReporter, + enableTripleShift: enableTripleShift, + enableConstFunctions: enableConstFunctions, + errorOnUnevaluatedConstant: errorOnUnevaluatedConstant, + evaluationMode: evaluationMode) { + // Only add Platform fields if the Platform class is part of the component + // being evaluated. + final os = _targetOS; + if (os != null && _platformClass != null) { + _constantFields['_operatingSystem'] = StringConstant(os.name); + _constantFields['_pathSeparator'] = StringConstant(os.pathSeparator); + } + } + + static VMConstantEvaluator create( + Target target, Component component, TargetOS? targetOS, NnbdMode nnbdMode, + {bool evaluateAnnotations = true, + bool enableTripleShift = false, + bool enableConstFunctions = false, + bool enableConstructorTearOff = false, + bool errorOnUnevaluatedConstant = false, + Map? environmentDefines, + CoreTypes? coreTypes, + ClassHierarchy? hierarchy}) { + coreTypes ??= CoreTypes(component); + hierarchy ??= ClassHierarchy(component, coreTypes); + + final typeEnvironment = TypeEnvironment(coreTypes, hierarchy); + + // Use the empty environment if unevaluated constants are not supported, + // as passing null for environmentDefines in this case is an error. + environmentDefines ??= + target.constantsBackend.supportsUnevaluatedConstants ? null : {}; + return VMConstantEvaluator( + target.dartLibrarySupport, + target.constantsBackend, + component, + environmentDefines, + typeEnvironment, + SimpleErrorReporter(), + targetOS, + enableTripleShift: enableTripleShift, + enableConstFunctions: enableConstFunctions, + errorOnUnevaluatedConstant: errorOnUnevaluatedConstant, + evaluationMode: EvaluationMode.fromNnbdMode(nnbdMode)); + } + + /// Used for methods and fields with initializers where the method body or + /// field initializer must evaluate to a constant value when a target + /// operating system is provided. + static const _constPragmaName = "vm:platform-const"; + + bool _hasAnnotation(Annotatable node, String name) { + for (final annotation in node.annotations) { + if (annotation is ConstantExpression) { + final constant = annotation.constant; + if (constant is InstanceConstant && + constant.classNode == _pragmaClass && + constant.fieldValues[_pragmaName.fieldReference] == + StringConstant(name)) { + return true; + } + } + } + return false; + } + + bool _isPlatformConst(Member node) => _hasAnnotation(node, _constPragmaName); + + bool transformerShouldEvaluateExpression(Expression node) => + _targetOS != null && node is StaticGet && _isPlatformConst(node.target); + + @override + Constant visitStaticGet(StaticGet node) { + assert(_targetOS != null); + final target = node.target; + + // This visitor can be called recursively while evaluating an abstraction + // over the Platform getters and fields, so check that the visited node has + // an appropriately annotated target. + if (!_isPlatformConst(target)) return super.visitStaticGet(node); + + return withNewEnvironment(() { + final nameText = target.name.text; + visitedLibraries.add(target.enclosingLibrary); + + // First, check for the fields in Platform whose initializers should not + // be evaluated, but instead uses of the field should just be replaced + // directly with an already calculated constant. + if (target is Field && target.enclosingClass == _platformClass) { + final constant = _constantFields[nameText]; + if (constant != null) { + return canonicalize(constant); + } + } + + late Constant result; + if (target is Field && target.initializer != null) { + result = evaluateExpressionInContext(target, target.initializer!); + } else if (target is Procedure && target.isGetter) { + final body = target.function.body!; + // If const functions are enabled, execute the getter as if it were + // a const function. Otherwise the annotated getter must be a single + // return statement whose expression is evaluated. + if (enableConstFunctions) { + result = executeBody(body); + } else if (body is ReturnStatement) { + if (body.expression == null) { + return canonicalize(NullConstant()); + } + result = evaluateExpressionInContext(target, body.expression!); + } else { + throw "Cannot evaluate method '$nameText' since it contains more " + "than a single return statement."; + } + } + if (result is AbortConstant) { + throw "The body or initialization of member '$nameText' does not " + "evaluate to a constant value for the specified target operating " + "system '$_targetOS'."; + } + return result; + }); + } +} diff --git a/pkg/vm/pubspec.yaml b/pkg/vm/pubspec.yaml index b83612d0836..b9768b58187 100644 --- a/pkg/vm/pubspec.yaml +++ b/pkg/vm/pubspec.yaml @@ -4,7 +4,7 @@ description: VM specific Dart code and helper scripts publish_to: none environment: - sdk: '>=2.15.0 <3.0.0' + sdk: '>=2.17.0 <3.0.0' # Use 'any' constraints here; we get our versions from the DEPS file. dependencies: diff --git a/pkg/vm/test/transformations/unreachable_code_elimination_test.dart b/pkg/vm/test/transformations/unreachable_code_elimination_test.dart index 269ac66fed1..11f0ff3beac 100644 --- a/pkg/vm/test/transformations/unreachable_code_elimination_test.dart +++ b/pkg/vm/test/transformations/unreachable_code_elimination_test.dart @@ -4,6 +4,7 @@ import 'dart:io'; +import 'package:front_end/src/base/nnbd_mode.dart'; import 'package:kernel/target/targets.dart'; import 'package:kernel/ast.dart'; import 'package:kernel/kernel.dart'; @@ -12,6 +13,7 @@ import 'package:test/test.dart'; import 'package:vm/target/vm.dart' show VmTarget; import 'package:vm/transformations/unreachable_code_elimination.dart' show transformComponent; +import 'package:vm/transformations/vm_constant_evaluator.dart'; import '../common_test_utils.dart'; @@ -26,7 +28,11 @@ runTestCase(Uri source) async { 'test.define.isFalse': 'false' }); - component = transformComponent(component, /* enableAsserts = */ false); + // Do not perform constant evaluation for a specific target operating system. + final evaluator = VMConstantEvaluator.create( + target, component, /* targetOS = */ null, NnbdMode.Strong); + component = + transformComponent(component, /* enableAsserts = */ false, evaluator); verifyComponent(component); final actual = kernelLibraryToString(component.mainMethod!.enclosingLibrary); diff --git a/pkg/vm/test/transformations/vm_constant_evaluator_test.dart b/pkg/vm/test/transformations/vm_constant_evaluator_test.dart new file mode 100644 index 00000000000..1ae70031c34 --- /dev/null +++ b/pkg/vm/test/transformations/vm_constant_evaluator_test.dart @@ -0,0 +1,59 @@ +// 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. + +import 'dart:io'; + +import 'package:front_end/src/base/nnbd_mode.dart'; +import 'package:kernel/target/targets.dart'; +import 'package:kernel/ast.dart'; +import 'package:kernel/kernel.dart'; +import 'package:kernel/verifier.dart'; +import 'package:path/path.dart' as path; +import 'package:test/test.dart'; +import 'package:vm/target_os.dart'; +import 'package:vm/target/vm.dart' show VmTarget; +import 'package:vm/transformations/unreachable_code_elimination.dart' + show transformComponent; +import 'package:vm/transformations/vm_constant_evaluator.dart'; + +import '../common_test_utils.dart'; + +final String pkgVmDir = Platform.script.resolve('../..').toFilePath(); + +runTestCase(Uri source, TargetOS os) async { + final target = new VmTarget(new TargetFlags()); + Component component = await compileTestCaseToKernelProgram(source, + target: target, + environmentDefines: { + 'test.define.isTrue': 'true', + 'test.define.isFalse': 'false' + }); + + final evaluator = + VMConstantEvaluator.create(target, component, os, NnbdMode.Strong); + final enableAsserts = false; + component = transformComponent(component, enableAsserts, evaluator); + verifyComponent(component); + + final actual = kernelLibraryToString(component.mainMethod!.enclosingLibrary); + final postfix = '.${os.name}'; + compareResultWithExpectationsFile(source, actual, expectFilePostfix: postfix); +} + +main() { + group('platform-use-transformation', () { + final testCasesPath = path.join( + pkgVmDir, 'testcases', 'transformations', 'vm_constant_evaluator'); + + for (var entry in Directory(testCasesPath) + .listSync(recursive: true, followLinks: false) + .reversed) { + if (entry.path.endsWith(".dart")) { + for (final os in TargetOS.values) { + test('${entry.path}.${os.name}', () => runTestCase(entry.uri, os)); + } + } + } + }); +} diff --git a/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart b/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart new file mode 100644 index 00000000000..8ec39499375 --- /dev/null +++ b/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart @@ -0,0 +1,82 @@ +// 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. + +import 'dart:io'; + +void testAndroid(int i) { + final b = Platform.isAndroid; + print(b); + if (Platform.isAndroid) { + final os = Platform.operatingSystem; + print(os); + final sep = Platform.pathSeparator; + print(sep); + } +} + +void testFuchsia(int i) { + final b = Platform.isFuchsia; + print(b); + if (Platform.isFuchsia) { + final os = Platform.operatingSystem; + print(os); + final sep = Platform.pathSeparator; + print(sep); + } +} + +void testIOS(int i) { + final b = Platform.isIOS; + print(b); + if (Platform.isIOS) { + final os = Platform.operatingSystem; + print(os); + final sep = Platform.pathSeparator; + print(sep); + } +} + +void testLinux(int i) { + final b = Platform.isLinux; + print(b); + if (Platform.isLinux) { + final os = Platform.operatingSystem; + print(os); + final sep = Platform.pathSeparator; + print(sep); + } +} + +void testMacOS(int i) { + final b = Platform.isMacOS; + print(b); + if (Platform.isMacOS) { + final os = Platform.operatingSystem; + print(os); + final sep = Platform.pathSeparator; + print(sep); + } +} + +void testWindows(int i) { + final b = Platform.isWindows; + print(b); + if (Platform.isWindows) { + final os = Platform.operatingSystem; + print(os); + final sep = Platform.pathSeparator; + print(sep); + } +} + +main(List args) { + if (args.isEmpty) return; + final i = int.parse(args[0]); + testAndroid(i); + testFuchsia(i); + testIOS(i); + testLinux(i); + testMacOS(i); + testWindows(i); +} diff --git a/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart.android.expect b/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart.android.expect new file mode 100644 index 00000000000..b0c9d28cfad --- /dev/null +++ b/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart.android.expect @@ -0,0 +1,53 @@ +library #lib /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +import "dart:io"; + +static method testAndroid(core::int i) → void { + final core::bool b = #C1; + core::print(b); + { + final core::String os = #C2; + core::print(os); + final core::String sep = #C3; + core::print(sep); + } +} +static method testFuchsia(core::int i) → void { + final core::bool b = #C4; + core::print(b); +} +static method testIOS(core::int i) → void { + final core::bool b = #C4; + core::print(b); +} +static method testLinux(core::int i) → void { + final core::bool b = #C4; + core::print(b); +} +static method testMacOS(core::int i) → void { + final core::bool b = #C4; + core::print(b); +} +static method testWindows(core::int i) → void { + final core::bool b = #C4; + core::print(b); +} +static method main(core::List args) → dynamic { + if(args.{core::Iterable::isEmpty}{core::bool}) + return; + final core::int i = core::int::parse(args.{core::List::[]}(0){(core::int) → core::String}); + self::testAndroid(i); + self::testFuchsia(i); + self::testIOS(i); + self::testLinux(i); + self::testMacOS(i); + self::testWindows(i); +} +constants { + #C1 = true + #C2 = "android" + #C3 = "/" + #C4 = false +} diff --git a/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart.fuchsia.expect b/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart.fuchsia.expect new file mode 100644 index 00000000000..7fd1a7e53f6 --- /dev/null +++ b/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart.fuchsia.expect @@ -0,0 +1,53 @@ +library #lib /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +import "dart:io"; + +static method testAndroid(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testFuchsia(core::int i) → void { + final core::bool b = #C2; + core::print(b); + { + final core::String os = #C3; + core::print(os); + final core::String sep = #C4; + core::print(sep); + } +} +static method testIOS(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testLinux(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testMacOS(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testWindows(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method main(core::List args) → dynamic { + if(args.{core::Iterable::isEmpty}{core::bool}) + return; + final core::int i = core::int::parse(args.{core::List::[]}(0){(core::int) → core::String}); + self::testAndroid(i); + self::testFuchsia(i); + self::testIOS(i); + self::testLinux(i); + self::testMacOS(i); + self::testWindows(i); +} +constants { + #C1 = false + #C2 = true + #C3 = "fuchsia" + #C4 = "/" +} diff --git a/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart.ios.expect b/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart.ios.expect new file mode 100644 index 00000000000..1cecbd9eaab --- /dev/null +++ b/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart.ios.expect @@ -0,0 +1,53 @@ +library #lib /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +import "dart:io"; + +static method testAndroid(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testFuchsia(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testIOS(core::int i) → void { + final core::bool b = #C2; + core::print(b); + { + final core::String os = #C3; + core::print(os); + final core::String sep = #C4; + core::print(sep); + } +} +static method testLinux(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testMacOS(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testWindows(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method main(core::List args) → dynamic { + if(args.{core::Iterable::isEmpty}{core::bool}) + return; + final core::int i = core::int::parse(args.{core::List::[]}(0){(core::int) → core::String}); + self::testAndroid(i); + self::testFuchsia(i); + self::testIOS(i); + self::testLinux(i); + self::testMacOS(i); + self::testWindows(i); +} +constants { + #C1 = false + #C2 = true + #C3 = "ios" + #C4 = "/" +} diff --git a/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart.linux.expect b/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart.linux.expect new file mode 100644 index 00000000000..585cfe2e688 --- /dev/null +++ b/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart.linux.expect @@ -0,0 +1,53 @@ +library #lib /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +import "dart:io"; + +static method testAndroid(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testFuchsia(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testIOS(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testLinux(core::int i) → void { + final core::bool b = #C2; + core::print(b); + { + final core::String os = #C3; + core::print(os); + final core::String sep = #C4; + core::print(sep); + } +} +static method testMacOS(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testWindows(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method main(core::List args) → dynamic { + if(args.{core::Iterable::isEmpty}{core::bool}) + return; + final core::int i = core::int::parse(args.{core::List::[]}(0){(core::int) → core::String}); + self::testAndroid(i); + self::testFuchsia(i); + self::testIOS(i); + self::testLinux(i); + self::testMacOS(i); + self::testWindows(i); +} +constants { + #C1 = false + #C2 = true + #C3 = "linux" + #C4 = "/" +} diff --git a/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart.macos.expect b/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart.macos.expect new file mode 100644 index 00000000000..d8b6c1ae92a --- /dev/null +++ b/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart.macos.expect @@ -0,0 +1,53 @@ +library #lib /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +import "dart:io"; + +static method testAndroid(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testFuchsia(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testIOS(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testLinux(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testMacOS(core::int i) → void { + final core::bool b = #C2; + core::print(b); + { + final core::String os = #C3; + core::print(os); + final core::String sep = #C4; + core::print(sep); + } +} +static method testWindows(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method main(core::List args) → dynamic { + if(args.{core::Iterable::isEmpty}{core::bool}) + return; + final core::int i = core::int::parse(args.{core::List::[]}(0){(core::int) → core::String}); + self::testAndroid(i); + self::testFuchsia(i); + self::testIOS(i); + self::testLinux(i); + self::testMacOS(i); + self::testWindows(i); +} +constants { + #C1 = false + #C2 = true + #C3 = "macos" + #C4 = "/" +} diff --git a/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart.windows.expect b/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart.windows.expect new file mode 100644 index 00000000000..e2dfdfd25ff --- /dev/null +++ b/pkg/vm/testcases/transformations/vm_constant_evaluator/platform_testcases.dart.windows.expect @@ -0,0 +1,53 @@ +library #lib /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +import "dart:io"; + +static method testAndroid(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testFuchsia(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testIOS(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testLinux(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testMacOS(core::int i) → void { + final core::bool b = #C1; + core::print(b); +} +static method testWindows(core::int i) → void { + final core::bool b = #C2; + core::print(b); + { + final core::String os = #C3; + core::print(os); + final core::String sep = #C4; + core::print(sep); + } +} +static method main(core::List args) → dynamic { + if(args.{core::Iterable::isEmpty}{core::bool}) + return; + final core::int i = core::int::parse(args.{core::List::[]}(0){(core::int) → core::String}); + self::testAndroid(i); + self::testFuchsia(i); + self::testIOS(i); + self::testLinux(i); + self::testMacOS(i); + self::testWindows(i); +} +constants { + #C1 = false + #C2 = true + #C3 = "windows" + #C4 = "\\" +} diff --git a/runtime/docs/pragmas.md b/runtime/docs/pragmas.md index b75455063eb..84aa4661446 100644 --- a/runtime/docs/pragmas.md +++ b/runtime/docs/pragmas.md @@ -12,7 +12,8 @@ These pragmas are part of the VM's API and are safe for use in external code. | `vm:notify-debugger-on-exception` | Marks a function that catches exceptions, making the VM treat any caught exception as if they were uncaught. This can be used to notify an attached debugger during debugging, without pausing the app during regular execution. | | `vm:external-name` | Allows to specify an external (native) name for an `external` function. This name is used to lookup native implementation via native resolver associated with the current library through embedding APIs. This is a replacement for legacy VM specific `native "name"` syntax. | | `vm:invisible` | Allows to mark a function as invisible so it will not appear on stack traces. | -| `vm:always-consider-inlining` | Marks a function which particularly benefits from inlining and specialization in context of the caller (for example, when concrete types of arguments are known). Inliner will not give up after one failed inlining attempt and will continue trying to inline this function. +| `vm:always-consider-inlining` | Marks a function which particularly benefits from inlining and specialization in context of the caller (for example, when concrete types of arguments are known). Inliner will not give up after one failed inlining attempt and will continue trying to inline this function. | +| `vm:platform-const` | Marks a static getter or a static field with an initializer where the getter body or field initializer evaluates to a constant value if the target operating system is known. | ## Unsafe pragmas for general use diff --git a/sdk/lib/io/platform.dart b/sdk/lib/io/platform.dart index d281ac59515..fa864573a59 100644 --- a/sdk/lib/io/platform.dart +++ b/sdk/lib/io/platform.dart @@ -60,7 +60,9 @@ part of dart.io; /// ``` class Platform { static final _numberOfProcessors = _Platform.numberOfProcessors; + @pragma("vm:platform-const") static final _pathSeparator = _Platform.pathSeparator; + @pragma("vm:platform-const") static final _operatingSystem = _Platform.operatingSystem; static final _operatingSystemVersion = _Platform.operatingSystemVersion; static final _localHostname = _Platform.localHostname; @@ -71,6 +73,7 @@ class Platform { /// The path separator used by the operating system to separate /// components in file paths. + @pragma("vm:platform-const") static String get pathSeparator => _pathSeparator; /// Get the name of the current locale. @@ -106,6 +109,7 @@ class Platform { /// /// Note that this list may change over time so platform-specific logic /// should be guarded by the appropriate boolean getter e.g. [isMacOS]. + @pragma("vm:platform-const") static String get operatingSystem => _operatingSystem; /// A string representing the version of the operating system or platform. @@ -134,26 +138,32 @@ class Platform { /// This value is `false` if the operating system is a specialized /// version of Linux that identifies itself by a different name, /// for example Android (see [isAndroid]). + @pragma("vm:platform-const") static final bool isLinux = (_operatingSystem == "linux"); /// Whether the operating system is a version of /// [macOS](https://en.wikipedia.org/wiki/MacOS). + @pragma("vm:platform-const") static final bool isMacOS = (_operatingSystem == "macos"); /// Whether the operating system is a version of /// [Microsoft Windows](https://en.wikipedia.org/wiki/Microsoft_Windows). + @pragma("vm:platform-const") static final bool isWindows = (_operatingSystem == "windows"); /// Whether the operating system is a version of /// [Android](https://en.wikipedia.org/wiki/Android_%28operating_system%29). + @pragma("vm:platform-const") static final bool isAndroid = (_operatingSystem == "android"); /// Whether the operating system is a version of /// [iOS](https://en.wikipedia.org/wiki/IOS). + @pragma("vm:platform-const") static final bool isIOS = (_operatingSystem == "ios"); /// Whether the operating system is a version of /// [Fuchsia](https://en.wikipedia.org/wiki/Google_Fuchsia). + @pragma("vm:platform-const") static final bool isFuchsia = (_operatingSystem == "fuchsia"); /// The environment for this process as a map from string key to string value. diff --git a/sdk/lib/io/platform_impl.dart b/sdk/lib/io/platform_impl.dart index 0fca80f8f2f..52bbc47373a 100644 --- a/sdk/lib/io/platform_impl.dart +++ b/sdk/lib/io/platform_impl.dart @@ -82,8 +82,7 @@ class _Platform { if (_environmentCache == null) { var env = _environment(); if (env is Iterable) { - var isWindows = operatingSystem == 'windows'; - var result = isWindows + var result = Platform.isWindows ? new _CaseInsensitiveStringMap() : new Map(); for (var environmentEntry in env) {