diff --git a/pkg/compiler/lib/src/kernel/transformations/global/js_get_flag_lowering.dart b/pkg/compiler/lib/src/kernel/transformations/global/js_get_flag_lowering.dart new file mode 100644 index 00000000000..a85842a37f7 --- /dev/null +++ b/pkg/compiler/lib/src/kernel/transformations/global/js_get_flag_lowering.dart @@ -0,0 +1,80 @@ +// 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/core_types.dart'; + +import '../../../options.dart'; + +/// An AST transformation which lowers invocations of `JS_GET_FLAG`. +/// +/// `JS_GET_FLAG` is defined in `dart:_foreign_helper` and is invoked in runtime +/// libraries as a way of accessing compiler options in runtime code. The return +/// value is a de facto constant, so the compiler replaces the invocation by its +/// result before codegen rather than performing an operation at runtime. +/// +/// The earlier this lowering is performed, the more optimizations are enabled. +/// Because `JS_GET_FLAG` invocations are typically used as conditions, SSA is +/// able to simplify boolean expressions and eliminate dead branches guarded by +/// `JS_GET_FLAG`. However, if we lower `JS_GET_FLAG` during SSA, the dead code +/// will still previously have been treated as live. Using a kernel +/// transformation early allows us to perform treeshaking of the now-dead +/// references as well. +/// +/// This transformation is a global transformation because it needs access to +/// [CompilerOptions], which is not available modularly. +class JsGetFlagLowering { + final CoreTypes _coreTypes; + final CompilerOptions _options; + + JsGetFlagLowering(this._coreTypes, this._options); + + TreeNode transformStaticInvocation(StaticInvocation node) { + if (node.target != _coreTypes.jsGetFlag) return node; + final argument = node.arguments.positional.single; + + String? flag; + if (argument is StringLiteral) { + flag = argument.value; + } else if (argument is ConstantExpression) { + final constant = argument.constant; + if (constant is StringConstant) { + flag = constant.value; + } + } + + if (flag == null) _unsupportedFlag(argument); + final flagValue = _getFlagValue(flag); + if (flagValue == null) _unsupportedFlag(flag); + return ConstantExpression(BoolConstant(flagValue)) + ..fileOffset = node.fileOffset; + } + + bool? _getFlagValue(String flagName) { + switch (flagName) { + case 'DEV_COMPILER': + return false; + case 'MINIFIED': + return _options.enableMinification; + case 'MUST_RETAIN_METADATA': + return false; + case 'USE_CONTENT_SECURITY_POLICY': + return _options.features.useContentSecurityPolicy.isEnabled; + case 'VARIANCE': + return _options.enableVariance; + case 'LEGACY': + return _options.useLegacySubtyping; + case 'EXTRA_NULL_SAFETY_CHECKS': + // TODO(fishythefish): Handle this flag as needed. + return false; + case 'PRINT_LEGACY_STARS': + return _options.printLegacyStars; + default: + return null; + } + } + + Never _unsupportedFlag(Object? flag) => + throw UnsupportedError('Unexpected JS_GET_FLAG argument: $flag'); +} diff --git a/pkg/compiler/lib/src/kernel/transformations/global/lowering.dart b/pkg/compiler/lib/src/kernel/transformations/global/lowering.dart index e2d3a68e815..7e4aa1b8062 100644 --- a/pkg/compiler/lib/src/kernel/transformations/global/lowering.dart +++ b/pkg/compiler/lib/src/kernel/transformations/global/lowering.dart @@ -3,19 +3,34 @@ // BSD-style license that can be found in the LICENSE file. import 'package:kernel/ast.dart'; +import 'package:kernel/core_types.dart'; +import '../../../options.dart'; import 'clone_mixin_methods_with_super.dart' as transformMixins; +import 'js_get_flag_lowering.dart'; -void transformLibraries(List libraries) { - final transformer = _Lowering(); +void transformLibraries( + List libraries, CoreTypes coreTypes, CompilerOptions options) { + final transformer = _Lowering(coreTypes, options); libraries.forEach(transformer.visitLibrary); } class _Lowering extends Transformer { + final JsGetFlagLowering _jsGetFlagLowering; + + _Lowering(CoreTypes coreTypes, CompilerOptions options) + : _jsGetFlagLowering = JsGetFlagLowering(coreTypes, options); + @override Class visitClass(Class node) { node.transformChildren(this); transformMixins.transformClass(node); return node; } + + @override + TreeNode visitStaticInvocation(StaticInvocation node) { + node.transformChildren(this); + return _jsGetFlagLowering.transformStaticInvocation(node); + } } diff --git a/pkg/compiler/lib/src/phase/load_kernel.dart b/pkg/compiler/lib/src/phase/load_kernel.dart index 87abcb5f80d..9d589539304 100644 --- a/pkg/compiler/lib/src/phase/load_kernel.dart +++ b/pkg/compiler/lib/src/phase/load_kernel.dart @@ -124,12 +124,13 @@ class _LoadFromKernelResult { // serialized components and components from source. void _doTransformsOnKernelLoad(Component component, CompilerOptions options) { if (options.stage.shouldRunGlobalTransforms) { - globalTransforms.transformLibraries(component.libraries); + ir.CoreTypes coreTypes = ir.CoreTypes(component); + globalTransforms.transformLibraries( + component.libraries, coreTypes, options); // referenceFromIndex is only necessary in the case where a module // containing a stub definition is invalidated, and then reloaded, because // we need to keep existing references to that stub valid. Here, we have the // whole program, and therefore do not need it. - ir.CoreTypes coreTypes = ir.CoreTypes(component); StaticInteropClassEraser(coreTypes, null, additionalCoreLibraries: {'_js_types', 'js_interop'}) .visitComponent(component); diff --git a/pkg/compiler/lib/src/ssa/builder.dart b/pkg/compiler/lib/src/ssa/builder.dart index 122978e456f..c2c2186a22a 100644 --- a/pkg/compiler/lib/src/ssa/builder.dart +++ b/pkg/compiler/lib/src/ssa/builder.dart @@ -376,39 +376,6 @@ class KernelSsaGraphBuilder extends ir.VisitorDefault open(newBlock); } - /// Helper to implement JS_GET_FLAG. - /// - /// The concrete SSA graph builder will extract a flag parameter from the - /// JS_GET_FLAG call and then push a boolean result onto the stack. This - /// function provides the boolean value corresponding to the given [flagName]. - /// If [flagName] is not recognized, this function returns `null` and the - /// concrete SSA builder reports an error. - bool? _getFlagValue(String flagName) { - switch (flagName) { - case 'FALSE': - return false; - case 'DEV_COMPILER': - return false; - case 'MINIFIED': - return options.enableMinification; - case 'MUST_RETAIN_METADATA': - return false; - case 'USE_CONTENT_SECURITY_POLICY': - return options.features.useContentSecurityPolicy.isEnabled; - case 'VARIANCE': - return options.enableVariance; - case 'LEGACY': - return options.useLegacySubtyping; - case 'EXTRA_NULL_SAFETY_CHECKS': - // TODO(fishythefish): Handle this flag as needed. - return false; - case 'PRINT_LEGACY_STARS': - return options.printLegacyStars; - default: - return null; - } - } - StaticType _getStaticType(ir.Expression node) { // TODO(johnniwinther): Substitute the type by the this type and type // arguments of the current frame. @@ -4474,8 +4441,8 @@ class KernelSsaGraphBuilder extends ir.VisitorDefault _handleForeignJsEmbeddedGlobal(invocation); } else if (name == 'JS_BUILTIN') { _handleForeignJsBuiltin(invocation); - } else if (name == 'JS_GET_FLAG') { - _handleForeignJsGetFlag(invocation); + } else if (name == 'JS_FALSE') { + _handleForeignJsFalse(invocation); } else if (name == 'JS_EFFECT') { stack.add(graph.addConstantNull(closedWorld)); } else if (name == 'JS_INTERCEPTOR_CONSTANT') { @@ -4987,24 +4954,9 @@ class KernelSsaGraphBuilder extends ir.VisitorDefault } } - void _handleForeignJsGetFlag(ir.StaticInvocation invocation) { - if (_unexpectedForeignArguments(invocation, - minPositional: 1, maxPositional: 1)) { - stack.add( - // Result expected on stack. - graph.addConstantBool(false, closedWorld)); - return; - } - String name = _foreignConstantStringArgument(invocation, 0, 'JS_GET_FLAG')!; - final value = _getFlagValue(name); - if (value == null) { - reporter.reportErrorMessage( - _elementMap.getSpannable(targetElement, invocation), - MessageKind.GENERIC, - {'text': 'Error: Unknown internal flag "$name".'}); - } else { - stack.add(graph.addConstantBool(value, closedWorld)); - } + void _handleForeignJsFalse(ir.StaticInvocation invocation) { + _unexpectedForeignArguments(invocation, minPositional: 0, maxPositional: 0); + stack.add(graph.addConstantBool(false, closedWorld)); } void _handleJsInterceptorConstant(ir.StaticInvocation invocation) { diff --git a/pkg/kernel/lib/core_types.dart b/pkg/kernel/lib/core_types.dart index b8aad69559a..cecfdca23e0 100644 --- a/pkg/kernel/lib/core_types.dart +++ b/pkg/kernel/lib/core_types.dart @@ -353,6 +353,9 @@ class CoreTypes { late final Field enumNameField = index.getField('dart:core', '_Enum', '_name'); + late final Procedure jsGetFlag = + index.getTopLevelProcedure('dart:_foreign_helper', 'JS_GET_FLAG'); + InterfaceType get objectLegacyRawType { return _objectLegacyRawType ??= _legacyRawTypes[objectClass] ??= new InterfaceType(objectClass, Nullability.legacy, const []); diff --git a/sdk/lib/_internal/js_runtime/lib/async_patch.dart b/sdk/lib/_internal/js_runtime/lib/async_patch.dart index f68c1c7ecaf..a412c7bf0ec 100644 --- a/sdk/lib/_internal/js_runtime/lib/async_patch.dart +++ b/sdk/lib/_internal/js_runtime/lib/async_patch.dart @@ -15,7 +15,7 @@ import 'dart:_js_helper' unwrapException; import 'dart:_foreign_helper' - show JS, JS_GET_FLAG, JS_RAW_EXCEPTION, RAW_DART_FUNCTION_REF; + show JS, JS_FALSE, JS_RAW_EXCEPTION, RAW_DART_FUNCTION_REF; import 'dart:_async_status_codes' as async_status_codes; @@ -597,7 +597,7 @@ class _SyncStarIterator implements Iterator { } bool moveNext() { - if (JS_GET_FLAG('FALSE')) _modelGeneratedCode(); + if (JS_FALSE()) _modelGeneratedCode(); Object? errorValue; int errorCode = async_status_codes.SUCCESS; while (true) { diff --git a/sdk/lib/_internal/js_runtime/lib/foreign_helper.dart b/sdk/lib/_internal/js_runtime/lib/foreign_helper.dart index 549e485bd4a..c0bc40c7bbf 100644 --- a/sdk/lib/_internal/js_runtime/lib/foreign_helper.dart +++ b/sdk/lib/_internal/js_runtime/lib/foreign_helper.dart @@ -232,6 +232,11 @@ external JS_BUILTIN(String typeDescription, JsBuiltin builtin, /// when the program has been analyzed. external bool JS_GET_FLAG(String name); +/// Always returns `false`. Because this method is opaque until SSA, it can be +/// used to guard code which should be treated as live but not emitted by +/// default. +external bool JS_FALSE(); + /// Returns the underlying JavaScript exception. Must be used in a catch block. /// /// try { diff --git a/sdk/lib/_internal/js_runtime/lib/interceptors.dart b/sdk/lib/_internal/js_runtime/lib/interceptors.dart index cd4d96c9a52..54db90fbfe7 100644 --- a/sdk/lib/_internal/js_runtime/lib/interceptors.dart +++ b/sdk/lib/_internal/js_runtime/lib/interceptors.dart @@ -8,7 +8,7 @@ import 'dart:_js_embedded_names' show DISPATCH_PROPERTY_NAME, TYPE_TO_INTERCEPTOR_MAP; import 'dart:collection' hide LinkedList, LinkedListEntry; -import 'dart:_foreign_helper' show JS_GET_FLAG, TYPE_REF; +import 'dart:_foreign_helper' show JS_FALSE, JS_GET_FLAG, TYPE_REF; import 'dart:_internal' hide Symbol; import "dart:_internal" as _symbol_dev show Symbol; import 'dart:_js_helper' diff --git a/sdk/lib/_internal/js_runtime/lib/js_number.dart b/sdk/lib/_internal/js_runtime/lib/js_number.dart index 27dc231e3e9..cdd8fec80f0 100644 --- a/sdk/lib/_internal/js_runtime/lib/js_number.dart +++ b/sdk/lib/_internal/js_runtime/lib/js_number.dart @@ -340,7 +340,7 @@ final class JSNumber extends Interceptor implements double { int operator ~/(num other) { if (other is! num) throw argumentErrorValue(other); - if (JS_GET_FLAG('FALSE')) _tdivFast(other); // Ensure resolution. + if (JS_FALSE()) _tdivFast(other); // Ensure resolution. if (_isInt32(this)) { if (other >= 1 || other < -1) { return JS('int', r'(# / #) | 0', this, other); @@ -400,7 +400,7 @@ final class JSNumber extends Interceptor implements double { num operator >>(num other) { if (other is! num) throw argumentErrorValue(other); if (other < 0) throw argumentErrorValue(other); - if (JS_GET_FLAG('FALSE')) _shrReceiverPositive(other); + if (JS_FALSE()) _shrReceiverPositive(other); return _shrOtherPositive(other); }