Files
Sigmund Cherem 14517e3735 [dyn_modules] dynamically-callable pragmas in annotator and TFA.
This change introduces the `dyn-module:dynamically-callable` and
`dyn-module:implicitly-dynamically-callable` entrypoint pragmas. It
changes the annotator to attach these pragmas to kernel nodes based
on the dynamic-interface and updates TFA to treat these nodes as
reachable and not elegible for tree-shaking.

On a later change, the pragma will be read by the VM to verify that
dynamic modules can only call dynamically members that were exposed
as dynamically callable in the dynamic interface..

TEST=pkg/vm/test/

Bug: b/448095881
Change-Id: I4a8dbe0614e23d8b921e83323eacf1a1b1171ab9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/495660
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
2026-04-16 09:49:11 -07:00

283 lines
9.9 KiB
Dart

// Copyright (c) 2018, 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' show CoreTypes;
import 'package:kernel/target/targets.dart' show Target;
// Pragmas recognized by the VM
const kVmEntryPointPragmaName = "vm:entry-point";
const kVmExactResultTypePragmaName = "vm:exact-result-type";
const kResultTypeUsesPassedTypeArguments =
"result-type-uses-passed-type-arguments";
const kVmRecognizedPragmaName = "vm:recognized";
const kVmDisableUnboxedParametersPragmaName = "vm:disable-unboxed-parameters";
const kVmKeepNamePragmaName = "vm:keep-name";
const kVmPlatformConstPragmaName = "vm:platform-const";
const kVmPlatformConstIfPragmaName = "vm:platform-const-if";
const kVmFfiNative = "vm:ffi:native";
const kVmSharedPragmaName = "vm:shared";
const kVmDeeplyImmutablePragmaName = "vm:deeply-immutable";
const kVmInvisiblePragmaName = "vm:invisible";
// Pragmas recognized by dart2wasm
const kWasmEntryPointPragmaName = "wasm:entry-point";
const kWasmExportPragmaName = "wasm:export";
// Dynamic modules pragmas, recognized both by the VM and dart2wasm
const kDynModuleExtendablePragmaName = "dyn-module:extendable";
const kDynModuleImplicitlyExtendablePragmaName =
"dyn-module:implicitly-extendable";
const kDynModuleCanBeOverriddenPragmaName = "dyn-module:can-be-overridden";
const kDynModuleCanBeOverriddenImplicitlyPragmaName =
"dyn-module:can-be-overridden-implicitly";
const kDynModuleCallablePragmaName = "dyn-module:callable";
const kDynModuleImplicitlyCallablePragmaName = "dyn-module:implicitly-callable";
const kDynModuleCanBeUsedAsTypePragmaName = "dyn-module:can-be-used-as-type";
const kDynModuleEntryPointPragmaName = "dyn-module:entry-point";
const kDynModuleDynamicallyCallablePragmaName =
"dyn-module:dynamically-callable";
const kDynModuleImplicitlyDynamicallyCallablePragmaName =
"dyn-module:implicitly-dynamically-callable";
abstract class ParsedPragma {}
enum PragmaEntryPointType {
Default,
Extendable,
ImplicitlyExtendable,
CanBeOverridden,
GetterOnly,
SetterOnly,
CallOnly,
CanBeUsedAsType,
DynamicallyCallable,
}
enum PragmaRecognizedType { AsmIntrinsic, GraphIntrinsic, Other }
class ParsedEntryPointPragma implements ParsedPragma {
final PragmaEntryPointType type;
const ParsedEntryPointPragma(this.type);
}
class ParsedResultTypeByTypePragma implements ParsedPragma {
final DartType type;
final bool resultTypeUsesPassedTypeArguments;
const ParsedResultTypeByTypePragma(
this.type,
this.resultTypeUsesPassedTypeArguments,
);
}
class ParsedResultTypeByPathPragma implements ParsedPragma {
final String path;
const ParsedResultTypeByPathPragma(this.path);
}
class ParsedRecognized implements ParsedPragma {
final PragmaRecognizedType type;
const ParsedRecognized(this.type);
}
class ParsedDisableUnboxedParameters implements ParsedPragma {
const ParsedDisableUnboxedParameters();
}
class ParsedKeepNamePragma implements ParsedPragma {
const ParsedKeepNamePragma();
}
class ParsedPlatformConstPragma implements ParsedPragma {
const ParsedPlatformConstPragma();
}
class ParsedFfiNativePragma implements ParsedPragma {
const ParsedFfiNativePragma();
}
class ParsedDynModuleEntryPointPragma implements ParsedPragma {
const ParsedDynModuleEntryPointPragma();
}
class ParsedVmSharedPragma implements ParsedPragma {
const ParsedVmSharedPragma();
}
class ParsedVmDeeplyImmutablePragma implements ParsedPragma {
const ParsedVmDeeplyImmutablePragma();
}
class ParsedVmInvisiblePragma implements ParsedPragma {
const ParsedVmInvisiblePragma();
}
abstract class PragmaAnnotationParser {
/// May return 'null' if the annotation does not represent a recognized
/// @pragma.
ParsedPragma? parsePragma(Expression annotation);
Iterable<R> parsedPragmas<R extends ParsedPragma>(Iterable<Expression> node);
}
class ConstantPragmaAnnotationParser implements PragmaAnnotationParser {
final CoreTypes coreTypes;
final Target target;
ConstantPragmaAnnotationParser(this.coreTypes, this.target);
ParsedEntryPointPragma? getEntryPointTypeFromOptions(
Constant options,
String pragmaName,
) {
PragmaEntryPointType? type;
if (options is NullConstant) {
type = PragmaEntryPointType.Default;
} else if (options is BoolConstant && options.value == true) {
type = PragmaEntryPointType.Default;
} else if (options is StringConstant) {
if (options.value == "get") {
type = PragmaEntryPointType.GetterOnly;
} else if (options.value == "set") {
type = PragmaEntryPointType.SetterOnly;
} else if (options.value == "call") {
type = PragmaEntryPointType.CallOnly;
} else {
throw "Error: string directive to "
"@pragma('$pragmaName', ...) "
"must be either 'get' or 'set' for fields "
"or 'get' or 'call' for procedures.";
}
}
return type != null ? ParsedEntryPointPragma(type) : null;
}
ParsedPragma? parsePragma(Expression annotation) {
InstanceConstant? pragmaConstant;
if (annotation is ConstantExpression) {
Constant constant = annotation.constant;
if (constant is InstanceConstant) {
if (constant.classNode == coreTypes.pragmaClass) {
pragmaConstant = constant;
}
} else if (constant is UnevaluatedConstant) {
throw 'Error: unevaluated constant $constant';
}
} else if (annotation is InvalidExpression) {
return null;
} else {
throw 'Error: non-constant annotation $annotation';
}
if (pragmaConstant == null) return null;
String pragmaName;
Constant? name =
pragmaConstant.fieldValues[coreTypes.pragmaName.fieldReference];
if (name is StringConstant) {
pragmaName = name.value;
} else {
return null;
}
if (!target.isSupportedPragma(pragmaName)) return null;
Constant options =
pragmaConstant.fieldValues[coreTypes.pragmaOptions.fieldReference]!;
switch (pragmaName) {
case kVmEntryPointPragmaName:
return getEntryPointTypeFromOptions(options, pragmaName);
case kVmExactResultTypePragmaName:
if (options is TypeLiteralConstant) {
return ParsedResultTypeByTypePragma(options.type, false);
} else if (options is StringConstant) {
return ParsedResultTypeByPathPragma(options.value);
} else if (options is ListConstant &&
options.entries.length == 2 &&
options.entries[0] is TypeLiteralConstant &&
options.entries[1] is StringConstant &&
(options.entries[1] as StringConstant).value ==
kResultTypeUsesPassedTypeArguments) {
return ParsedResultTypeByTypePragma(
(options.entries[0] as TypeLiteralConstant).type,
true,
);
}
throw "ERROR: Unsupported option to '$kVmExactResultTypePragmaName' "
"pragma: $options";
case kVmRecognizedPragmaName:
PragmaRecognizedType? type;
if (options is StringConstant) {
if (options.value == "asm-intrinsic") {
type = PragmaRecognizedType.AsmIntrinsic;
} else if (options.value == "graph-intrinsic") {
type = PragmaRecognizedType.GraphIntrinsic;
} else if (options.value == "other") {
type = PragmaRecognizedType.Other;
}
}
if (type == null) {
throw "ERROR: Unsupported option to '$kVmRecognizedPragmaName' "
"pragma: $options";
}
return ParsedRecognized(type);
case kVmDisableUnboxedParametersPragmaName:
return const ParsedDisableUnboxedParameters();
case kVmKeepNamePragmaName:
return const ParsedKeepNamePragma();
case kVmPlatformConstPragmaName:
return const ParsedPlatformConstPragma();
case kVmPlatformConstIfPragmaName:
if (options is! BoolConstant) {
throw "ERROR: Non-boolean option to '$kVmPlatformConstIfPragmaName' "
"pragma: $options";
}
return options.value ? const ParsedPlatformConstPragma() : null;
case kVmFfiNative:
return const ParsedFfiNativePragma();
case kWasmEntryPointPragmaName:
return const ParsedEntryPointPragma(PragmaEntryPointType.Default);
case kWasmExportPragmaName:
// Exports are treated as entry points.
return const ParsedEntryPointPragma(PragmaEntryPointType.Default);
case kDynModuleExtendablePragmaName:
return const ParsedEntryPointPragma(PragmaEntryPointType.Extendable);
case kDynModuleImplicitlyExtendablePragmaName:
return const ParsedEntryPointPragma(
PragmaEntryPointType.ImplicitlyExtendable,
);
case kDynModuleCanBeOverriddenPragmaName:
return const ParsedEntryPointPragma(
PragmaEntryPointType.CanBeOverridden,
);
case kDynModuleCanBeUsedAsTypePragmaName:
return const ParsedEntryPointPragma(
PragmaEntryPointType.CanBeUsedAsType,
);
case kDynModuleCallablePragmaName:
case kDynModuleImplicitlyCallablePragmaName:
return getEntryPointTypeFromOptions(options, pragmaName);
case kDynModuleDynamicallyCallablePragmaName:
case kDynModuleImplicitlyDynamicallyCallablePragmaName:
return const ParsedEntryPointPragma(
PragmaEntryPointType.DynamicallyCallable,
);
case kDynModuleEntryPointPragmaName:
return const ParsedDynModuleEntryPointPragma();
case kVmSharedPragmaName:
return const ParsedVmSharedPragma();
case kVmDeeplyImmutablePragmaName:
return const ParsedVmDeeplyImmutablePragma();
case kVmInvisiblePragmaName:
return const ParsedVmInvisiblePragma();
default:
return null;
}
}
Iterable<R> parsedPragmas<R extends ParsedPragma>(
Iterable<Expression> annotations,
) => annotations.map(parsePragma).whereType<R>();
}