Add 'external-effect' pragma support to all the backends.

Call sites targeting a procedure annotated with `external-effect` will
not produce any code, including the argument which will not be
evaluated.

However, the single parameter will be treated as 'live' for the purposes
of any global analysis the backends do. This is useful for things like
protobuf shaking where a user may want to retain certain protobuf
messages without actually emitting the code that retains those messages.

Today this functionality is available internally in the vm and wasm SDK
libraries. dart2js has similar functionality represented via the
opaqueTrue and opaqueFalse booleans (which will cause conditional
branches to get shaken after analysis). This will replace dart2js's
opaque(True/False).

This also adds validation to the frontend to ensure a method annotated
with 'external-effect' is well-formed.

Change-Id: If1c4096673e655c58fe7638840a16125003e7809
Tested: Backend tests for codegen were added. A frontend test was added for the validation. A language test was added to confirm the behavior.
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/476020
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
This commit is contained in:
Nate Biggs
2026-04-27 09:42:38 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent d29b88d152
commit 3d2d6492c1
53 changed files with 1263 additions and 73 deletions
+8 -10
View File
@@ -11,6 +11,8 @@ import 'package:_js_interop_checks/src/js_interop.dart'
show getDartJSInteropJSName, hasDartJSInteropAnnotation;
import 'package:_js_interop_checks/src/transformations/js_util_optimizer.dart'
show ExtensionIndex;
import 'package:front_end/src/api_prototype/external_effect.dart'
show ExternalEffect;
import 'package:front_end/src/api_unstable/ddc.dart';
import 'package:js_shared/synced/embedded_names.dart' show JsGetName, JsBuiltin;
import 'package:kernel/class_hierarchy.dart';
@@ -3644,8 +3646,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
_currentUri = savedUri;
_staticTypeContext.leaveMember(p);
if (_options.dynamicModule &&
p.annotations.any((a) => _isEntrypointPragma(a, _coreTypes))) {
if (_options.dynamicModule && _isDynamicModuleEntryPoint(p, _coreTypes)) {
if (_dynamicEntrypoint == null) {
if (p.function.requiredParameterCount > 0) {
// TODO(sigmund): this error should be caught by a kernel checker that
@@ -6684,6 +6685,9 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
@override
js_ast.Expression visitStaticInvocation(StaticInvocation node) {
var target = node.target;
if (ExternalEffect.isExternalEffect(node)) {
return js_ast.LiteralNull();
}
if (isInlineJS(target)) return _emitInlineJSCode(node) as js_ast.Expression;
if (target.isFactory) return _emitFactoryInvocation(node);
@@ -9275,12 +9279,6 @@ class _SwitchLabelState {
///
/// Used to denote the entrypoint method of a dynamic module.
// TODO(sigmund): move to package:kernel.
bool _isEntrypointPragma(Expression expression, CoreTypes coreTypes) {
if (expression is! ConstantExpression) return false;
final value = expression.constant;
if (value is! InstanceConstant) return false;
if (value.classReference != coreTypes.pragmaClass.reference) return false;
final name = value.fieldValues[coreTypes.pragmaName.fieldReference];
if (name is! StringConstant) return false;
return name.value == 'dyn-module:entry-point';
bool _isDynamicModuleEntryPoint(Procedure p, CoreTypes coreTypes) {
return hasPragma(p, 'dyn-module:entry-point', coreTypes);
}
@@ -11,6 +11,8 @@ import 'package:_js_interop_checks/src/js_interop.dart'
show getDartJSInteropJSName, hasDartJSInteropAnnotation;
import 'package:_js_interop_checks/src/transformations/js_util_optimizer.dart'
show ExtensionIndex;
import 'package:front_end/src/api_prototype/external_effect.dart'
show ExternalEffect;
import 'package:front_end/src/api_unstable/ddc.dart';
import 'package:js_shared/synced/embedded_names.dart' show JsGetName, JsBuiltin;
import 'package:kernel/class_hierarchy.dart';
@@ -4249,8 +4251,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
_currentUri = savedUri;
_staticTypeContext.leaveMember(p);
if (_options.dynamicModule &&
p.annotations.any((a) => _isEntrypointPragma(a, _coreTypes))) {
if (_options.dynamicModule && _isDynamicModuleEntryPoint(p, _coreTypes)) {
if (_dynamicEntrypoint == null) {
if (p.function.requiredParameterCount > 0) {
// TODO(sigmund): this error should be caught by a kernel checker that
@@ -7377,6 +7378,9 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
@override
js_ast.Expression visitStaticInvocation(StaticInvocation node) {
var target = node.target;
if (ExternalEffect.isExternalEffect(node)) {
return js_ast.LiteralNull();
}
if (isInlineJS(target)) return _emitInlineJSCode(node) as js_ast.Expression;
if (target.isFactory) return _emitFactoryInvocation(node);
@@ -10098,12 +10102,6 @@ class _SwitchLabelState {
/// `const pragma('dyn-module:entry-point')`.
///
/// Used to denote the entrypoint method of a dynamic module.
bool _isEntrypointPragma(Expression expression, CoreTypes coreTypes) {
if (expression is! ConstantExpression) return false;
final value = expression.constant;
if (value is! InstanceConstant) return false;
if (value.classReference != coreTypes.pragmaClass.reference) return false;
final name = value.fieldValues[coreTypes.pragmaName.fieldReference];
if (name is! StringConstant) return false;
return name.value == 'dyn-module:entry-point';
bool _isDynamicModuleEntryPoint(Procedure p, CoreTypes coreTypes) {
return hasPragma(p, 'dyn-module:entry-point', coreTypes);
}
@@ -243,6 +243,22 @@ bool isUnsupportedFactoryConstructor(Procedure node) {
return false;
}
bool hasPragma(Annotatable node, String name, CoreTypes coreTypes) {
for (var a in node.annotations) {
if (a is! ConstantExpression) continue;
final value = a.constant;
if (value is! InstanceConstant) continue;
if (value.classReference != coreTypes.pragmaClass.reference) {
continue;
}
final nameValue = value.fieldValues[coreTypes.pragmaName.fieldReference];
if (nameValue is StringConstant && nameValue.value == name) {
return true;
}
}
return false;
}
/// Gets the real supertype of [c] and the list of [mixins] in reverse
/// application order (mixins will appear before ones they override).
///