[dart2js] Lower JS_GET_FLAG in phase 0b kernel transformer
Also adds `JS_FALSE` to replace uses of `JS_GET_FLAG(false)`. See the
doc comment for details.
Change-Id: I33f89f158c1955a19fa299f22c50d51e36ede3d8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/330171
Commit-Queue: Mayank Patke <fishythefish@google.com>
Reviewed-by: Stephen Adams <sra@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
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 0000000..a85842a
--- /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 e2d3a68..7e4aa1b 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<Library> libraries) {
- final transformer = _Lowering();
+void transformLibraries(
+ List<Library> 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 87abcb5..9d58953 100644
--- a/pkg/compiler/lib/src/phase/load_kernel.dart
+++ b/pkg/compiler/lib/src/phase/load_kernel.dart
@@ -124,12 +124,13 @@
// 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 122978e..c2c2186 100644
--- a/pkg/compiler/lib/src/ssa/builder.dart
+++ b/pkg/compiler/lib/src/ssa/builder.dart
@@ -376,39 +376,6 @@
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 @@
_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 @@
}
}
- 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 b8aad69..cecfdca 100644
--- a/pkg/kernel/lib/core_types.dart
+++ b/pkg/kernel/lib/core_types.dart
@@ -353,6 +353,9 @@
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 <DartType>[]);
diff --git a/sdk/lib/_internal/js_runtime/lib/async_patch.dart b/sdk/lib/_internal/js_runtime/lib/async_patch.dart
index f68c1c7..a412c7b 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 @@
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 @@
}
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 549e485..c0bc40c 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 @@
/// 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 cd4d96c..54db90f 100644
--- a/sdk/lib/_internal/js_runtime/lib/interceptors.dart
+++ b/sdk/lib/_internal/js_runtime/lib/interceptors.dart
@@ -8,7 +8,7 @@
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 27dc231..cdd8fec 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 @@
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 @@
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);
}