Skip to content

Commit

Permalink
[pkg/vm] Create @pragma("vm:platform-const-if", <cond>) annotation
Browse files Browse the repository at this point in the history
If a static field or getter is annotated with
@pragma("vm:platform-const-if", <cond>) and <cond> const evaluates
to true, then uses of the static field or getter are const evaluated
when a target operating system is available. If <cond> const evaluates
to any other value, then the annotation is ignored.

For example, when runtime-only code is guarded like the following,
using Flutter's kDebugMode constant, then debug mode Flutter programs
can alter the defaultTargetPlatform for testing, but in release mode,
the defaultTargetPlatform getter is const evaluated and code guarded
with defaultTargetPlatform checks can be eliminated if unreachable:

  @pragma("vm:platform-const-if", !kDebugMode)
  TargetPlatform get defaultTargetPlatform {
   ...
   assert(() {
     if (Platform.environment.containsKey('FLUTTER_TEST')) {
       result = TestPlatform.android;
     }
     return true;
   }());
   if (kDebugMode &&
       platform.debugDefaultTargetPlatformOverride != null) {
     result = platform.debugDefaultTargetPlatformOverride;
   }
   ...
  }

TEST=pkg/vm/test/transformations/vm_constant_evaluator

Change-Id: I55b88502a908c56cf42a761dd06741f15c8a23d9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/333220
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
  • Loading branch information
