blob: 95b283ddc8ceb36c422bb99c51cf3ba706c7cab2 [file] [log] [blame]
// Copyright (c) 2014, 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.
library;
import '../../common.dart';
import '../../common/elements.dart' show JCommonElements, JElementEnvironment;
import '../../common/names.dart' show Names, Selectors;
import '../../constants/values.dart';
import '../../deferred_load/output_unit.dart'
show deferredPartFileName, OutputUnit, OutputUnitData;
import '../../elements/entities.dart';
import '../../elements/types.dart';
import '../../io/source_information.dart';
import '../../js/js.dart' as js;
import '../../js_backend/field_analysis.dart'
show FieldAnalysisData, JFieldAnalysis;
import '../../js_backend/backend_usage.dart';
import '../../js_backend/custom_elements_analysis.dart';
import '../../js_backend/inferred_data.dart';
import '../../js_backend/interceptor_data.dart';
import '../../js_backend/namer.dart' show Namer;
import '../../js_backend/namer.dart' show StringBackedName, compareNames;
import '../../js_backend/native_data.dart';
import '../../js_backend/records_codegen.dart' show RecordsCodegen;
import '../../js_backend/runtime_types.dart' show RuntimeTypesChecks;
import '../../js_backend/runtime_types_codegen.dart' show TypeCheck;
import '../../js_backend/runtime_types_new.dart'
show RecipeEncoder, RecipeEncoding;
import '../../js_backend/runtime_types_new.dart' as new_rti;
import '../../js_backend/runtime_types_resolution.dart' show RuntimeTypesNeed;
import '../../js_model/elements.dart'
show JField, JParameterStub, JSignatureMethod;
import '../../js_model/js_world.dart';
import '../../js_model/records.dart' show RecordData, RecordRepresentation;
import '../../js_model/type_recipe.dart'
show FullTypeEnvironmentStructure, TypeExpressionRecipe;
import '../../native/enqueue.dart' show NativeCodegenEnqueuer;
import '../../options.dart';
import '../../universe/class_hierarchy.dart';
import '../../universe/codegen_world_builder.dart';
import '../../universe/selector.dart' show Selector;
import '../../universe/world_builder.dart' show SelectorConstraints;
import '../class_stub_generator.dart' show ClassStubGenerator;
import '../instantiation_stub_generator.dart' show InstantiationStubGenerator;
import '../interceptor_stub_generator.dart' show InterceptorStubGenerator;
import '../main_call_stub_generator.dart' show MainCallStubGenerator;
import '../runtime_type_generator.dart'
show RuntimeTypeGenerator, TypeTestProperties;
import '../js_emitter.dart' show CodeEmitterTask, Emitter;
import '../model.dart';
import '../sorter.dart';
part 'collector.dart';
part 'field_visitor.dart';
part 'registry.dart';
/// Builds a self-contained representation of the program that can then be
/// emitted more easily by the individual emitters.
class ProgramBuilder {
final CompilerOptions _options;
final JElementEnvironment _elementEnvironment;
final JCommonElements _commonElements;
final OutputUnitData _outputUnitData;
final CodegenWorld _codegenWorld;
final NativeCodegenEnqueuer _nativeCodegenEnqueuer;
final BackendUsage _backendUsage;
final NativeData _nativeData;
final RuntimeTypesNeed _rtiNeed;
final InterceptorData _interceptorData;
final RuntimeTypesChecks _rtiChecks;
final RecipeEncoder _rtiRecipeEncoder;
final OneShotInterceptorData _oneShotInterceptorData;
final CustomElementsCodegenAnalysis _customElementsCodegenAnalysis;
final RecordsCodegen _recordsCodegen;
final Map<MemberEntity, js.Expression> _generatedCode;
final Namer _namer;
final CodeEmitterTask _task;
final JClosedWorld _closedWorld;
final JFieldAnalysis _fieldAnalysis;
final RecordData _recordData;
final InferredData _inferredData;
final SourceInformationStrategy _sourceInformationStrategy;
/// The [Sorter] used for ordering elements in the generated JavaScript.
final Sorter _sorter;
/// Contains the collected information the program builder used to build
/// the model.
// The collector will be filled on the first call to `buildProgram`.
// It is publicly exposed for backwards compatibility. New code
// (and in particular new emitters) should not access it outside this class.
final Collector collector;
final Registry _registry;
final FunctionEntity _mainFunction;
final Iterable<ClassEntity> _rtiNeededClasses;
/// True if the program should store function types in the metadata.
bool _storeFunctionTypesInMetadata = false;
final Set<TypeVariableType> _lateNamedTypeVariables = {};
ClassHierarchy get _classHierarchy => _closedWorld.classHierarchy;
DartTypes get _dartTypes => _closedWorld.dartTypes;
ProgramBuilder(
this._options,
this._elementEnvironment,
this._commonElements,
this._outputUnitData,
this._codegenWorld,
this._nativeCodegenEnqueuer,
this._backendUsage,
this._nativeData,
this._rtiNeed,
this._interceptorData,
this._rtiChecks,
this._rtiRecipeEncoder,
this._oneShotInterceptorData,
this._customElementsCodegenAnalysis,
this._recordsCodegen,
this._generatedCode,
this._namer,
this._task,
this._closedWorld,
this._fieldAnalysis,
this._recordData,
this._inferredData,
this._sourceInformationStrategy,
this._sorter,
this._rtiNeededClasses,
this._mainFunction,
) : collector = Collector(
_commonElements,
_elementEnvironment,
_outputUnitData,
_codegenWorld,
_task.emitter,
_nativeData,
_interceptorData,
_oneShotInterceptorData,
_closedWorld,
_rtiNeededClasses,
_generatedCode,
_sorter,
),
_registry = Registry(_outputUnitData.mainOutputUnit, _sorter);
/// Mapping from [ClassEntity] to constructed [Class]. We need this to
/// update the superclass in the [Class].
final Map<ClassEntity, Class> _classes = {};
/// Mapping from [ClassEntity] to constructed [ClassTypeData] object. Used to build
/// libraries.
final Map<ClassEntity, ClassTypeData> _classTypeData = {};
/// Mapping from [OutputUnit] to constructed [Fragment]. We need this to
/// generate the deferredLoadingMap (to know which hunks to load).
final Map<OutputUnit, Fragment> _outputs = {};
/// Mapping from [ConstantValue] to constructed [Constant]. We need this to
/// update field-initializers to point to the ConstantModel.
final Map<ConstantValue, Constant> _constants = {};
late final Set<Class> _unneededNativeClasses;
ClassEntity get _jsInteropInterceptor =>
_commonElements.jsLegacyJavaScriptObjectClass;
final List<StubMethod> _jsInteropIsChecks = [];
final Set<TypeCheck> _jsInteropTypeChecks = {};
Program buildProgram({bool storeFunctionTypesInMetadata = false}) {
collector.collect();
_storeFunctionTypesInMetadata = storeFunctionTypesInMetadata;
// Note: In rare cases (mostly tests) output units can be empty. This
// happens when the deferred code is dead-code eliminated but we still need
// to check that the library has been loaded.
_closedWorld.outputUnitData.outputUnits.forEach(
_registry.registerOutputUnit,
);
collector.outputClassLists.forEach(_registry.registerClasses);
collector.outputClassTypeLists.forEach(_registry.registerClassTypes);
collector.outputStaticLists.forEach(_registry.registerMembers);
collector.outputConstantLists.forEach(_registerConstants);
collector.outputStaticNonFinalFieldLists.forEach(_registry.registerMembers);
// We need to run the native-preparation before we build the output. The
// preparation code, in turn needs the classes to be set up.
// We thus build the classes before building their containers.
collector.outputClassTypeLists.forEach((
OutputUnit _,
List<ClassEntity> types,
) {
types.forEach(_buildClassTypeData);
});
collector.outputClassLists.forEach((
OutputUnit _,
List<ClassEntity> classes,
) {
classes.forEach(_buildClass);
});
// Resolve the superclass references after we've processed all the classes.
_classes.forEach((ClassEntity cls, Class c) {
final superclass = _elementEnvironment.getSuperClass(cls);
if (superclass != null) {
c.superclass = _classes[superclass];
assert(
c.onlyForConstructor || c.superclass != null,
failedAt(
cls,
"No Class for has been created for superclass "
"$superclass of $c.",
),
);
}
if (c.isSimpleMixinApplication || c.isMixinApplicationWithMembers) {
final effectiveMixinClass = _elementEnvironment.getEffectiveMixinClass(
cls,
);
c.mixinClass = _classes[effectiveMixinClass];
assert(
c.mixinClass != null,
failedAt(
cls,
"No class for effective mixin $effectiveMixinClass on "
"$cls.",
),
);
}
});
List<Class> nativeClasses = collector.nativeClassesAndSubclasses
.map((ClassEntity classElement) => _classes[classElement]!)
.toList();
Set<ClassEntity> interceptorClassesNeededByConstants = collector
.computeInterceptorsReferencedFromConstants();
_unneededNativeClasses = _task.nativeEmitter.prepareNativeClasses(
nativeClasses,
interceptorClassesNeededByConstants,
_rtiNeededClasses,
);
_addJsInteropStubs(_registry.mainLibrariesMap);
MainFragment mainFragment = _buildMainFragment(_registry.mainLibrariesMap);
Iterable<Fragment> deferredFragments = _registry.deferredLibrariesMap.map(
_buildDeferredFragment,
);
List<Fragment> fragments = [
mainFragment,
...deferredFragments,
].toList(growable: false);
_markEagerClasses();
associateNamedTypeVariables();
bool needsNativeSupport =
_nativeCodegenEnqueuer.hasInstantiatedNativeClasses ||
_nativeData.isAllowInteropUsed;
assert(
!needsNativeSupport ||
nativeClasses.isNotEmpty ||
_nativeData.isAllowInteropUsed,
);
List<js.TokenFinalizer> finalizers = [_task.metadataCollector];
if (_namer is js.TokenFinalizer) {
var namingFinalizer = _namer;
finalizers.add(namingFinalizer as js.TokenFinalizer);
}
return Program(
fragments,
_buildTypeToInterceptorMap(),
_task.metadataCollector,
finalizers,
needsNativeSupport: needsNativeSupport,
outputContainsConstantList: collector.outputContainsConstantList,
);
}
void _markEagerClasses() {
_markEagerInterceptorClasses();
}
js.Expression? _buildTypeToInterceptorMap() {
InterceptorStubGenerator stubGenerator = InterceptorStubGenerator(
_commonElements,
_task.emitter,
_nativeCodegenEnqueuer,
_namer,
_customElementsCodegenAnalysis,
_codegenWorld,
_closedWorld,
);
return stubGenerator.generateTypeToInterceptorMap();
}
MainFragment _buildMainFragment(LibrariesMap librariesMap) {
final outputUnit = librariesMap.outputUnit;
// Construct the main output from the libraries and the registered holders.
MainFragment result = MainFragment(
outputUnit,
"", // The empty string is the name for the main output file.
_buildInvokeMain(),
_buildMainUnitRecordTypeStubs(outputUnit),
_buildLibraries(librariesMap),
_buildStaticNonFinalFields(librariesMap),
_buildStaticLazilyInitializedFields(librariesMap),
_buildConstants(librariesMap),
);
_outputs[outputUnit] = result;
return result;
}
js.Statement _buildInvokeMain() {
return MainCallStubGenerator.generateInvokeMain(
_commonElements,
_task.emitter,
_mainFunction,
_backendUsage.requiresStartupMetrics,
_options,
);
}
js.Expression? _buildMainUnitRecordTypeStubs(OutputUnit mainOutputUnit) {
return _recordsCodegen.generateTestTableForOutputUnit(
mainOutputUnit,
_outputUnitData,
_namer,
);
}
DeferredFragment _buildDeferredFragment(LibrariesMap librariesMap) {
final outputUnit = librariesMap.outputUnit;
final name = librariesMap.name;
DeferredFragment result = DeferredFragment(
outputUnit,
deferredPartFileName(_options, name, addExtension: false),
name,
_buildLibraries(librariesMap),
_buildStaticNonFinalFields(librariesMap),
_buildStaticLazilyInitializedFields(librariesMap),
_buildConstants(librariesMap),
);
_outputs[outputUnit] = result;
return result;
}
List<Constant> _buildConstants(LibrariesMap librariesMap) {
final constantValues =
collector.outputConstantLists[librariesMap.outputUnit];
if (constantValues == null) return const [];
return constantValues
.map((ConstantValue value) => _constants[value]!)
.toList(growable: false);
}
List<StaticField> _buildStaticNonFinalFields(LibrariesMap librariesMap) {
final staticNonFinalFields =
collector.outputStaticNonFinalFieldLists[librariesMap.outputUnit];
if (staticNonFinalFields == null) return const [];
return staticNonFinalFields.map(_buildStaticField).toList(growable: false);
}
StaticField _buildStaticField(FieldEntity element) {
FieldAnalysisData fieldData = _fieldAnalysis.getFieldData(
element as JField,
);
final initialValue = fieldData.initialValue;
late js.Expression code;
if (initialValue != null) {
code = _task.emitter.constantReference(initialValue);
} else {
assert(fieldData.isEager);
code = _generatedCode[element]!;
}
js.Name name = _namer.globalPropertyNameForMember(element);
// TODO(floitsch): we shouldn't update the registry in the middle of
// building a static field. (Note that the static-state holder was
// already registered earlier, and that we just call the register to get
// the holder-instance.
return StaticField(
element,
name,
null,
code,
isFinal: false,
isLazy: false,
isInitializedByConstant: initialValue != null,
);
}
List<StaticField> _buildStaticLazilyInitializedFields(
LibrariesMap librariesMap,
) {
final lazyFields =
collector.outputLazyStaticFieldLists[librariesMap.outputUnit];
if (lazyFields == null) return const [];
return lazyFields
.map(_buildLazyField)
.whereType<StaticField>() // Happens when the field was unused.
.toList(growable: false);
}
StaticField? _buildLazyField(FieldEntity element) {
final code = _generatedCode[element];
// The code is null if we ended up not needing the lazily
// initialized field after all because of constant folding
// before code generation.
if (code == null) return null;
js.Name name = _namer.globalPropertyNameForMember(element);
js.Name getterName = _namer.lazyInitializerName(element);
// TODO(floitsch): we shouldn't update the registry in the middle of
// building a static field. (Note that the static-state holder was
// already registered earlier, and that we just call the register to get
// the holder-instance.
return StaticField(
element,
name,
getterName,
code,
isFinal: !element.isAssignable,
isLazy: true,
);
}
List<Library> _buildLibraries(LibrariesMap librariesMap) {
return librariesMap.entries
.map((entry) {
final contents = entry.value;
return _buildLibrary(
entry.key,
contents.classes,
contents.members,
contents.classTypes,
);
})
.toList(growable: false);
}
void _addJsInteropStubs(LibrariesMap librariesMap) {
if (_classes.containsKey(_commonElements.objectClass)) {
js.Name toStringInvocation = _namer.invocationName(Selectors.toString_);
// TODO(jacobr): register toString as used so that it is always accessible
// from JavaScript.
_classes[_commonElements.objectClass]!.callStubs.add(
_buildStubMethod(
StringBackedName("toString"),
js.js('function() { return this.#(this) }', toStringInvocation),
),
);
}
// We add all members from classes marked with isJsInterop to the base
// Interceptor class with implementations that directly call the
// corresponding JavaScript member. We do not attempt to bind this when
// tearing off JavaScript methods as we cannot distinguish between calling
// a regular getter that returns a JavaScript function and tearing off
// a method in the case where there exist multiple JavaScript classes
// that conflict on whether the member is a getter or a method.
final interceptorClass = _classes[_jsInteropInterceptor];
final interceptorTypeData = _classTypeData[_jsInteropInterceptor];
interceptorClass?.isChecks.addAll(_jsInteropIsChecks);
interceptorTypeData?.classChecks.addAll(_jsInteropTypeChecks);
late final interopNullAssert = _task.emitter.staticFunctionAccess(
_commonElements.interopNullAssertion,
);
Set<String> stubNames = {};
librariesMap.forEach((
LibraryEntity library,
List<ClassEntity> classElements,
memberElement,
typeElement,
) {
for (ClassEntity cls in classElements) {
if (_nativeData.isJsInteropClass(cls)) {
_elementEnvironment.forEachLocalClassMember(cls, (
MemberEntity member,
) {
String jsName = _nativeData.computeUnescapedJSInteropName(
member.name!,
);
if (!member.isInstanceMember) return;
if (member.isGetter || member is FieldEntity || member.isFunction) {
final selectors = _codegenWorld.getterInvocationsByName(
member.name!,
);
if (selectors != null && selectors.isNotEmpty) {
for (Selector selector in selectors) {
js.Name stubName = _namer.invocationName(selector);
if (stubNames.add(stubName.key)) {
final code =
_options.interopNullAssertions &&
_nativeData.interopNullChecks[selector] ==
InteropNullCheckKind.calleeCheck
? js.js('function(obj) { return #(obj.#) }', [
interopNullAssert,
jsName,
])
: js.js('function(obj) { return obj.# }', [jsName]);
interceptorClass!.callStubs.add(
_buildStubMethod(stubName, code, element: member),
);
}
}
}
}
if (member.isSetter || (member is FieldEntity && !member.isConst)) {
final selectors = _codegenWorld.setterInvocationsByName(
member.name!,
);
if (selectors != null && selectors.isNotEmpty) {
var stubName = _namer.setterForMember(member);
if (stubNames.add(stubName.key)) {
interceptorClass!.callStubs.add(
_buildStubMethod(
stubName,
js.js('function(obj, v) { return obj.# = v }', [jsName]),
element: member,
),
);
}
}
}
// Generating stubs for direct calls and stubs for call-through
// of getters that happen to be functions.
bool isFunctionLike = false;
FunctionType? functionType;
if (member.isFunction) {
final fn = member as FunctionEntity;
functionType = _elementEnvironment.getFunctionType(fn);
} else if (member.isGetter) {
isFunctionLike = true;
} // TODO(jacobr): handle field elements.
if (isFunctionLike || functionType != null) {
int minArgs;
int maxArgs;
if (functionType != null) {
minArgs = functionType.parameterTypes.length;
maxArgs = minArgs + functionType.optionalParameterTypes.length;
} else {
minArgs = 0;
maxArgs = 32767;
}
var selectors = _codegenWorld.invocationsByName(member.name!);
// Named arguments are not yet supported. In the future we
// may want to map named arguments to an object literal containing
// all named arguments.
if (selectors != null && selectors.isNotEmpty) {
for (var selector in selectors.keys) {
// Check whether the arity matches this member.
var argumentCount = selector.argumentCount;
// JS interop does not support named arguments.
if (selector.namedArgumentCount > 0) continue;
if (argumentCount < minArgs) continue;
if (argumentCount > maxArgs) continue;
var stubName = _namer.invocationName(selector);
if (!stubNames.add(stubName.key)) continue;
var parameters = List<String>.generate(
argumentCount,
(i) => 'p$i',
);
// We intentionally generate the same stub method for direct
// calls and call-throughs of getters so that calling a
// getter that returns a function behaves the same as calling
// a method. This is helpful as many typed JavaScript APIs
// specify member functions with getters that return
// functions. The behavior of this solution matches JavaScript
// behavior implicitly binding this only when JavaScript
// would.
final code =
_options.interopNullAssertions &&
_nativeData.interopNullChecks[selector] ==
InteropNullCheckKind.calleeCheck
? js.js(
'function(receiver, #) { return #(receiver.#(#)) }',
[parameters, interopNullAssert, jsName, parameters],
)
: js.js(
'function(receiver, #) { return receiver.#(#) }',
[parameters, jsName, parameters],
);
interceptorClass!.callStubs.add(
_buildStubMethod(stubName, code, element: member),
);
}
}
}
});
}
}
});
}
// Note that a library-element may have multiple [Library]s, if it is split
// into multiple output units.
Library _buildLibrary(
LibraryEntity library,
List<ClassEntity> classElements,
List<MemberEntity> memberElements,
List<ClassEntity> classTypeElements,
) {
String uri = library.canonicalUri.toString();
List<StaticMethod> statics = memberElements
// We omit static stubs here because we use the function bodies directly
// when we install the tear offs.
.where((e) => e is! FieldEntity && e is! JParameterStub)
.cast<FunctionEntity>()
.map<StaticMethod>(_buildStaticMethod)
.toList();
if (library == _commonElements.interceptorsLibrary) {
statics.addAll(_generateGetInterceptorMethods());
statics.addAll(_generateOneShotInterceptors());
}
List<Class> classes = classElements
.map((ClassEntity classElement) => _classes[classElement]!)
.where(
(Class cls) => !cls.isNative || !_unneededNativeClasses.contains(cls),
)
.toList(growable: false);
List<ClassTypeData> classTypeData = classTypeElements
.map(
(ClassEntity classTypeElement) => _classTypeData[classTypeElement]!,
)
.toList();
classTypeData.addAll(classes.map((Class cls) => cls.typeData).toList());
return Library(library, uri, statics, classes, classTypeData);
}
Class _buildClass(ClassEntity cls) {
ClassTypeData typeData = _buildClassTypeData(cls);
bool onlyForConstructor = collector.classesOnlyNeededForConstructor
.contains(cls);
// TODO(joshualitt): Can we just emit JSInteropClasses as types?
// TODO(jacobr): check whether the class has any active static fields
// if it does not we can suppress it completely.
bool onlyForRti = _nativeData.isJsInteropClass(cls);
bool hasRtiField = _rtiNeed.classNeedsTypeArguments(cls);
bool onlyForConstructorOrRti = onlyForConstructor || onlyForRti;
// Recognize the specialized base classes for closures.
bool isClosureBaseClass = cls == _commonElements.closureClass;
int? sharedClosureApplyMetadata;
if (cls == _commonElements.closureClass) {
// The root base class has metadata for single-argument closures.
sharedClosureApplyMetadata = 1;
} else if (cls == _commonElements.closureClass0Args) {
sharedClosureApplyMetadata = 0;
} else if (cls == _commonElements.closureClass2Args) {
sharedClosureApplyMetadata = 2;
}
int? recordShapeTag;
js.Expression? recordShapeRecipe;
RecordRepresentation? record = _recordData.representationForClass(cls);
if (record != null && record.definesShape) {
recordShapeTag = record.shapeTag;
recordShapeRecipe = _rtiRecipeEncoder.encodeRecordFromBindingRecipe(
record.shape,
);
}
List<Method> methods = [];
List<StubMethod> callStubs = [];
ClassStubGenerator classStubGenerator = ClassStubGenerator(
_task.emitter,
_commonElements,
_namer,
_codegenWorld,
_closedWorld,
enableMinification: _options.enableMinification,
);
RuntimeTypeGenerator runtimeTypeGenerator = RuntimeTypeGenerator(
_commonElements,
_outputUnitData,
_task,
_namer,
_rtiChecks,
);
void visitInstanceMember(MemberEntity member) {
if (!member.isAbstract && member is! FieldEntity) {
if (member is! JSignatureMethod) {
final method = _buildMethod(member as FunctionEntity);
if (method != null) methods.add(method);
}
}
if (member.isGetter || member is FieldEntity) {
Map<Selector, SelectorConstraints>? selectors = _codegenWorld
.invocationsByName(member.name!);
if (selectors != null && selectors.isNotEmpty) {
Map<js.Name, js.Expression> callStubsForMember = classStubGenerator
.generateCallStubsForGetter(member, selectors);
callStubsForMember.forEach((js.Name name, js.Expression code) {
callStubs.add(_buildStubMethod(name, code, element: member));
});
}
}
}
void visitMember(MemberEntity member) {
if (member.isInstanceMember) {
visitInstanceMember(member);
}
}
List<StubMethod> noSuchMethodStubs = [];
if (_backendUsage.isNoSuchMethodUsed &&
cls == _commonElements.objectClass) {
Map<js.Name, Selector> selectors = classStubGenerator
.computeSelectorsForNsmHandlers();
selectors.forEach((js.Name name, Selector selector) {
// If the program contains `const Symbol` names we have to retain them.
String selectorName = selector.name;
if (selector.isSetter) selectorName = "$selectorName=";
noSuchMethodStubs.add(
classStubGenerator.generateStubForNoSuchMethod(name, selector),
);
});
}
if (isClosureBaseClass) {
// We add a special getter to allow for tearing off a closure from itself.
js.Name name = _namer.getterForMember(Names.call);
final function = js.js('function() { return this; }') as js.Fun;
callStubs.add(_buildStubMethod(name, function));
}
if (_commonElements.isInstantiationClass(cls) && !onlyForConstructorOrRti) {
callStubs.addAll(_generateInstantiationStubs(cls));
}
// MixinApplications run through the members of their mixin. Here, we are
// only interested in direct members.
bool isMixinApplicationWithMembers = false;
if (!onlyForConstructorOrRti) {
if (_elementEnvironment.isMixinApplicationWithMembers(cls)) {
List<MemberEntity> members = [];
void add(MemberEntity member) {
if (member.enclosingClass == cls) {
members.add(member);
isMixinApplicationWithMembers = true;
}
}
_elementEnvironment.forEachLocalClassMember(cls, add);
_elementEnvironment.forEachInjectedClassMember(cls, add);
if (members.isNotEmpty) {
_sorter.sortMembers(members).forEach(visitMember);
}
} else if (!_elementEnvironment.isMixinApplication(cls)) {
List<MemberEntity> members = [];
_elementEnvironment.forEachLocalClassMember(cls, members.add);
_elementEnvironment.forEachInjectedClassMember(cls, members.add);
_elementEnvironment.forEachConstructorBody(cls, (body) {
if (_codegenWorld.isLateMemberReachable(body)) members.add(body);
});
_sorter.sortMembers(members).forEach(visitMember);
}
}
bool isInterceptedClass = _interceptorData.isInterceptedClass(cls);
List<Field> instanceFields = onlyForConstructorOrRti
? const []
: _buildFields(cls: cls, isHolderInterceptedClass: isInterceptedClass);
List<StubMethod> gettersSetters = onlyForConstructorOrRti
? const []
: [
for (Field field in instanceFields)
if (field.needsGetter)
classStubGenerator.generateGetter(field) as StubMethod,
for (Field field in instanceFields)
if (field.needsUncheckedSetter)
classStubGenerator.generateSetter(field) as StubMethod,
];
TypeTestProperties typeTests = runtimeTypeGenerator.generateIsTests(
cls,
_generatedCode,
storeFunctionTypeInMetadata: _storeFunctionTypesInMetadata,
);
List<StubMethod> checkedSetters = [];
List<StubMethod> isChecks = [];
if (_nativeData.isJsInteropClass(cls)) {
// TODO(johnniwinther): Instead of generating all stubs for each
// js-interop class we should generate a stub for each implemented class.
// Currently we generate duplicates if a class is implemented by multiple
// js-interop classes.
typeTests.forEachProperty(_sorter, (js.Name name, js.Node code) {
_jsInteropIsChecks.add(_buildStubMethod(name, code as js.Expression));
});
_jsInteropTypeChecks.addAll(typeData.classChecks.checks);
} else {
for (Field field in instanceFields) {
if (field.needsCheckedSetter) {
assert(!field.needsUncheckedSetter);
FieldEntity element = field.element;
final code = _generatedCode[element] as js.Expression;
js.Name name = _namer.deriveSetterName(field.accessorName);
checkedSetters.add(_buildStubMethod(name, code, element: element));
}
}
typeTests.forEachProperty(_sorter, (js.Name name, js.Node code) {
isChecks.add(_buildStubMethod(name, code as js.Expression));
});
}
js.Name name = _namer.className(cls);
bool isInstantiated =
!_nativeData.isJsInteropClass(cls) &&
_codegenWorld.directlyInstantiatedClasses.contains(cls);
Class result;
if (_elementEnvironment.isMixinApplication(cls) &&
!onlyForConstructorOrRti &&
!isMixinApplicationWithMembers) {
assert(!_nativeData.isNativeClass(cls));
assert(methods.isEmpty);
assert(!isClosureBaseClass);
assert(sharedClosureApplyMetadata == null);
assert(recordShapeTag == null);
assert(recordShapeRecipe == null);
result = MixinApplication(
cls,
typeData,
name,
instanceFields,
callStubs,
checkedSetters,
gettersSetters,
isChecks,
typeTests.functionTypeIndex,
isDirectlyInstantiated: isInstantiated,
hasRtiField: hasRtiField,
onlyForRti: onlyForRti,
onlyForConstructor: onlyForConstructor,
);
} else {
result = Class(
cls,
typeData,
name,
methods,
instanceFields,
callStubs,
noSuchMethodStubs,
checkedSetters,
gettersSetters,
isChecks,
typeTests.functionTypeIndex,
isDirectlyInstantiated: isInstantiated,
hasRtiField: hasRtiField,
onlyForRti: onlyForRti,
onlyForConstructor: onlyForConstructor,
isNative: _nativeData.isNativeClass(cls),
isClosureBaseClass: isClosureBaseClass,
sharedClosureApplyMetadata: sharedClosureApplyMetadata,
isMixinApplicationWithMembers: isMixinApplicationWithMembers,
recordShapeTag: recordShapeTag,
recordShapeRecipe: recordShapeRecipe,
);
}
_classes[cls] = result;
return result;
}
ClassTypeData _buildClassTypeData(ClassEntity cls) =>
_classTypeData.putIfAbsent(
cls,
() => ClassTypeData(cls, _rtiChecks.requiredChecks[cls]),
);
void associateNamedTypeVariables() {
for (TypeVariableType typeVariable
in _codegenWorld.namedTypeVariables.union(_lateNamedTypeVariables)) {
final declaration = typeVariable.element.typeDeclaration as ClassEntity;
Iterable<ClassEntity> subtypes =
new_rti.mustCheckAllSubtypes(_closedWorld, declaration)
? _classHierarchy.subtypesOf(declaration)
: _classHierarchy.subclassesOf(declaration);
for (ClassEntity entity in subtypes) {
ClassTypeData classTypeData = _nativeData.isJsInteropClass(entity)
? _buildClassTypeData(_jsInteropInterceptor)
: _buildClassTypeData(entity);
classTypeData.namedTypeVariables.add(typeVariable);
}
}
}
bool _methodCanBeApplied(FunctionEntity method) {
return _backendUsage.isFunctionApplyUsed &&
_inferredData.getMightBePassedToApply(method);
}
Object? /* Map | List */ _computeParameterDefaultValues(
FunctionEntity method,
) {
Object? /* Map | List */ optionalParameterDefaultValues;
ParameterStructure parameterStructure = method.parameterStructure;
if (parameterStructure.namedParameters.isNotEmpty) {
final defaults = <String, ConstantValue>{};
_elementEnvironment.forEachParameter(method, (
DartType type,
String? name,
ConstantValue? defaultValue,
) {
if (parameterStructure.namedParameters.contains(name)) {
defaults[name!] = defaultValue!;
}
});
optionalParameterDefaultValues = defaults;
} else {
final defaults = <ConstantValue>[];
int index = 0;
_elementEnvironment.forEachParameter(method, (
DartType type,
String? name,
ConstantValue? defaultValue,
) {
if (index >= parameterStructure.requiredPositionalParameters) {
defaults.add(defaultValue!);
}
index++;
});
optionalParameterDefaultValues = defaults;
}
return optionalParameterDefaultValues;
}
DartMethod? _buildMethod(FunctionEntity element) {
js.Name name = _namer.methodPropertyName(element);
final code = _generatedCode[element];
// TODO(kasperl): Figure out under which conditions code is null.
if (code == null) return null;
bool canTearOff = false;
bool tearOffNeedsDirectAccess = false;
js.Name? tearOffName;
bool isClosureCallMethod = false;
bool inheritsApplyMetadata = false;
bool isNotApplyTarget =
!element.isFunction || element.isGetter || element.isSetter;
bool canBeApplied = _methodCanBeApplied(element);
final aliasName = _codegenWorld.isAliasedSuperMember(element)
? _namer.aliasedSuperMemberPropertyName(element)
: null;
if (isNotApplyTarget) {
canTearOff = false;
} else {
if (element.enclosingClass!.isClosure) {
canTearOff = false;
isClosureCallMethod = true;
final superclass = _elementEnvironment.getSuperClass(
element.enclosingClass!,
);
if (superclass == _commonElements.closureClass &&
element.parameterStructure == ParameterStructure.oneArgument ||
superclass == _commonElements.closureClass0Args &&
element.parameterStructure ==
ParameterStructure.zeroArguments ||
superclass == _commonElements.closureClass2Args &&
element.parameterStructure == ParameterStructure.twoArguments) {
inheritsApplyMetadata = true;
}
} else {
// Careful with operators.
bool needsSuperGetter = _codegenWorld.methodsNeedsSuperGetter(element);
canTearOff =
_codegenWorld.hasInvokedGetter(element) || needsSuperGetter;
tearOffName = _namer.getterForElement(element);
tearOffNeedsDirectAccess = needsSuperGetter;
}
}
if (canTearOff) {
assert(element is! ConstructorEntity, failedAt(element));
assert(element is! ConstructorBodyEntity, failedAt(element));
}
bool isIntercepted = _closedWorld.interceptorData.isInterceptedMethod(
element,
);
js.Name? callName;
if (canTearOff) {
Selector callSelector = Selector.fromElement(element).toCallSelector();
callName = _namer.invocationName(callSelector);
}
final memberType = _elementEnvironment.getFunctionType(element);
js.Expression? functionType;
if (canTearOff) {
OutputUnit outputUnit = _outputUnitData.outputUnitForMember(element);
functionType = _generateFunctionType(
element.enclosingClass!,
memberType,
outputUnit,
);
}
FunctionEntity method = element;
ParameterStructure parameterStructure = method.parameterStructure;
int requiredParameterCount =
parameterStructure.requiredPositionalParameters;
Object? /* List | Map */ optionalParameterDefaultValues;
int applyIndex = 0;
if (canBeApplied) {
optionalParameterDefaultValues = _computeParameterDefaultValues(method);
if (parameterStructure.typeParameters > 0) {
applyIndex = 1;
}
}
return InstanceMethod(
element,
name,
code,
_stubsForMethod(method),
callName,
needsTearOff: canTearOff,
tearOffName: tearOffName,
tearOffNeedsDirectAccess: tearOffNeedsDirectAccess,
isClosureCallMethod: isClosureCallMethod,
inheritsApplyMetadata: inheritsApplyMetadata,
isIntercepted: isIntercepted,
aliasName: aliasName,
canBeApplied: canBeApplied,
requiredParameterCount: requiredParameterCount,
optionalParameterDefaultValues: optionalParameterDefaultValues,
functionType: functionType,
applyIndex: applyIndex,
);
}
js.Expression _generateFunctionType(
ClassEntity? enclosingClass,
FunctionType type,
OutputUnit outputUnit,
) {
InterfaceType? enclosingType;
if (enclosingClass != null && type.containsTypeVariables) {
enclosingType = _elementEnvironment.getThisType(enclosingClass);
if (!_rtiNeed.classNeedsTypeArguments(enclosingClass)) {
// Erase type arguments.
List<DartType> typeArguments = enclosingType.typeArguments;
type =
_dartTypes.subst(
List<DartType>.filled(
typeArguments.length,
_dartTypes.erasedType(),
),
typeArguments,
type,
)
as FunctionType;
}
}
if (type.containsTypeVariables) {
RecipeEncoding encoding = _rtiRecipeEncoder.encodeRecipe(
_task.emitter,
FullTypeEnvironmentStructure(classType: enclosingType),
TypeExpressionRecipe(type),
);
_lateNamedTypeVariables.addAll(encoding.typeVariables);
return encoding.recipe;
} else {
return _task.metadataCollector.reifyType(type, outputUnit);
}
}
List<StubMethod> _generateInstantiationStubs(ClassEntity instantiationClass) {
InstantiationStubGenerator generator = InstantiationStubGenerator(
_task,
_namer,
_closedWorld,
_codegenWorld,
_sourceInformationStrategy,
);
return generator.generateStubs(instantiationClass, null);
}
/// Builds a stub method.
///
/// Stub methods may have an element that can be used for code-size
/// attribution.
StubMethod _buildStubMethod(
js.Name name,
js.Expression code, {
MemberEntity? element,
}) {
return StubMethod(name, code, element: element);
}
// The getInterceptor methods directly access the prototype of classes.
// We must evaluate these classes eagerly so that the prototype is
// accessible.
void _markEagerInterceptorClasses() {
Iterable<SpecializedGetInterceptor> interceptors =
_oneShotInterceptorData.specializedGetInterceptors;
for (SpecializedGetInterceptor interceptor in interceptors) {
for (ClassEntity element in interceptor.classes) {
final cls = _classes[element];
if (cls != null) cls.isEager = true;
}
}
}
Iterable<StaticStubMethod> _generateGetInterceptorMethods() {
InterceptorStubGenerator stubGenerator = InterceptorStubGenerator(
_commonElements,
_task.emitter,
_nativeCodegenEnqueuer,
_namer,
_customElementsCodegenAnalysis,
_codegenWorld,
_closedWorld,
);
List<js.Name> names = [];
Map<js.Name, SpecializedGetInterceptor> interceptorMap = {};
for (SpecializedGetInterceptor interceptor
in _oneShotInterceptorData.specializedGetInterceptors) {
js.Name name = _namer.nameForGetInterceptor(interceptor.classes);
names.add(name);
assert(
!interceptorMap.containsKey(name),
"Duplicate specialized get interceptor for $name: Existing: "
"${interceptorMap[name]}, new $interceptor.",
);
interceptorMap[name] = interceptor;
}
names.sort(compareNames);
return names.map((js.Name name) {
final interceptor = interceptorMap[name]!;
js.Expression code = stubGenerator.generateGetInterceptorMethod(
interceptor,
);
return StaticStubMethod(_commonElements.interceptorsLibrary!, name, code);
});
}
List<Field> _buildFields({
bool isHolderInterceptedClass = false,
required ClassEntity cls,
}) {
List<Field> fields = [];
void visitField(
FieldEntity field,
bool needsGetter,
bool needsSetter,
bool needsCheckedSetter,
) {
int getterFlags = 0;
if (needsGetter) {
if (!_interceptorData.fieldHasInterceptedGetter(field)) {
getterFlags = 1;
} else {
getterFlags += 2;
// TODO(sra): 'isInterceptedClass' might not be the correct test
// for methods forced to use the interceptor convention because
// the method's class was elsewhere mixed-in to an interceptor.
if (!isHolderInterceptedClass) {
getterFlags += 1;
}
}
}
int setterFlags = 0;
if (needsSetter) {
if (!_interceptorData.fieldHasInterceptedSetter(field)) {
setterFlags = 1;
} else {
setterFlags += 2;
if (!isHolderInterceptedClass) {
setterFlags += 1;
}
}
}
FieldAnalysisData fieldData = _fieldAnalysis.getFieldData(
field as JField,
);
ConstantValue? initializerInAllocator;
if (fieldData.isInitializedInAllocator) {
initializerInAllocator = fieldData.initialValue;
}
ConstantValue? constantValue;
if (fieldData.isEffectivelyConstant) {
constantValue = fieldData.constantValue;
}
js.Name name = _namer.instanceFieldPropertyName(field);
js.Name accessorName = _namer.fieldAccessorName(field);
fields.add(
Field(
field,
name,
accessorName,
getterFlags,
setterFlags,
needsCheckedSetter,
initializerInAllocator,
constantValue,
fieldData.isElided,
),
);
}
FieldVisitor visitor = FieldVisitor(
_elementEnvironment,
_codegenWorld,
_nativeData,
_closedWorld,
);
visitor.visitFields(visitField, cls);
return fields;
}
Iterable<StaticStubMethod> _generateOneShotInterceptors() {
InterceptorStubGenerator stubGenerator = InterceptorStubGenerator(
_commonElements,
_task.emitter,
_nativeCodegenEnqueuer,
_namer,
_customElementsCodegenAnalysis,
_codegenWorld,
_closedWorld,
);
List<js.Name> names = [];
Map<js.Name, OneShotInterceptor> interceptorMap = {};
for (OneShotInterceptor interceptor
in _oneShotInterceptorData.oneShotInterceptors) {
js.Name name = _namer.nameForOneShotInterceptor(
interceptor.selector,
interceptor.classes,
);
names.add(name);
assert(
!interceptorMap.containsKey(name),
"Duplicate specialized get interceptor for $name: Existing: "
"${interceptorMap[name]}, new $interceptor.",
);
interceptorMap[name] = interceptor;
}
names.sort(compareNames);
return names.map((js.Name name) {
final interceptor = interceptorMap[name]!;
js.Expression code = stubGenerator.generateOneShotInterceptor(
interceptor,
);
return StaticStubMethod(_commonElements.interceptorsLibrary!, name, code);
});
}
StaticDartMethod _buildStaticMethod(FunctionEntity element) {
js.Name name = _namer.methodPropertyName(element);
js.Expression code = _generatedCode[element]!;
bool isApplyTarget =
element is! ConstructorEntity && !element.isGetter && !element.isSetter;
bool canBeApplied = _methodCanBeApplied(element);
bool needsTearOff =
isApplyTarget && _codegenWorld.closurizedStatics.contains(element);
final tearOffName = needsTearOff ? _namer.staticClosureName(element) : null;
js.Name? callName;
if (needsTearOff) {
Selector callSelector = Selector.fromElement(element).toCallSelector();
callName = _namer.invocationName(callSelector);
}
js.Expression? functionType;
final type = _elementEnvironment.getFunctionType(element);
if (needsTearOff) {
OutputUnit outputUnit = _outputUnitData.outputUnitForMember(element);
functionType = _generateFunctionType(null, type, outputUnit);
}
FunctionEntity method = element;
ParameterStructure parameterStructure = method.parameterStructure;
int requiredParameterCount =
parameterStructure.requiredPositionalParameters;
Object? /* List | Map */ optionalParameterDefaultValues;
int applyIndex = 0;
if (canBeApplied) {
optionalParameterDefaultValues = _computeParameterDefaultValues(method);
if (parameterStructure.typeParameters > 0) {
applyIndex = 1;
}
}
return StaticDartMethod(
element,
name,
code,
_stubsForMethod(method),
callName,
needsTearOff: needsTearOff,
tearOffName: tearOffName,
canBeApplied: canBeApplied,
requiredParameterCount: requiredParameterCount,
optionalParameterDefaultValues: optionalParameterDefaultValues,
functionType: functionType,
applyIndex: applyIndex,
);
}
List<ParameterStubMethod> _stubsForMethod(FunctionEntity element) {
final stubMethods = _codegenWorld
.getParameterStubs(element)
.map((stub) {
final name = element.isStatic
? null
: _namer.instanceMethodName(stub);
final callSelector = stub.callSelector;
final callName = (callSelector != null)
? _namer.invocationName(callSelector)
: null;
final stubCode = _generatedCode[stub]!;
return ParameterStubMethod(
name,
callName,
stubCode,
element: element,
);
})
.toList(growable: false);
return stubMethods.isEmpty ? const [] : stubMethods;
}
void _registerConstants(
OutputUnit outputUnit,
Iterable<ConstantValue>? constantValues,
) {
// `constantValues` is null if an outputUnit doesn't contain any constants.
if (constantValues == null) return;
for (ConstantValue constantValue in constantValues) {
_registry.registerConstant(outputUnit, constantValue);
assert(!_constants.containsKey(constantValue));
js.Name name = _namer.constantName(constantValue);
Constant constant = Constant(name, constantValue);
_constants[constantValue] = constant;
}
}
}