// Copyright (c) 2016, 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. library kernel.target.targets; import '../ast.dart'; import '../class_hierarchy.dart'; import '../core_types.dart'; import '../reference_from_index.dart'; import 'changed_structure_notifier.dart'; final List targetNames = targets.keys.toList(); class TargetFlags { final bool trackWidgetCreation; final int forceLateLoweringsForTesting; final bool forceLateLoweringSentinelForTesting; final bool forceStaticFieldLoweringForTesting; final bool forceNoExplicitGetterCallsForTesting; final bool enableNullSafety; const TargetFlags( {this.trackWidgetCreation = false, this.forceLateLoweringsForTesting = LateLowering.none, this.forceLateLoweringSentinelForTesting = false, this.forceStaticFieldLoweringForTesting = false, this.forceNoExplicitGetterCallsForTesting = false, this.enableNullSafety = false}); bool operator ==(other) { if (identical(this, other)) return true; return other is TargetFlags && trackWidgetCreation == other.trackWidgetCreation && forceLateLoweringsForTesting == other.forceLateLoweringsForTesting && forceLateLoweringSentinelForTesting == other.forceLateLoweringSentinelForTesting && forceStaticFieldLoweringForTesting == other.forceStaticFieldLoweringForTesting && forceNoExplicitGetterCallsForTesting == other.forceNoExplicitGetterCallsForTesting && enableNullSafety == other.enableNullSafety; } int get hashCode { int hash = 485786; hash = 0x3fffffff & (hash * 31 + (hash ^ trackWidgetCreation.hashCode)); hash = 0x3fffffff & (hash * 31 + (hash ^ forceLateLoweringsForTesting.hashCode)); hash = 0x3fffffff & (hash * 31 + (hash ^ forceLateLoweringSentinelForTesting.hashCode)); hash = 0x3fffffff & (hash * 31 + (hash ^ forceStaticFieldLoweringForTesting.hashCode)); hash = 0x3fffffff & (hash * 31 + (hash ^ forceNoExplicitGetterCallsForTesting.hashCode)); hash = 0x3fffffff & (hash * 31 + (hash ^ enableNullSafety.hashCode)); return hash; } } typedef Target _TargetBuilder(TargetFlags flags); final Map targets = { 'none': (TargetFlags flags) => new NoneTarget(flags), }; Target? getTarget(String name, TargetFlags flags) { _TargetBuilder? builder = targets[name]; if (builder == null) return null; return builder(flags); } abstract class DiagnosticReporter { void report(M message, int charOffset, int length, Uri fileUri, {List context}); } /// The different kinds of number semantics supported by the constant evaluator. enum NumberSemantics { /// Dart VM number semantics. vm, /// JavaScript (Dart2js and DDC) number semantics. js, } // Backend specific constant evaluation behavior class ConstantsBackend { const ConstantsBackend(); /// Lowering of a list constant to a backend-specific representation. Constant lowerListConstant(ListConstant constant) => constant; /// Returns `true` if [constant] is lowered list constant created by /// [lowerListConstant]. bool isLoweredListConstant(Constant constant) => false; /// Calls `f` for each element in the lowered list [constant]. /// /// This assumes that `isLoweredListConstant(constant)` is true. void forEachLoweredListConstantElement( Constant constant, void Function(Constant element) f) {} /// Lowering of a set constant to a backend-specific representation. Constant lowerSetConstant(SetConstant constant) => constant; /// Returns `true` if [constant] is lowered set constant created by /// [lowerSetConstant]. bool isLoweredSetConstant(Constant constant) => false; /// Calls `f` for each element in the lowered set [constant]. /// /// This assumes that `isLoweredSetConstant(constant)` is true. void forEachLoweredSetConstantElement( Constant constant, void Function(Constant element) f) {} /// Lowering of a map constant to a backend-specific representation. Constant lowerMapConstant(MapConstant constant) => constant; /// Returns `true` if [constant] is lowered map constant created by /// [lowerMapConstant]. bool isLoweredMapConstant(Constant constant) => false; /// Calls `f` for each key/value pair in the lowered map [constant]. /// /// This assumes that `lowerMapConstant(constant)` is true. void forEachLoweredMapConstantEntry( Constant constant, void Function(Constant key, Constant value) f) {} /// Number semantics to use for this backend. NumberSemantics get numberSemantics => NumberSemantics.vm; /// Inline control of constant variables. The given constant expression /// is the initializer of a [Field] or [VariableDeclaration] node. /// If this method returns `true`, the variable will be inlined at all /// points of reference and the variable itself removed (unless overridden /// by the `keepFields` or `keepLocals` properties). /// This method must be deterministic, i.e. it must always return the same /// value for the same constant value and place in the AST. bool shouldInlineConstant(ConstantExpression initializer) => true; /// Whether this target supports unevaluated constants. /// /// If not, then trying to perform constant evaluation without an environment /// raises an exception. /// /// This defaults to `false` since it requires additional work for a backend /// to support unevaluated constants. bool get supportsUnevaluatedConstants => false; /// If `true` constant [Field] declarations are not removed from the AST even /// when use-sites are inlined. /// /// All use-sites will be rewritten based on [shouldInlineConstant]. bool get keepFields => true; /// If `true` constant [VariableDeclaration]s are not removed from the AST /// even when use-sites are inlined. /// /// All use-sites will be rewritten based on [shouldInlineConstant]. bool get keepLocals => false; } /// A target provides backend-specific options for generating kernel IR. abstract class Target { TargetFlags get flags; String get name; /// A list of URIs of required libraries, not including dart:core. /// /// Libraries will be loaded in order. List get extraRequiredLibraries => const []; /// A list of URIs of extra required libraries when compiling the platform. /// /// Libraries will be loaded in order after the [extraRequiredLibraries] /// above. /// /// Normally not needed, but can be useful if removing libraries from the /// [extraRequiredLibraries] list so libraries will still be available in the /// platform if having a weird mix of current and not-quite-current as can /// sometimes be the case. List get extraRequiredLibrariesPlatform => const []; /// A list of URIs of libraries to be indexed in the CoreTypes index, not /// including dart:_internal, dart:async, dart:core and dart:mirrors. List get extraIndexedLibraries => const []; /// Additional declared variables implied by this target. /// /// These can also be passed on the command-line of form `-D=`, /// and those provided on the command-line take precedence over those defined /// by the target. Map get extraDeclaredVariables => const {}; /// Classes from the SDK whose interface is required for the modular /// transformations. Map> get requiredSdkClasses => CoreTypes.requiredClasses; /// A derived class may change this to `true` to enable forwarders to /// user-defined `noSuchMethod` that are generated for each abstract member /// if such `noSuchMethod` is present. /// /// The forwarders are abstract [Procedure]s with [isNoSuchMethodForwarder] /// bit set. The implementation of the behavior of such forwarders is up /// for the target backend. bool get enableNoSuchMethodForwarders => false; /// A derived class may change this to `true` to enable Flutter specific /// "super-mixins" semantics. /// /// This semantics relaxes a number of constraint previously imposed on /// mixins. Importantly it imposes the following change: /// /// An abstract class may contain a member with a super-invocation that /// corresponds to a member of the superclass interface, but where the /// actual superclass does not declare or inherit a matching method. /// Since no amount of overriding can change this property, such a class /// cannot be extended to a class that is not abstract, it can only be /// used to derive a mixin from. /// /// See dartbug.com/31542 for details of the semantics. bool get enableSuperMixins => false; /// Perform target-specific transformations on the outlines stored in /// [Component] when generating summaries. /// /// This transformation is used to add metadata on outlines and to filter /// unnecessary information before generating program summaries. This /// transformation is not applied when compiling full kernel programs to /// prevent affecting the internal invariants of the compiler and accidentally /// slowing down compilation. void performOutlineTransformations(Component component) {} /// Perform target-specific transformations on the given libraries that must /// run before constant evaluation. void performPreConstantEvaluationTransformations( Component component, CoreTypes coreTypes, List libraries, DiagnosticReporter diagnosticReporter, {void Function(String msg)? logger, ChangedStructureNotifier? changedStructureNotifier}) {} /// Perform target-specific modular transformations on the given libraries. void performModularTransformationsOnLibraries( Component component, CoreTypes coreTypes, ClassHierarchy hierarchy, List libraries, // TODO(askesc): Consider how to generally pass compiler options to // transformations. Map environmentDefines, DiagnosticReporter diagnosticReporter, ReferenceFromIndex? referenceFromIndex, {void logger(String msg), ChangedStructureNotifier changedStructureNotifier}); /// Perform target-specific modular transformations on the given program. /// /// This is used when an individual expression is compiled, e.g. for debugging /// purposes. It is illegal to modify any of the enclosing nodes of the /// procedure. void performTransformationsOnProcedure( CoreTypes coreTypes, ClassHierarchy hierarchy, Procedure procedure, // TODO(askesc): Consider how to generally pass compiler options to // transformations. Map environmentDefines, {void Function(String msg)? logger}) {} /// Whether a platform library may define a restricted type, such as `bool`, /// `int`, `double`, `num`, and `String`. /// /// By default only `dart:core` may define restricted types, but some target /// implementations override this. bool mayDefineRestrictedType(Uri uri) => uri.scheme == 'dart' && uri.path == 'core'; /// Whether a library is allowed to import a platform private library. /// /// By default only `dart:*` libraries are allowed. May be overridden for /// testing purposes. bool allowPlatformPrivateLibraryAccess(Uri importer, Uri imported) => imported.scheme != "dart" || !imported.path.startsWith("_") || importer.scheme == "dart" || (importer.scheme == "package" && importer.path.startsWith("dart_internal/")); /// Whether the `native` language extension is supported within [library]. /// /// The `native` language extension is not part of the language specification, /// it means something else to each target, and it is enabled under different /// circumstances for each target implementation. For example, the VM target /// enables it everywhere because of existing support for "dart-ext:" native /// extensions, but targets like dart2js only enable it on the core libraries. bool enableNative(Uri uri) => false; /// There are two variants of the `native` language extension. The VM expects /// the native token to be followed by string, whereas dart2js and DDC do not. // TODO(sigmund, ahe): ideally we should remove the `native` syntax, if not, // we should at least unify the VM and non-VM variants. bool get nativeExtensionExpectsString => false; /// Whether integer literals that cannot be represented exactly on the web /// (i.e. in Javascript) should cause an error to be issued. /// An example of such a number is `2^53 + 1` where in Javascript - because /// integers are represented as doubles /// `Math.pow(2, 53) = Math.pow(2, 53) + 1`. bool get errorOnUnexactWebIntLiterals => false; /// Whether set literals are natively supported by this target. If set /// literals are not supported by the target, they will be desugared into /// explicit `Set` creation (for non-const set literals) or wrapped map /// literals (for const set literals). bool get supportsSetLiterals => true; /// Bit mask of [LateLowering] values for the late lowerings that should /// be performed by the CFE. /// /// For the selected lowerings, late fields and variables are encoded using /// fields, getter, setters etc. in a way that provide equivalent semantics. /// See `pkg/kernel/nnbd_api.md` for details. int get enabledLateLowerings; /// Returns `true` if the CFE should lower a late field given it /// [hasInitializer], [isFinal], and [isStatic]. /// /// This is determined by the [enabledLateLowerings] mask. bool isLateFieldLoweringEnabled( {required bool hasInitializer, required bool isFinal, required bool isStatic}) { // ignore: unnecessary_null_comparison assert(hasInitializer != null); // ignore: unnecessary_null_comparison assert(isFinal != null); // ignore: unnecessary_null_comparison assert(isStatic != null); int mask = LateLowering.getFieldLowering( hasInitializer: hasInitializer, isFinal: isFinal, isStatic: isStatic); return enabledLateLowerings & mask != 0; } /// Returns `true` if the CFE should lower a late local variable given it /// [hasInitializer], [isFinal], and its type [isPotentiallyNullable]. /// /// This is determined by the [enabledLateLowerings] mask. bool isLateLocalLoweringEnabled( {required bool hasInitializer, required bool isFinal, required bool isPotentiallyNullable}) { // ignore: unnecessary_null_comparison assert(hasInitializer != null); // ignore: unnecessary_null_comparison assert(isFinal != null); // ignore: unnecessary_null_comparison assert(isPotentiallyNullable != null); int mask = LateLowering.getLocalLowering( hasInitializer: hasInitializer, isFinal: isFinal, isPotentiallyNullable: isPotentiallyNullable); return enabledLateLowerings & mask != 0; } /// If `true`, the backend supports creation and checking of a sentinel value /// for uninitialized late fields and variables through the `createSentinel` /// and `isSentinel` methods in `dart:_internal`. /// /// If `true` this is used when [supportsLateFields] is `false`. bool get supportsLateLoweringSentinel; /// Whether static fields with initializers in nnbd libraries should be /// encoded using the late field lowering. bool get useStaticFieldLowering; /// Whether calls to getters and fields should be encoded as a .call /// invocation on a property get. /// /// If `false`, calls to getters and fields are encoded as method invocations /// with the accessed getter or field as the interface target. bool get supportsExplicitGetterCalls; bool get supportsNewMethodInvocationEncoding; /// Builds an expression that instantiates an [Invocation] that can be passed /// to [noSuchMethod]. Expression instantiateInvocation(CoreTypes coreTypes, Expression receiver, String name, Arguments arguments, int offset, bool isSuper); Expression instantiateNoSuchMethodError(CoreTypes coreTypes, Expression receiver, String name, Arguments arguments, int offset, {bool isMethod: false, bool isGetter: false, bool isSetter: false, bool isField: false, bool isLocalVariable: false, bool isDynamic: false, bool isSuper: false, bool isStatic: false, bool isConstructor: false, bool isTopLevel: false}); /// Configure the given [Component] in a target specific way. /// Returns the configured component. Component configureComponent(Component component) => component; // Configure environment defines in a target-specific way. Map updateEnvironmentDefines(Map map) => map; String toString() => 'Target($name)'; Class? concreteListLiteralClass(CoreTypes coreTypes) => null; Class? concreteConstListLiteralClass(CoreTypes coreTypes) => null; Class? concreteMapLiteralClass(CoreTypes coreTypes) => null; Class? concreteConstMapLiteralClass(CoreTypes coreTypes) => null; Class? concreteIntLiteralClass(CoreTypes coreTypes, int value) => null; Class? concreteDoubleLiteralClass(CoreTypes coreTypes, double value) => null; Class? concreteStringLiteralClass(CoreTypes coreTypes, String value) => null; ConstantsBackend constantsBackend(CoreTypes coreTypes); } class NoneConstantsBackend extends ConstantsBackend { @override final bool supportsUnevaluatedConstants; const NoneConstantsBackend({required this.supportsUnevaluatedConstants}); } class NoneTarget extends Target { final TargetFlags flags; NoneTarget(this.flags); @override int get enabledLateLowerings => flags.forceLateLoweringsForTesting; @override bool get supportsLateLoweringSentinel => flags.forceLateLoweringSentinelForTesting; @override bool get useStaticFieldLowering => flags.forceStaticFieldLoweringForTesting; @override bool get supportsExplicitGetterCalls => !flags.forceNoExplicitGetterCallsForTesting; @override bool get supportsNewMethodInvocationEncoding => true; @override String get name => 'none'; @override List get extraRequiredLibraries => []; @override void performModularTransformationsOnLibraries( Component component, CoreTypes coreTypes, ClassHierarchy hierarchy, List libraries, Map environmentDefines, DiagnosticReporter diagnosticReporter, ReferenceFromIndex? referenceFromIndex, {void Function(String msg)? logger, ChangedStructureNotifier? changedStructureNotifier}) {} @override Expression instantiateInvocation(CoreTypes coreTypes, Expression receiver, String name, Arguments arguments, int offset, bool isSuper) { return new InvalidExpression(null); } @override Expression instantiateNoSuchMethodError(CoreTypes coreTypes, Expression receiver, String name, Arguments arguments, int offset, {bool isMethod: false, bool isGetter: false, bool isSetter: false, bool isField: false, bool isLocalVariable: false, bool isDynamic: false, bool isSuper: false, bool isStatic: false, bool isConstructor: false, bool isTopLevel: false}) { return new InvalidExpression(null); } @override ConstantsBackend constantsBackend(CoreTypes coreTypes) => // TODO(johnniwinther): Should this vary with the use case? const NoneConstantsBackend(supportsUnevaluatedConstants: true); } class LateLowering { static const int nullableUninitializedNonFinalLocal = 1 << 0; static const int nonNullableUninitializedNonFinalLocal = 1 << 1; static const int nullableUninitializedFinalLocal = 1 << 2; static const int nonNullableUninitializedFinalLocal = 1 << 3; static const int nullableInitializedNonFinalLocal = 1 << 4; static const int nonNullableInitializedNonFinalLocal = 1 << 5; static const int nullableInitializedFinalLocal = 1 << 6; static const int nonNullableInitializedFinalLocal = 1 << 7; static const int uninitializedNonFinalStaticField = 1 << 8; static const int uninitializedFinalStaticField = 1 << 9; static const int initializedNonFinalStaticField = 1 << 10; static const int initializedFinalStaticField = 1 << 11; static const int uninitializedNonFinalInstanceField = 1 << 12; static const int uninitializedFinalInstanceField = 1 << 13; static const int initializedNonFinalInstanceField = 1 << 14; static const int initializedFinalInstanceField = 1 << 15; static const int none = 0; static const int all = (1 << 16) - 1; static int getLocalLowering( {required bool hasInitializer, required bool isFinal, required bool isPotentiallyNullable}) { // ignore: unnecessary_null_comparison assert(hasInitializer != null); // ignore: unnecessary_null_comparison assert(isFinal != null); // ignore: unnecessary_null_comparison assert(isPotentiallyNullable != null); if (hasInitializer) { if (isFinal) { if (isPotentiallyNullable) { return nullableInitializedFinalLocal; } else { return nonNullableInitializedFinalLocal; } } else { if (isPotentiallyNullable) { return nullableInitializedNonFinalLocal; } else { return nonNullableInitializedNonFinalLocal; } } } else { if (isFinal) { if (isPotentiallyNullable) { return nullableUninitializedFinalLocal; } else { return nonNullableUninitializedFinalLocal; } } else { if (isPotentiallyNullable) { return nullableUninitializedNonFinalLocal; } else { return nonNullableUninitializedNonFinalLocal; } } } } static int getFieldLowering( {required bool hasInitializer, required bool isFinal, required bool isStatic}) { // ignore: unnecessary_null_comparison assert(hasInitializer != null); // ignore: unnecessary_null_comparison assert(isFinal != null); // ignore: unnecessary_null_comparison assert(isStatic != null); if (hasInitializer) { if (isFinal) { if (isStatic) { return initializedFinalStaticField; } else { return initializedFinalInstanceField; } } else { if (isStatic) { return initializedNonFinalStaticField; } else { return initializedNonFinalInstanceField; } } } else { if (isFinal) { if (isStatic) { return uninitializedFinalStaticField; } else { return uninitializedFinalInstanceField; } } else { if (isStatic) { return uninitializedNonFinalStaticField; } else { return uninitializedNonFinalInstanceField; } } } } }