sstrickl authored and Commit Queue committed Jan 4, 2024
1 parent 91d16af commit 57a1168
Show file tree
Hide file tree
Showing 34 changed files with 4,181 additions and 167 deletions.
46 changes: 25 additions & 21 deletions pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -763,8 +763,12 @@ class ConstantsTransformer extends RemovingTransformer {
if (shouldInline(target.initializer!)) {
return evaluateAndTransformWithContext(node, node);
}
} else if (target is Procedure && target.kind == ProcedureKind.Method) {
return evaluateAndTransformWithContext(node, node);
} else if (target is Procedure) {
if (target.kind == ProcedureKind.Method) {
return evaluateAndTransformWithContext(node, node);
} else if (target.kind == ProcedureKind.Getter && enableConstFunctions) {
return evaluateAndTransformWithContext(node, node);
}
}
return super.visitStaticGet(node, removalSentinel);
}
Expand Down Expand Up @@ -2443,7 +2447,8 @@ class ConstantEvaluator implements ExpressionVisitor<Constant> {
final EvaluationMode evaluationMode;

final bool enableTripleShift;
final bool enableConstFunctions;
final bool enableAsserts;
bool enableConstFunctions;
bool inExtensionTypeConstConstructor = false;

final Map<Constant, Constant> canonicalizationCache;
Expand Down Expand Up @@ -2488,6 +2493,7 @@ class ConstantEvaluator implements ExpressionVisitor<Constant> {
this._environmentDefines, this.typeEnvironment, this.errorReporter,
{this.enableTripleShift = false,
this.enableConstFunctions = false,
this.enableAsserts = true,
this.errorOnUnevaluatedConstant = false,
this.evaluationMode = EvaluationMode.weak})
: numberSemantics = backend.numberSemantics,
Expand Down Expand Up @@ -3692,6 +3698,7 @@ class ConstantEvaluator implements ExpressionVisitor<Constant> {
/// Returns [null] on success and an error-"constant" on failure, as such the
/// return value should be checked.
AbortConstant? checkAssert(AssertStatement statement) {
if (!enableAsserts) return null;
final Constant condition = _evaluateSubexpression(statement.condition);
if (condition is AbortConstant) return condition;

Expand Down Expand Up @@ -4577,27 +4584,23 @@ class ConstantEvaluator implements ExpressionVisitor<Constant> {

@override
Constant visitStaticGet(StaticGet node) {
return withNewEnvironment(() {
final Member target = node.target;
visitedLibraries.add(target.enclosingLibrary);
if (target is Field) {
if (target.isConst) {
return evaluateExpressionInContext(target, target.initializer!);
}
return createEvaluationErrorConstant(
node,
templateConstEvalInvalidStaticInvocation
.withArguments(target.name.text));
} else if (target is Procedure && target.kind == ProcedureKind.Method) {
final Member target = node.target;
visitedLibraries.add(target.enclosingLibrary);
if (target is Field && target.isConst) {
return withNewEnvironment(
() => evaluateExpressionInContext(target, target.initializer!));
} else if (target is Procedure) {
if (target.kind == ProcedureKind.Method) {
// TODO(johnniwinther): Remove this. This should never occur.
return canonicalize(new StaticTearOffConstant(target));
} else {
return createEvaluationErrorConstant(
node,
templateConstEvalInvalidStaticInvocation
.withArguments(target.name.text));
} else if (target.kind == ProcedureKind.Getter && enableConstFunctions) {
return _handleFunctionInvocation(target.function, [], [], {});
}
});
}
return createEvaluationErrorConstant(
node,
templateConstEvalInvalidStaticInvocation
.withArguments(target.name.text));
}

@override
Expand Down Expand Up @@ -5617,6 +5620,7 @@ class StatementConstantEvaluator implements StatementVisitor<ExecutionStatus> {

@override
ExecutionStatus visitAssertBlock(AssertBlock node) {
if (!exprEvaluator.enableAsserts) return const ProceedStatus();
throw new UnsupportedError(
'Statement constant evaluation does not support ${node.runtimeType}.');
}
Expand Down
4 changes: 3 additions & 1 deletion pkg/vm/lib/kernel_front_end.dart
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,9 @@ Future runGlobalTransformations(
final os = targetOS != null ? TargetOS.fromString(targetOS)! : null;
final evaluator = vm_constant_evaluator.VMConstantEvaluator.create(
target, component, os, nnbdMode,
environmentDefines: environmentDefines, coreTypes: coreTypes);
enableAsserts: enableAsserts,
environmentDefines: environmentDefines,
coreTypes: coreTypes);
unreachable_code_elimination.transformComponent(
target, component, evaluator, enableAsserts);

Expand Down
40 changes: 29 additions & 11 deletions pkg/vm/lib/transformations/pragma.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,60 +15,66 @@ const kResultTypeUsesPassedTypeArguments =
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";

// Pragmas recognized by dart2wasm
const kWasmEntryPointPragmaName = "wasm:entry-point";
const kWasmExportPragmaName = "wasm:export";

abstract class ParsedPragma {
const ParsedPragma();
}
abstract class ParsedPragma {}

enum PragmaEntryPointType { Default, GetterOnly, SetterOnly, CallOnly }

enum PragmaRecognizedType { AsmIntrinsic, GraphIntrinsic, Other }

class ParsedEntryPointPragma extends ParsedPragma {
class ParsedEntryPointPragma implements ParsedPragma {
final PragmaEntryPointType type;
const ParsedEntryPointPragma(this.type);
}

class ParsedResultTypeByTypePragma extends ParsedPragma {
class ParsedResultTypeByTypePragma implements ParsedPragma {
final DartType type;
final bool resultTypeUsesPassedTypeArguments;
const ParsedResultTypeByTypePragma(
this.type, this.resultTypeUsesPassedTypeArguments);
}

class ParsedResultTypeByPathPragma extends ParsedPragma {
class ParsedResultTypeByPathPragma implements ParsedPragma {
final String path;
const ParsedResultTypeByPathPragma(this.path);
}

class ParsedNonNullableResultType extends ParsedPragma {
class ParsedNonNullableResultType implements ParsedPragma {
const ParsedNonNullableResultType();
}

class ParsedRecognized extends ParsedPragma {
class ParsedRecognized implements ParsedPragma {
final PragmaRecognizedType type;
const ParsedRecognized(this.type);
}

class ParsedDisableUnboxedParameters extends ParsedPragma {
class ParsedDisableUnboxedParameters implements ParsedPragma {
const ParsedDisableUnboxedParameters();
}

class ParsedKeepNamePragma extends ParsedPragma {
class ParsedKeepNamePragma implements ParsedPragma {
const ParsedKeepNamePragma();
}

class ParsedPlatformConstPragma implements ParsedPragma {
const ParsedPlatformConstPragma();
}

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 extends PragmaAnnotationParser {
class ConstantPragmaAnnotationParser implements PragmaAnnotationParser {
final CoreTypes coreTypes;
final Target target;

Expand Down Expand Up @@ -166,6 +172,14 @@ class ConstantPragmaAnnotationParser extends PragmaAnnotationParser {
return const ParsedDisableUnboxedParameters();
case kVmKeepNamePragmaName:
return ParsedKeepNamePragma();
case kVmPlatformConstPragmaName:
return ParsedPlatformConstPragma();
case kVmPlatformConstIfPragmaName:
if (options is! BoolConstant) {
throw "ERROR: Non-boolean option to '$kVmPlatformConstIfPragmaName' "
"pragma: $options";
}
return options.value ? ParsedPlatformConstPragma() : null;
case kWasmEntryPointPragmaName:
return ParsedEntryPointPragma(PragmaEntryPointType.Default);
case kWasmExportPragmaName:
Expand All @@ -175,4 +189,8 @@ class ConstantPragmaAnnotationParser extends PragmaAnnotationParser {
return null;
}
}

Iterable<R> parsedPragmas<R extends ParsedPragma>(
Iterable<Expression> annotations) =>
annotations.map(parsePragma).whereType<R>();
}
37 changes: 16 additions & 21 deletions pkg/vm/lib/transformations/unreachable_code_elimination.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,25 +48,10 @@ class SimpleUnreachableCodeElimination extends RemovingTransformer {
return constant is BoolConstant ? constant.value : null;
}

Expression _makeConstantExpression(Constant constant, Expression node) {
if (constant is UnevaluatedConstant &&
constant.expression is InvalidExpression) {
return constant.expression;
}
ConstantExpression constantExpression = new ConstantExpression(
constant, node.getStaticType(_staticTypeContext!))
..fileOffset = node.fileOffset;
if (node is FileUriExpression) {
return new FileUriConstantExpression(constantExpression.constant,
type: constantExpression.type, fileUri: node.fileUri)
..fileOffset = node.fileOffset;
}
return constantExpression;
}

Expression _createBoolConstantExpression(bool value, Expression node) =>
_makeConstantExpression(
constantEvaluator.canonicalize(BoolConstant(value)), node);
ConstantExpression(constantEvaluator.makeBoolConstant(value),
node.getStaticType(_staticTypeContext!))
..fileOffset = node.fileOffset;

Statement _makeEmptyBlockIfEmptyStatement(Statement node, TreeNode parent) =>
node is EmptyStatement ? (Block(<Statement>[])..parent = parent) : node;
Expand Down Expand Up @@ -219,11 +204,21 @@ class SimpleUnreachableCodeElimination extends RemovingTransformer {
if (target is Field && target.isConst) {
throw 'StaticGet from const field $target should be evaluated by front-end: $node';
}
if (!constantEvaluator.transformerShouldEvaluateExpression(node)) {
return node;

if (!constantEvaluator.hasTargetOS ||
!constantEvaluator.isPlatformConst(target)) {
return super.visitStaticGet(node, removalSentinel);
}

final result = constantEvaluator.evaluate(_staticTypeContext!, node);
return _makeConstantExpression(result, node);

if (result is UnevaluatedConstant &&
result.expression is InvalidExpression) {
return result.expression;
}

final type = node.getStaticType(_staticTypeContext!);
return ConstantExpression(result, type)..fileOffset = node.fileOffset;
}

@override
Expand Down
Loading

0 comments on commit 57a1168

Please sign in to comment.