[dart2js] Implement basic lowering for late instance variables.

This feature is gated behind the --experiment-late-instance-variables
flag.

Change-Id: I1ecb2d4d960b58204207ea055361463efa3a0bcb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/200922
Commit-Queue: Mayank Patke <fishythefish@google.com>
Reviewed-by: Stephen Adams <sra@google.com>
This commit is contained in:
Mayank Patke 2021-06-02 23:21:24 +00:00 committed by commit-bot@chromium.org
parent 7cdb6444f2
commit 93c96d5857
9 changed files with 343 additions and 49 deletions

View file

@ -52,6 +52,11 @@ class Flags {
static const String experimentNewRti = '--experiment-new-rti';
/// Use the dart2js lowering of late instance variables rather than the CFE
/// lowering.
static const String experimentLateInstanceVariables =
'--experiment-late-instance-variables';
static const String enableLanguageExperiments = '--enable-experiment';
static const String fastStartup = '--fast-startup';

View file

@ -602,6 +602,7 @@ Future<api.CompilationResult> compile(List<String> argv,
new OptionHandler(Flags.experimentUnreachableMethodsThrow, passThrough),
new OptionHandler(Flags.experimentCallInstrumentation, passThrough),
new OptionHandler(Flags.experimentNewRti, ignoreOption),
new OptionHandler(Flags.experimentLateInstanceVariables, passThrough),
new OptionHandler('${Flags.mergeFragmentsThreshold}=.+', passThrough),
// The following three options must come last.

View file

@ -17,6 +17,7 @@ import 'package:kernel/reference_from_index.dart';
import 'package:kernel/target/changed_structure_notifier.dart';
import 'package:kernel/target/targets.dart';
import '../options.dart';
import 'invocation_mirror_constants.dart';
import 'transformations/lowering.dart' as lowering show transformLibraries;
@ -77,17 +78,20 @@ class Dart2jsTarget extends Target {
@override
final String name;
final bool omitLateNames;
final CompilerOptions options;
Map<String, ir.Class> _nativeClasses;
Dart2jsTarget(this.name, this.flags, {this.omitLateNames = false});
Dart2jsTarget(this.name, this.flags, {this.options});
@override
bool get enableNoSuchMethodForwarders => true;
@override
final int enabledLateLowerings = _enabledLateLowerings;
int get enabledLateLowerings =>
(options != null && options.experimentLateInstanceVariables)
? LateLowering.none
: _enabledLateLowerings;
@override
bool get supportsLateLoweringSentinel => true;
@ -161,8 +165,7 @@ class Dart2jsTarget extends Target {
_nativeClasses)
.visitLibrary(library);
}
lowering.transformLibraries(libraries, coreTypes, hierarchy,
omitLateNames: omitLateNames);
lowering.transformLibraries(libraries, coreTypes, hierarchy, options);
logger?.call("Lowering transformations performed");
}

View file

@ -136,8 +136,8 @@ class KernelLoaderTask extends CompilerTask {
}
} else {
bool verbose = false;
Target target = Dart2jsTarget(targetName, TargetFlags(),
omitLateNames: _options.omitLateNames);
Target target =
Dart2jsTarget(targetName, TargetFlags(), options: _options);
fe.FileSystem fileSystem = CompilerFileSystem(_compilerInput);
fe.Verbosity verbosity = _options.verbosity;
fe.DiagnosticMessageHandler onDiagnostic =

View file

@ -6,16 +6,7 @@ import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/type_algebra.dart';
bool _shouldLowerVariable(VariableDeclaration variable) => variable.isLate;
bool _shouldLowerUninitializedVariable(VariableDeclaration variable) =>
_shouldLowerVariable(variable) && variable.initializer == null;
bool _shouldLowerInitializedVariable(VariableDeclaration variable) =>
_shouldLowerVariable(variable) && variable.initializer != null;
bool _shouldLowerField(Field field) =>
field.isLate && field.isStatic && field.initializer == null;
import '../../options.dart';
class _Reader {
final Procedure _procedure;
@ -30,7 +21,8 @@ class _Reader {
class LateLowering {
final CoreTypes _coreTypes;
final bool omitLateNames;
final bool _omitLateNames;
final bool _lowerInstanceVariables;
final _Reader _readLocal;
final _Reader _readField;
@ -38,15 +30,26 @@ class LateLowering {
final _Reader _readInitializedFinal;
// Each map contains the mapping from late local variables to cells for a
// given function scope.
// given function scope. All late local variables are lowered to cells.
final List<Map<VariableDeclaration, VariableDeclaration>> _variableCells = [];
// Uninitialized late static fields are lowered to cells.
final Map<Field, Field> _fieldCells = {};
// Late instance fields are lowered to a backing field (plus a getter/setter
// pair).
final Map<Field, Field> _backingInstanceFields = {};
// TODO(fishythefish): Remove this when [FieldInitializer] maintains a correct
// [Reference] to its [Field].
final Map<Procedure, Field> _getterToField = {};
Member _contextMember;
LateLowering(this._coreTypes, {this.omitLateNames})
: assert(omitLateNames != null),
LateLowering(this._coreTypes, CompilerOptions _options)
: _omitLateNames = _options?.omitLateNames ?? false,
_lowerInstanceVariables =
_options?.experimentLateInstanceVariables ?? false,
_readLocal = _Reader(_coreTypes.cellReadLocal),
_readField = _Reader(_coreTypes.cellReadField),
_readInitialized = _Reader(_coreTypes.initializedCellRead),
@ -54,6 +57,26 @@ class LateLowering {
Nullability get nonNullable => _contextMember.enclosingLibrary.nonNullable;
bool _shouldLowerVariable(VariableDeclaration variable) => variable.isLate;
bool _shouldLowerUninitializedVariable(VariableDeclaration variable) =>
_shouldLowerVariable(variable) && variable.initializer == null;
bool _shouldLowerInitializedVariable(VariableDeclaration variable) =>
_shouldLowerVariable(variable) && variable.initializer != null;
bool _shouldLowerStaticField(Field field) =>
field.isLate && field.isStatic && field.initializer == null;
bool _shouldLowerInstanceField(Field field) =>
field.isLate && !field.isStatic && _lowerInstanceVariables;
String _mangleFieldName(Field field) {
assert(_shouldLowerInstanceField(field));
Class cls = field.parent;
return '_#${cls.name}#${field.name.text}';
}
void transformAdditionalExports(Library library) {
List<Reference> additionalExports = library.additionalExports;
Set<Reference> newExports = {};
@ -67,7 +90,7 @@ class LateLowering {
}
ConstructorInvocation _callCellConstructor(Expression name, int fileOffset) =>
omitLateNames
_omitLateNames
? _callCellUnnamedConstructor(fileOffset)
: _callCellNamedConstructor(name, fileOffset);
@ -84,7 +107,7 @@ class LateLowering {
ConstructorInvocation _callInitializedCellConstructor(
Expression name, Expression initializer, int fileOffset) =>
omitLateNames
_omitLateNames
? _callInitializedCellUnnamedConstructor(initializer, fileOffset)
: _callInitializedCellNamedConstructor(name, initializer, fileOffset);
@ -125,6 +148,11 @@ class LateLowering {
interfaceTarget: _setter)
..fileOffset = fileOffset;
StaticInvocation _callIsSentinel(Expression value, int fileOffset) =>
StaticInvocation(_coreTypes.isSentinelMethod,
Arguments([value])..fileOffset = fileOffset)
..fileOffset = fileOffset;
void enterFunction() {
_variableCells.add(null);
}
@ -210,8 +238,7 @@ class LateLowering {
return _variableCell(variable);
}
VariableGet _variableCellAccess(
VariableDeclaration variable, int fileOffset) {
VariableGet _variableCellRead(VariableDeclaration variable, int fileOffset) {
assert(_shouldLowerVariable(variable));
return VariableGet(_variableCell(variable))..fileOffset = fileOffset;
}
@ -223,7 +250,7 @@ class LateLowering {
if (!_shouldLowerVariable(variable)) return node;
int fileOffset = node.fileOffset;
VariableGet cell = _variableCellAccess(variable, fileOffset);
VariableGet cell = _variableCellRead(variable, fileOffset);
_Reader reader = variable.initializer == null
? _readLocal
: (variable.isFinal ? _readInitializedFinal : _readInitialized);
@ -238,7 +265,7 @@ class LateLowering {
if (!_shouldLowerVariable(variable)) return node;
int fileOffset = node.fileOffset;
VariableGet cell = _variableCellAccess(variable, fileOffset);
VariableGet cell = _variableCellRead(variable, fileOffset);
Procedure setter = variable.initializer == null
? (variable.isFinal
? _coreTypes.cellFinalLocalValueSetter
@ -250,7 +277,7 @@ class LateLowering {
}
Field _fieldCell(Field field) {
assert(_shouldLowerField(field));
assert(_shouldLowerStaticField(field));
return _fieldCells.putIfAbsent(field, () {
int fileOffset = field.fileOffset;
Name name = field.name;
@ -268,43 +295,261 @@ class LateLowering {
});
}
StaticGet _fieldCellAccess(Field field, int fileOffset) =>
StaticGet(_fieldCell(field))..fileOffset = fileOffset;
Field _backingInstanceField(Field field) {
assert(_shouldLowerInstanceField(field));
return _backingInstanceFields[field] ??=
_computeBackingInstanceField(field);
}
Field _computeBackingInstanceField(Field field) {
assert(_shouldLowerInstanceField(field));
assert(!_backingInstanceFields.containsKey(field));
int fileOffset = field.fileOffset;
Uri fileUri = field.fileUri;
Name name = field.name;
String nameText = name.text;
DartType type = field.type;
Expression initializer = field.initializer;
Class enclosingClass = field.enclosingClass;
Name mangledName = Name(_mangleFieldName(field), field.enclosingLibrary);
Field backingField = Field.mutable(mangledName,
type: type,
initializer: StaticInvocation(_coreTypes.createSentinelMethod,
Arguments(const [], types: [type])..fileOffset = fileOffset)
..fileOffset = fileOffset,
fileUri: fileUri)
..fileOffset = fileOffset
..isNonNullableByDefault = true
..isInternalImplementation = true;
InstanceGet fieldRead() => InstanceGet(InstanceAccessKind.Instance,
ThisExpression()..fileOffset = fileOffset, mangledName,
interfaceTarget: backingField, resultType: type)
..fileOffset = fileOffset;
InstanceSet fieldWrite(Expression value) => InstanceSet(
InstanceAccessKind.Instance,
ThisExpression()..fileOffset = fileOffset,
mangledName,
value,
interfaceTarget: backingField)
..fileOffset = fileOffset;
Statement getterBody() {
if (initializer == null) {
// The lowered getter for `late T field;` and `late final T field;` is
//
// T get field => _lateReadCheck<T>(this._#field, "field");
return ReturnStatement(
StaticInvocation(
_coreTypes.lateReadCheck,
Arguments([fieldRead(), _nameLiteral(nameText, fileOffset)],
types: [type])
..fileOffset = fileOffset)
..fileOffset = fileOffset)
..fileOffset = fileOffset;
} else if (field.isFinal) {
// The lowered getter for `late final T field = e;` is
//
// T get field {
// var value = this._#field;
// if (isSentinel(value)) {
// final result = e;
// _lateInitializeOnceCheck(this._#field, "field");
// value = this._#field = result;
// }
// return value;
// }
VariableDeclaration value =
VariableDeclaration('value', initializer: fieldRead(), type: type)
..fileOffset = fileOffset;
VariableGet valueRead() => VariableGet(value)..fileOffset = fileOffset;
VariableDeclaration result = VariableDeclaration('result',
initializer: initializer, type: type, isFinal: true)
..fileOffset = fileOffset;
VariableGet resultRead() =>
VariableGet(result)..fileOffset = fileOffset;
return Block([
value,
IfStatement(
_callIsSentinel(valueRead(), fileOffset),
Block([
result,
ExpressionStatement(
StaticInvocation(
_coreTypes.lateInitializeOnceCheck,
Arguments(
[fieldRead(), _nameLiteral(nameText, fileOffset)])
..fileOffset = fileOffset)
..fileOffset = fileOffset)
..fileOffset = fileOffset,
ExpressionStatement(
VariableSet(value, fieldWrite(resultRead()))
..fileOffset = fileOffset)
..fileOffset = fileOffset
])
..fileOffset = fileOffset,
null)
..fileOffset = fileOffset,
ReturnStatement(valueRead())..fileOffset = fileOffset
])
..fileOffset = fileOffset;
} else {
// The lowered getter for `late T field = e;` is
//
// T get field {
// var value = this._#field;
// if (isSentinel(value)) {
// value = this._#field = e;
// }
// return value;
// }
VariableDeclaration value =
VariableDeclaration('value', initializer: fieldRead(), type: type)
..fileOffset = fileOffset;
VariableGet valueRead() => VariableGet(value)..fileOffset = fileOffset;
return Block([
value,
IfStatement(
_callIsSentinel(valueRead(), fileOffset),
ExpressionStatement(
VariableSet(value, fieldWrite(initializer))
..fileOffset = fileOffset)
..fileOffset = fileOffset,
null)
..fileOffset = fileOffset,
ReturnStatement(valueRead())..fileOffset = fileOffset
])
..fileOffset = fileOffset;
}
}
Procedure getter = Procedure(name, ProcedureKind.Getter,
FunctionNode(getterBody(), returnType: type)..fileOffset = fileOffset,
fileUri: fileUri, reference: field.getterReference)
..fileOffset = fileOffset
..isNonNullableByDefault = true;
enclosingClass.addProcedure(getter);
_getterToField[getter] = backingField;
VariableDeclaration setterValue = VariableDeclaration('value', type: type)
..fileOffset = fileOffset;
VariableGet setterValueRead() =>
VariableGet(setterValue)..fileOffset = fileOffset;
Statement setterBody() {
if (!field.isFinal) {
// The lowered setter for `late T field;` and `late T field = e;` is
//
// set field(T value) {
// this._#field = value;
// }
return ExpressionStatement(fieldWrite(setterValueRead()))
..fileOffset = fileOffset;
} else if (initializer == null) {
// The lowered setter for `late final T field;` is
//
// set field(T value) {
// _lateWriteOnceCheck(this._#field, "field");
// this._#field = value;
// }
return Block([
ExpressionStatement(
StaticInvocation(
_coreTypes.lateWriteOnceCheck,
Arguments([fieldRead(), _nameLiteral(nameText, fileOffset)])
..fileOffset = fileOffset)
..fileOffset = fileOffset)
..fileOffset = fileOffset,
ExpressionStatement(fieldWrite(setterValueRead()))
..fileOffset = fileOffset
])
..fileOffset = fileOffset;
} else {
// There is no setter for `late final T field = e;`.
return null;
}
}
Statement body = setterBody();
if (body != null) {
Procedure setter = Procedure(
name,
ProcedureKind.Setter,
FunctionNode(body,
positionalParameters: [setterValue], returnType: VoidType())
..fileOffset = fileOffset,
fileUri: fileUri,
reference: field.setterReference)
..fileOffset = fileOffset
..isNonNullableByDefault = true;
enclosingClass.addProcedure(setter);
}
return backingField;
}
TreeNode transformField(Field field, Member contextMember) {
_contextMember = contextMember;
if (!_shouldLowerField(field)) return field;
if (_shouldLowerStaticField(field)) return _fieldCell(field);
if (_shouldLowerInstanceField(field)) return _backingInstanceField(field);
return _fieldCell(field);
return field;
}
TreeNode transformFieldInitializer(
FieldInitializer initializer, Member contextMember) {
_contextMember = contextMember;
// If the [Field] has been lowered, we can't use `node.field` to retrieve it
// because the `getterReference` of the original field now points to the new
// getter for the backing field.
// TODO(fishythefish): Clean this up when [FieldInitializer] maintains a
// correct [Reference] to its [Field].
NamedNode node = initializer.fieldReference.node;
assert(node != null);
Field backingField;
if (node is Field) {
if (!_shouldLowerInstanceField(node)) return initializer;
backingField = _backingInstanceField(node);
} else {
backingField = _getterToField[node];
}
assert(backingField != null);
return FieldInitializer(backingField, initializer.value)
..fileOffset = initializer.fileOffset;
}
StaticGet _fieldCellAccess(Field field, int fileOffset) =>
StaticGet(_fieldCell(field))..fileOffset = fileOffset;
TreeNode transformStaticGet(StaticGet node, Member contextMember) {
_contextMember = contextMember;
Member target = node.target;
if (target is Field && _shouldLowerField(target)) {
if (target is Field && _shouldLowerStaticField(target)) {
int fileOffset = node.fileOffset;
StaticGet cell = _fieldCellAccess(target, fileOffset);
return _callReader(_readField, cell, target.type, fileOffset);
} else {
return node;
}
return node;
}
TreeNode transformStaticSet(StaticSet node, Member contextMember) {
_contextMember = contextMember;
Member target = node.target;
if (target is Field && _shouldLowerField(target)) {
if (target is Field && _shouldLowerStaticField(target)) {
int fileOffset = node.fileOffset;
StaticGet cell = _fieldCellAccess(target, fileOffset);
Procedure setter = target.isFinal
? _coreTypes.cellFinalFieldValueSetter
: _coreTypes.cellValueSetter;
return _callSetter(setter, cell, node.value, fileOffset);
} else {
return node;
}
return node;
}
}

View file

@ -5,6 +5,8 @@
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
import 'package:kernel/core_types.dart' show CoreTypes;
import '../../options.dart';
import 'factory_specializer.dart';
import 'late_lowering.dart';
@ -13,12 +15,9 @@ import 'late_lowering.dart';
///
/// Each transformation is applied locally to AST nodes of certain types after
/// transforming children nodes.
void transformLibraries(
List<Library> libraries, CoreTypes coreTypes, ClassHierarchy hierarchy,
{bool omitLateNames}) {
assert(omitLateNames != null);
final transformer =
_Lowering(coreTypes, hierarchy, omitLateNames: omitLateNames);
void transformLibraries(List<Library> libraries, CoreTypes coreTypes,
ClassHierarchy hierarchy, CompilerOptions options) {
final transformer = _Lowering(coreTypes, hierarchy, options);
libraries.forEach(transformer.visitLibrary);
// Do a second pass to remove/replace now-unused nodes.
@ -34,10 +33,10 @@ class _Lowering extends Transformer {
Member _currentMember;
_Lowering(CoreTypes coreTypes, ClassHierarchy hierarchy, {bool omitLateNames})
: assert(omitLateNames != null),
factorySpecializer = FactorySpecializer(coreTypes, hierarchy),
_lateLowering = LateLowering(coreTypes, omitLateNames: omitLateNames);
_Lowering(
CoreTypes coreTypes, ClassHierarchy hierarchy, CompilerOptions _options)
: factorySpecializer = FactorySpecializer(coreTypes, hierarchy),
_lateLowering = LateLowering(coreTypes, _options);
void transformAdditionalExports(Library node) {
_lateLowering.transformAdditionalExports(node);
@ -88,6 +87,12 @@ class _Lowering extends Transformer {
return _lateLowering.transformField(node, _currentMember);
}
@override
TreeNode visitFieldInitializer(FieldInitializer node) {
node.transformChildren(this);
return _lateLowering.transformFieldInitializer(node, _currentMember);
}
@override
TreeNode visitStaticGet(StaticGet node) {
node.transformChildren(this);

View file

@ -400,6 +400,10 @@ class CompilerOptions implements DiagnosticOptions {
/// called.
bool experimentCallInstrumentation = false;
/// Use the dart2js lowering of late instance variables rather than the CFE
/// lowering.
bool experimentLateInstanceVariables = false;
/// When null-safety is enabled, whether the compiler should emit code with
/// unsound or sound semantics.
///
@ -518,6 +522,8 @@ class CompilerOptions implements DiagnosticOptions {
_hasOption(options, Flags.experimentUnreachableMethodsThrow)
..experimentCallInstrumentation =
_hasOption(options, Flags.experimentCallInstrumentation)
..experimentLateInstanceVariables =
_hasOption(options, Flags.experimentLateInstanceVariables)
..generateSourceMap = !_hasOption(options, Flags.noSourceMaps)
..outputUri = _extractUriOption(options, '--out=')
..platformBinaries = platformBinaries

View file

@ -371,6 +371,15 @@ class CoreTypes {
late final Procedure initializedCellFinalValueSetter = index.getMember(
'dart:_late_helper', '_InitializedCell', 'set:finalValue') as Procedure;
late final Procedure lateReadCheck =
index.getTopLevelProcedure('dart:_late_helper', '_lateReadCheck');
late final Procedure lateWriteOnceCheck =
index.getTopLevelProcedure('dart:_late_helper', '_lateWriteOnceCheck');
late final Procedure lateInitializeOnceCheck = index.getTopLevelProcedure(
'dart:_late_helper', '_lateInitializeOnceCheck');
InterfaceType get objectLegacyRawType {
return _objectLegacyRawType ??= _legacyRawTypes[objectClass] ??=
new InterfaceType(objectClass, Nullability.legacy, const <DartType>[]);

View file

@ -4,7 +4,7 @@
library _late_helper;
import 'dart:_internal' show LateError;
import 'dart:_internal' show LateError, createSentinel, isSentinel;
void throwLateFieldADI(String fieldName) => throw LateError.fieldADI(fieldName);
@ -109,3 +109,23 @@ class _InitializedCell {
_value = v;
}
}
// Helpers for lowering late instance fields:
// TODO(fishythefish): Support specialization of sentinels based on type.
@pragma('dart2js:noInline')
@pragma('dart2js:as:trust')
T _lateReadCheck<T>(Object? value, String name) {
if (isSentinel(value)) throw LateError.fieldNI(name);
return value as T;
}
@pragma('dart2js:noInline')
void _lateWriteOnceCheck(Object? value, String name) {
if (!isSentinel(value)) throw LateError.fieldAI(name);
}
@pragma('dart2js:noInline')
void _lateInitializeOnceCheck(Object? value, String name) {
if (!isSentinel(value)) throw LateError.fieldADI(name);
}