blob: e0ba1569c6f0331deb493f4d982acd3aa55036b9 [file] [log] [blame]
// Copyright (c) 2012, 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.
part of js_backend;
typedef void Recompile(Element element);
class ReturnInfo {
HType returnType;
List<Element> compiledFunctions;
ReturnInfo(HType this.returnType)
: compiledFunctions = new List<Element>();
ReturnInfo.unknownType() : this(null);
void update(HType type, Recompile recompile, Compiler compiler) {
HType newType =
returnType != null ? returnType.union(type, compiler) : type;
if (newType != returnType) {
if (returnType == null && identical(newType, HType.UNKNOWN)) {
// If the first actual piece of information is not providing any type
// information there is no need to recompile callers.
compiledFunctions.clear();
}
returnType = newType;
if (recompile != null) {
compiledFunctions.forEach(recompile);
}
compiledFunctions.clear();
}
}
// Note that lazy initializers are treated like functions (but are not
// of type [FunctionElement].
addCompiledFunction(Element function) => compiledFunctions.add(function);
}
class OptionalParameterTypes {
final List<SourceString> names;
final List<HType> types;
OptionalParameterTypes(int optionalArgumentsCount)
: names = new List<SourceString>.fixedLength(optionalArgumentsCount),
types = new List<HType>.fixedLength(optionalArgumentsCount);
int get length => names.length;
SourceString name(int index) => names[index];
HType type(int index) => types[index];
int indexOf(SourceString name) => names.indexOf(name);
HType typeFor(SourceString name) {
int index = indexOf(name);
if (index == -1) return null;
return type(index);
}
void update(int index, SourceString name, HType type) {
names[index] = name;
types[index] = type;
}
String toString() => "OptionalParameterTypes($names, $types)";
}
class HTypeList {
final List<HType> types;
final List<SourceString> namedArguments;
HTypeList(int length)
: types = new List<HType>.fixedLength(length),
namedArguments = null;
HTypeList.withNamedArguments(int length, this.namedArguments)
: types = new List<HType>.fixedLength(length);
const HTypeList.withAllUnknown()
: types = null,
namedArguments = null;
factory HTypeList.fromStaticInvocation(HInvokeStatic node, HTypeMap types) {
bool allUnknown = true;
for (int i = 1; i < node.inputs.length; i++) {
if (types[node.inputs[i]] != HType.UNKNOWN) {
allUnknown = false;
break;
}
}
if (allUnknown) return HTypeList.ALL_UNKNOWN;
HTypeList result = new HTypeList(node.inputs.length - 1);
for (int i = 0; i < result.types.length; i++) {
result.types[i] = types[node.inputs[i + 1]];
}
return result;
}
factory HTypeList.fromDynamicInvocation(HInvokeDynamic node,
Selector selector,
HTypeMap types) {
HTypeList result;
int argumentsCount = node.inputs.length - 1;
int startInvokeIndex = HInvoke.ARGUMENTS_OFFSET;
if (node.isInterceptorCall) {
argumentsCount--;
startInvokeIndex++;
}
if (selector.namedArgumentCount > 0) {
result =
new HTypeList.withNamedArguments(
argumentsCount, selector.namedArguments);
} else {
result = new HTypeList(argumentsCount);
}
for (int i = 0; i < result.types.length; i++) {
result.types[i] = types[node.inputs[i + startInvokeIndex]];
}
return result;
}
static const HTypeList ALL_UNKNOWN = const HTypeList.withAllUnknown();
bool get allUnknown => types == null;
bool get hasNamedArguments => namedArguments != null;
int get length => types.length;
HType operator[](int index) => types[index];
void operator[]=(int index, HType type) { types[index] = type; }
HTypeList union(HTypeList other, Compiler compiler) {
if (allUnknown) return this;
if (other.allUnknown) return other;
if (length != other.length) return HTypeList.ALL_UNKNOWN;
bool onlyUnknown = true;
HTypeList result = this;
for (int i = 0; i < length; i++) {
HType newType = this[i].union(other[i], compiler);
if (result == this && newType != this[i]) {
// Create a new argument types object with the matching types copied.
result = new HTypeList(length);
result.types.setRange(0, i, this.types);
}
if (result != this) {
result.types[i] = newType;
}
if (result[i] != HType.UNKNOWN) onlyUnknown = false;
}
return onlyUnknown ? HTypeList.ALL_UNKNOWN : result;
}
HTypeList unionWithOptionalParameters(
Selector selector,
FunctionSignature signature,
OptionalParameterTypes defaultValueTypes) {
assert(allUnknown || selector.argumentCount == this.length);
// Create a new HTypeList for holding types for all parameters.
HTypeList result = new HTypeList(signature.parameterCount);
// First fill in the type of the positional arguments.
int nextTypeIndex = -1;
if (allUnknown) {
for (int i = 0; i < selector.positionalArgumentCount; i++) {
result.types[i] = HType.UNKNOWN;
}
} else {
result.types.setRange(0, selector.positionalArgumentCount, this.types);
nextTypeIndex = selector.positionalArgumentCount;
}
// Next fill the type of the optional arguments.
// As the selector can pass optional arguments positionally some of the
// optional arguments might already have a type set. We only need to look
// at the optional arguments not passed positionally.
// The variable 'index' is counting the signatures optional arguments, the
// variable 'next' is set to the next optional arguments to look at and
// is used to skip some optional arguments.
int next = selector.positionalArgumentCount;
int index = signature.requiredParameterCount;
signature.forEachOptionalParameter((Element element) {
// If some optional parameters were passed positionally these have
// already been filled.
if (index == next) {
assert(result.types[index] == null);
HType type = null;
if (hasNamedArguments &&
selector.namedArguments.indexOf(element.name) >= 0) {
type = types[nextTypeIndex++];
} else {
type = defaultValueTypes.typeFor(element.name);
}
result.types[index] = type;
next++;
}
index++;
});
return result;
}
String toString() =>
allUnknown ? "HTypeList.ALL_UNKNOWN" : "HTypeList $types";
}
class FieldTypesRegistry {
final JavaScriptBackend backend;
/**
* For each class, [constructors] holds the set of constructors. If there is
* more than one constructor for a class it is currently not possible to
* infer the field types from construction, as the information collected does
* not correlate the generative constructors and generative constructor
* body/bodies.
*/
final Map<ClassElement, Set<Element>> constructors;
/**
* The collected type information is stored in three maps. One for types
* assigned in the initializer list(s) [fieldInitializerTypeMap], one for
* types assigned in the constructor(s) [fieldConstructorTypeMap], and one
* for types assigned in the rest of the code, where the field can be
* resolved [fieldTypeMap].
*
* If a field has a type both from constructors and from the initializer
* list(s), then the type from the constructor(s) will owerride the one from
* the initializer list(s).
*
* Because the order in which generative constructors, generative constructor
* bodies and normal method/function bodies are compiled is undefined, and
* because they can all be recompiled, it is not possible to combine this
* information into one map at the moment.
*/
final Map<Element, HType> fieldInitializerTypeMap;
final Map<Element, HType> fieldConstructorTypeMap;
final Map<Element, HType> fieldTypeMap;
/**
* The set of current names setter selectors used. If a named selector is
* used it is currently not possible to infer the type of the field.
*/
final Set<SourceString> setterSelectorsUsed;
final Map<Element, Set<Element>> optimizedStaticFunctions;
final Map<Element, FunctionSet> optimizedFunctions;
FieldTypesRegistry(JavaScriptBackend backend)
: constructors = new Map<ClassElement, Set<Element>>(),
fieldInitializerTypeMap = new Map<Element, HType>(),
fieldConstructorTypeMap = new Map<Element, HType>(),
fieldTypeMap = new Map<Element, HType>(),
setterSelectorsUsed = new Set<SourceString>(),
optimizedStaticFunctions = new Map<Element, Set<Element>>(),
optimizedFunctions = new Map<Element, FunctionSet>(),
this.backend = backend;
Compiler get compiler => backend.compiler;
void scheduleRecompilation(Element field) {
Set optimizedStatics = optimizedStaticFunctions[field];
if (optimizedStatics != null) {
optimizedStatics.forEach(backend.scheduleForRecompilation);
optimizedStaticFunctions.remove(field);
}
FunctionSet optimized = optimizedFunctions[field];
if (optimized != null) {
optimized.forEach(backend.scheduleForRecompilation);
optimizedFunctions.remove(field);
}
}
int constructorCount(Element element) {
assert(element.isClass());
Set<Element> ctors = constructors[element];
return ctors == null ? 0 : ctors.length;
}
void registerFieldType(Map<Element, HType> typeMap,
Element field,
HType type) {
assert(field.isField());
HType before = optimisticFieldType(field);
HType oldType = typeMap[field];
HType newType;
if (oldType != null) {
newType = oldType.union(type, compiler);
} else {
newType = type;
}
typeMap[field] = newType;
if (oldType != newType) {
scheduleRecompilation(field);
}
}
void registerConstructor(Element element) {
assert(element.isGenerativeConstructor());
Element cls = element.getEnclosingClass();
constructors.putIfAbsent(cls, () => new Set<Element>());
Set<Element> ctors = constructors[cls];
if (ctors.contains(element)) return;
ctors.add(element);
// We cannot infer field types for classes with more than one constructor.
// When the second constructor is seen, recompile all functions relying on
// optimistic field types for that class.
// TODO(sgjesse): Handle field types for classes with more than one
// constructor.
if (ctors.length == 2) {
optimizedFunctions.forEach((Element field, _) {
if (identical(field.enclosingElement, cls)) {
scheduleRecompilation(field);
}
});
}
}
void registerFieldInitializer(Element field, HType type) {
registerFieldType(fieldInitializerTypeMap, field, type);
}
void registerFieldConstructor(Element field, HType type) {
registerFieldType(fieldConstructorTypeMap, field, type);
}
void registerFieldSetter(FunctionElement element, Element field, HType type) {
HType initializerType = fieldInitializerTypeMap[field];
HType constructorType = fieldConstructorTypeMap[field];
HType setterType = fieldTypeMap[field];
if (type == HType.UNKNOWN
&& initializerType == null
&& constructorType == null
&& setterType == null) {
// Don't register UNKONWN if there is currently no type information
// present for the field. Instead register the function holding the
// setter for recompilation if better type information for the field
// becomes available.
registerOptimizedFunction(element, field, type);
return;
}
registerFieldType(fieldTypeMap, field, type);
}
void addedDynamicSetter(Selector setter, HType type) {
// Field type optimizations are disabled for all fields matching a
// setter selector.
assert(setter.isSetter());
// TODO(sgjesse): Take the type of the setter into account.
if (setterSelectorsUsed.contains(setter.name)) return;
setterSelectorsUsed.add(setter.name);
optimizedStaticFunctions.forEach((Element field, _) {
if (field.name == setter.name) {
scheduleRecompilation(field);
}
});
optimizedFunctions.forEach((Element field, _) {
if (field.name == setter.name) {
scheduleRecompilation(field);
}
});
}
HType optimisticFieldType(Element field) {
assert(field.isField());
if (constructorCount(field.getEnclosingClass()) > 1) {
return HType.UNKNOWN;
}
if (setterSelectorsUsed.contains(field.name)) {
return HType.UNKNOWN;
}
HType initializerType = fieldInitializerTypeMap[field];
HType constructorType = fieldConstructorTypeMap[field];
if (initializerType == null && constructorType == null) {
// If there are no constructor type information return UNKNOWN. This
// ensures that the function will be recompiled if useful constructor
// type information becomes available.
return HType.UNKNOWN;
}
// A type set through the constructor overrides the type from the
// initializer list.
HType result = constructorType != null ? constructorType : initializerType;
HType type = fieldTypeMap[field];
if (type != null) result = result.union(type, compiler);
return result;
}
void registerOptimizedFunction(FunctionElement element,
Element field,
HType type) {
assert(field.isField());
if (Elements.isStaticOrTopLevel(element)) {
optimizedStaticFunctions.putIfAbsent(
field, () => new Set<Element>());
optimizedStaticFunctions[field].add(element);
} else {
optimizedFunctions.putIfAbsent(
field, () => new FunctionSet(backend.compiler));
optimizedFunctions[field].add(element);
}
}
void dump() {
Set<Element> allFields = new Set<Element>();
fieldInitializerTypeMap.keys.forEach(allFields.add);
fieldConstructorTypeMap.keys.forEach(allFields.add);
fieldTypeMap.keys.forEach(allFields.add);
allFields.forEach((Element field) {
print("Inferred $field has type ${optimisticFieldType(field)}");
});
}
}
class ArgumentTypesRegistry {
final JavaScriptBackend backend;
/**
* Documentation wanted -- johnniwinther
*
* Invariant: Keys must be declaration elements.
*/
final Map<Element, HTypeList> staticTypeMap;
/**
* Documentation wanted -- johnniwinther
*
* Invariant: Elements must be declaration elements.
*/
final Set<Element> optimizedStaticFunctions;
final SelectorMap<HTypeList> selectorTypeMap;
final FunctionSet optimizedFunctions;
/**
* Documentation wanted -- johnniwinther
*
* Invariant: Keys must be declaration elements.
*/
final Map<Element, HTypeList> optimizedTypes;
final Map<Element, OptionalParameterTypes> optimizedDefaultValueTypes;
ArgumentTypesRegistry(JavaScriptBackend backend)
: staticTypeMap = new Map<Element, HTypeList>(),
optimizedStaticFunctions = new Set<Element>(),
selectorTypeMap = new SelectorMap<HTypeList>(backend.compiler),
optimizedFunctions = new FunctionSet(backend.compiler),
optimizedTypes = new Map<Element, HTypeList>(),
optimizedDefaultValueTypes =
new Map<Element, OptionalParameterTypes>(),
this.backend = backend;
Compiler get compiler => backend.compiler;
bool updateTypes(HTypeList oldTypes, HTypeList newTypes, var key, var map) {
if (oldTypes.allUnknown) return false;
newTypes = oldTypes.union(newTypes, backend.compiler);
if (identical(newTypes, oldTypes)) return false;
map[key] = newTypes;
return true;
}
void registerStaticInvocation(HInvokeStatic node, HTypeMap types) {
Element element = node.element;
assert(invariant(node, element.isDeclaration));
HTypeList oldTypes = staticTypeMap[element];
HTypeList newTypes = new HTypeList.fromStaticInvocation(node, types);
if (oldTypes == null) {
staticTypeMap[element] = newTypes;
} else if (updateTypes(oldTypes, newTypes, element, staticTypeMap)) {
if (optimizedStaticFunctions.contains(element)) {
backend.scheduleForRecompilation(element);
}
}
}
void registerNonCallStaticUse(HStatic node) {
// When a static is used for anything else than a call target we cannot
// infer anything about its parameter types.
Element element = node.element;
assert(invariant(node, element.isDeclaration));
if (optimizedStaticFunctions.contains(element)) {
backend.scheduleForRecompilation(element);
}
staticTypeMap[element] = HTypeList.ALL_UNKNOWN;
}
void registerDynamicInvocation(HTypeList providedTypes, Selector selector) {
if (selector.isClosureCall()) {
// We cannot use the current framework to do optimizations based
// on the 'call' selector because we are also generating closure
// calls during the emitter phase, which at this point, does not
// track parameter types, nor invalidates optimized methods.
return;
}
if (!selectorTypeMap.containsKey(selector)) {
selectorTypeMap[selector] = providedTypes;
} else {
HTypeList oldTypes = selectorTypeMap[selector];
updateTypes(oldTypes, providedTypes, selector, selectorTypeMap);
}
// If we're not compiling, we don't have to do anything.
if (compiler.phase != Compiler.PHASE_COMPILING) return;
// Run through all optimized functions and figure out if they need
// to be recompiled because of this new invocation.
optimizedFunctions.filterBySelector(selector).forEach((Element element) {
// TODO(kasperl): Maybe check if the element is already marked for
// recompilation? Could be pretty cheap compared to computing
// union types.
HTypeList newTypes =
parameterTypes(element, optimizedDefaultValueTypes[element]);
bool recompile = false;
if (newTypes.allUnknown) {
recompile = true;
} else {
HTypeList oldTypes = optimizedTypes[element];
assert(newTypes.length == oldTypes.length);
for (int i = 0; i < oldTypes.length; i++) {
if (newTypes[i] != oldTypes[i]) {
recompile = true;
break;
}
}
}
if (recompile) backend.scheduleForRecompilation(element);
});
}
HTypeList parameterTypes(FunctionElement element,
OptionalParameterTypes defaultValueTypes) {
assert(invariant(element, element.isDeclaration));
// Handle static functions separately.
if (Elements.isStaticOrTopLevelFunction(element) ||
element.kind == ElementKind.GENERATIVE_CONSTRUCTOR) {
HTypeList types = staticTypeMap[element];
if (types != null) {
if (!optimizedStaticFunctions.contains(element)) {
optimizedStaticFunctions.add(element);
}
return types;
} else {
return HTypeList.ALL_UNKNOWN;
}
}
// Getters have no parameters.
if (element.isGetter()) return HTypeList.ALL_UNKNOWN;
// TODO(kasperl): What kind of non-members do we get here?
if (!element.isMember()) return HTypeList.ALL_UNKNOWN;
// If there are any getters for this method we cannot know anything about
// the types of the provided parameters. Use resolverWorld for now as that
// information does not change during compilation.
// TODO(ngeoffray): These checks should use the codegenWorld and keep track
// of changes to this information.
if (compiler.resolverWorld.hasInvokedGetter(element, compiler)) {
return HTypeList.ALL_UNKNOWN;
}
FunctionSignature signature = element.computeSignature(compiler);
HTypeList found = null;
selectorTypeMap.visitMatching(element,
(Selector selector, HTypeList types) {
if (selector.argumentCount != signature.parameterCount ||
selector.namedArgumentCount > 0) {
types = types.unionWithOptionalParameters(selector,
signature,
defaultValueTypes);
}
assert(types.allUnknown || types.length == signature.parameterCount);
found = (found == null) ? types : found.union(types, compiler);
return !found.allUnknown;
});
return found != null ? found : HTypeList.ALL_UNKNOWN;
}
void registerOptimizedFunction(Element element,
HTypeList parameterTypes,
OptionalParameterTypes defaultValueTypes) {
if (Elements.isStaticOrTopLevelFunction(element)) {
if (parameterTypes.allUnknown) {
optimizedStaticFunctions.remove(element);
} else {
optimizedStaticFunctions.add(element);
}
}
// TODO(kasperl): What kind of non-members do we get here?
if (!element.isMember()) return;
if (parameterTypes.allUnknown) {
optimizedFunctions.remove(element);
optimizedTypes.remove(element);
optimizedDefaultValueTypes.remove(element);
} else {
optimizedFunctions.add(element);
optimizedTypes[element] = parameterTypes;
optimizedDefaultValueTypes[element] = defaultValueTypes;
}
}
void dump() {
optimizedFunctions.forEach((Element element) {
HTypeList types = optimizedTypes[element];
print("Inferred $element has argument types ${types.types}");
});
}
}
class JavaScriptItemCompilationContext extends ItemCompilationContext {
final HTypeMap types;
final Set<HInstruction> boundsChecked;
JavaScriptItemCompilationContext()
: types = new HTypeMap(),
boundsChecked = new Set<HInstruction>();
}
class JavaScriptBackend extends Backend {
SsaBuilderTask builder;
SsaOptimizerTask optimizer;
SsaCodeGeneratorTask generator;
CodeEmitterTask emitter;
/**
* The generated code as a js AST for compiled methods.
*/
Map<Element, js.Expression> get generatedCode {
return compiler.enqueuer.codegen.generatedCode;
}
/**
* The generated code as a js AST for compiled bailout methods.
*/
final Map<Element, js.Expression> generatedBailoutCode =
new Map<Element, js.Expression>();
ClassElement jsStringClass;
ClassElement jsArrayClass;
ClassElement jsNumberClass;
ClassElement jsIntClass;
ClassElement jsDoubleClass;
ClassElement jsFunctionClass;
ClassElement jsNullClass;
ClassElement jsBoolClass;
ClassElement objectInterceptorClass;
Element jsArrayLength;
Element jsStringLength;
Element jsArrayRemoveLast;
Element jsArrayAdd;
Element jsStringSplit;
Element jsStringConcat;
Element jsStringToString;
Element getInterceptorMethod;
Element fixedLengthListConstructor;
bool seenAnyClass = false;
final Namer namer;
/**
* Interface used to determine if an object has the JavaScript
* indexing behavior. The interface is only visible to specific
* libraries.
*/
ClassElement jsIndexingBehaviorInterface;
final Map<Element, ReturnInfo> returnInfo;
/**
* Documentation wanted -- johnniwinther
*
* Invariant: Elements must be declaration elements.
*/
final List<Element> invalidateAfterCodegen;
ArgumentTypesRegistry argumentTypes;
FieldTypesRegistry fieldTypes;
/**
* A collection of selectors of intercepted method calls. The
* emitter uses this set to generate the [:ObjectInterceptor:] class
* whose members just forward the call to the intercepted receiver.
*/
final Set<Selector> usedInterceptors;
/**
* A collection of selectors that must have a one shot interceptor
* generated.
*/
final Set<Selector> oneShotInterceptors;
/**
* The members of instantiated interceptor classes: maps a member
* name to the list of members that have that name. This map is used
* by the codegen to know whether a send must be intercepted or not.
*/
final Map<SourceString, Set<Element>> interceptedElements;
/**
* A map of specialized versions of the [getInterceptorMethod].
* Since [getInterceptorMethod] is a hot method at runtime, we're
* always specializing it based on the incoming type. The keys in
* the map are the names of these specialized versions. Note that
* the generic version that contains all possible type checks is
* also stored in this map.
*/
final Map<String, Collection<ClassElement>> specializedGetInterceptors;
/**
* Set of classes whose instances are intercepted. Implemented as a
* [LinkedHashMap] to preserve the insertion order.
* TODO(ngeoffray): No need to preserve order anymore.
*/
final Map<ClassElement, ClassElement> interceptedClasses;
List<CompilerTask> get tasks {
return <CompilerTask>[builder, optimizer, generator, emitter];
}
final RuntimeTypeInformation rti;
JavaScriptBackend(Compiler compiler, bool generateSourceMap, bool disableEval)
: namer = determineNamer(compiler),
returnInfo = new Map<Element, ReturnInfo>(),
invalidateAfterCodegen = new List<Element>(),
usedInterceptors = new Set<Selector>(),
oneShotInterceptors = new Set<Selector>(),
interceptedElements = new Map<SourceString, Set<Element>>(),
rti = new RuntimeTypeInformation(compiler),
specializedGetInterceptors =
new Map<String, Collection<ClassElement>>(),
interceptedClasses = new LinkedHashMap<ClassElement, ClassElement>(),
super(compiler, JAVA_SCRIPT_CONSTANT_SYSTEM) {
emitter = disableEval
// TODO(8522): Restore --disallow-unsafe-eval.
? null // new CodeEmitterNoEvalTask(compiler, namer, generateSourceMap)
: new CodeEmitterTask(compiler, namer, generateSourceMap);
builder = new SsaBuilderTask(this);
optimizer = new SsaOptimizerTask(this);
generator = new SsaCodeGeneratorTask(this);
argumentTypes = new ArgumentTypesRegistry(this);
fieldTypes = new FieldTypesRegistry(this);
}
static Namer determineNamer(Compiler compiler) {
return compiler.enableMinification ?
new MinifyNamer(compiler) :
new Namer(compiler);
}
bool isInterceptorClass(Element element) {
if (element == null) return false;
return interceptedClasses.containsKey(element);
}
void addInterceptedSelector(Selector selector) {
usedInterceptors.add(selector);
}
void addOneShotInterceptor(Selector selector) {
oneShotInterceptors.add(selector);
}
/**
* Returns a set of interceptor classes that contain a member whose
* signature matches the given [selector]. Returns [:null:] if there
* is no class.
*/
Set<ClassElement> getInterceptedClassesOn(Selector selector) {
Set<Element> intercepted = interceptedElements[selector.name];
if (intercepted == null) return null;
Set<ClassElement> result = new Set<ClassElement>();
for (Element element in intercepted) {
if (selector.applies(element, compiler)) {
result.add(element.getEnclosingClass());
}
}
if (result.isEmpty) return null;
return result;
}
List<ClassElement> getListOfInterceptedClasses() {
return <ClassElement>[jsStringClass, jsArrayClass, jsIntClass,
jsDoubleClass, jsNumberClass, jsNullClass,
jsFunctionClass, jsBoolClass];
}
void initializeInterceptorElements() {
objectInterceptorClass =
compiler.findInterceptor(const SourceString('ObjectInterceptor'));
getInterceptorMethod =
compiler.findInterceptor(const SourceString('getInterceptor'));
List<ClassElement> classes = [
jsStringClass = compiler.findInterceptor(const SourceString('JSString')),
jsArrayClass = compiler.findInterceptor(const SourceString('JSArray')),
// The int class must be before the double class, because the
// emitter relies on this list for the order of type checks.
jsIntClass = compiler.findInterceptor(const SourceString('JSInt')),
jsDoubleClass = compiler.findInterceptor(const SourceString('JSDouble')),
jsNumberClass = compiler.findInterceptor(const SourceString('JSNumber')),
jsNullClass = compiler.findInterceptor(const SourceString('JSNull')),
jsFunctionClass =
compiler.findInterceptor(const SourceString('JSFunction')),
jsBoolClass = compiler.findInterceptor(const SourceString('JSBool'))];
jsArrayClass.ensureResolved(compiler);
jsArrayLength = compiler.lookupElementIn(
jsArrayClass, const SourceString('length'));
jsArrayRemoveLast = compiler.lookupElementIn(
jsArrayClass, const SourceString('removeLast'));
jsArrayAdd = compiler.lookupElementIn(
jsArrayClass, const SourceString('add'));
jsStringClass.ensureResolved(compiler);
jsStringLength = compiler.lookupElementIn(
jsStringClass, const SourceString('length'));
jsStringSplit = compiler.lookupElementIn(
jsStringClass, const SourceString('split'));
jsStringConcat = compiler.lookupElementIn(
jsStringClass, const SourceString('concat'));
jsStringToString = compiler.lookupElementIn(
jsStringClass, const SourceString('toString'));
for (ClassElement cls in classes) {
if (cls != null) interceptedClasses[cls] = null;
}
}
void addInterceptors(ClassElement cls, Enqueuer enqueuer) {
if (enqueuer.isResolutionQueue) {
cls.ensureResolved(compiler);
cls.forEachMember((ClassElement classElement, Element member) {
Set<Element> set = interceptedElements.putIfAbsent(
member.name, () => new Set<Element>());
set.add(member);
},
includeSuperMembers: true);
}
enqueuer.registerInstantiatedClass(cls);
}
void registerSpecializedGetInterceptor(Set<ClassElement> classes) {
compiler.enqueuer.codegen.registerInstantiatedClass(objectInterceptorClass);
String name = namer.getInterceptorName(getInterceptorMethod, classes);
if (classes.contains(compiler.objectClass)) {
// We can't use a specialized [getInterceptorMethod], so we make
// sure we emit the one with all checks.
specializedGetInterceptors.putIfAbsent(name, () {
// It is important to take the order provided by the map,
// because we want the int type check to happen before the
// double type check: the double type check covers the int
// type check. Also we don't need to do a number type check
// because that is covered by the double type check.
List<ClassElement> keys = <ClassElement>[];
interceptedClasses.forEach((ClassElement cls, _) {
if (cls != jsNumberClass) keys.add(cls);
});
return keys;
});
} else {
specializedGetInterceptors[name] = classes;
}
}
void initializeNoSuchMethod() {
// In case the emitter generates noSuchMethod calls, we need to
// make sure all [noSuchMethod] methods know they might take a
// [JsInvocationMirror] as parameter.
HTypeList types = new HTypeList(1);
types[0] = new HBoundedType.exact(
compiler.jsInvocationMirrorClass.computeType(compiler));
argumentTypes.registerDynamicInvocation(types, new Selector.noSuchMethod());
}
void registerInstantiatedClass(ClassElement cls, Enqueuer enqueuer) {
if (!seenAnyClass) {
initializeInterceptorElements();
initializeNoSuchMethod();
seenAnyClass = true;
}
ClassElement result = null;
if (cls == compiler.stringClass) {
addInterceptors(jsStringClass, enqueuer);
} else if (cls == compiler.listClass) {
addInterceptors(jsArrayClass, enqueuer);
// The backend will try to optimize array access and use the
// `ioore` and `iae` helpers directly.
if (enqueuer.isResolutionQueue) {
enqueuer.registerStaticUse(
compiler.findHelper(const SourceString('ioore')));
enqueuer.registerStaticUse(
compiler.findHelper(const SourceString('iae')));
}
} else if (cls == compiler.intClass) {
addInterceptors(jsIntClass, enqueuer);
addInterceptors(jsNumberClass, enqueuer);
} else if (cls == compiler.doubleClass) {
addInterceptors(jsDoubleClass, enqueuer);
addInterceptors(jsNumberClass, enqueuer);
} else if (cls == compiler.functionClass) {
addInterceptors(jsFunctionClass, enqueuer);
} else if (cls == compiler.boolClass) {
addInterceptors(jsBoolClass, enqueuer);
} else if (cls == compiler.nullClass) {
addInterceptors(jsNullClass, enqueuer);
} else if (cls == compiler.numClass) {
addInterceptors(jsIntClass, enqueuer);
addInterceptors(jsDoubleClass, enqueuer);
addInterceptors(jsNumberClass, enqueuer);
} else if (cls == compiler.mapClass) {
// The backend will use a literal list to initialize the entries
// of the map.
if (enqueuer.isResolutionQueue) {
enqueuer.registerInstantiatedClass(compiler.listClass);
enqueuer.registerInstantiatedClass(compiler.mapLiteralClass);
}
}
}
Element get cyclicThrowHelper {
return compiler.findHelper(const SourceString("throwCyclicInit"));
}
JavaScriptItemCompilationContext createItemCompilationContext() {
return new JavaScriptItemCompilationContext();
}
void enqueueHelpers(ResolutionEnqueuer world) {
enqueueAllTopLevelFunctions(compiler.jsHelperLibrary, world);
jsIndexingBehaviorInterface =
compiler.findHelper(const SourceString('JavaScriptIndexingBehavior'));
if (jsIndexingBehaviorInterface != null) {
world.registerIsCheck(jsIndexingBehaviorInterface.computeType(compiler));
}
for (var helper in [const SourceString('Closure'),
const SourceString('ConstantMap'),
const SourceString('ConstantProtoMap')]) {
var e = compiler.findHelper(helper);
if (e != null) world.registerInstantiatedClass(e);
}
}
void codegen(CodegenWorkItem work) {
Element element = work.element;
if (element.kind.category == ElementCategory.VARIABLE) {
Constant initialValue = compiler.constantHandler.compileWorkItem(work);
if (initialValue != null) {
return;
} else {
// If the constant-handler was not able to produce a result we have to
// go through the builder (below) to generate the lazy initializer for
// the static variable.
// We also need to register the use of the cyclic-error helper.
compiler.enqueuer.codegen.registerStaticUse(cyclicThrowHelper);
}
}
HGraph graph = builder.build(work);
optimizer.optimize(work, graph, false);
if (work.allowSpeculativeOptimization
&& optimizer.trySpeculativeOptimizations(work, graph)) {
js.Expression code = generator.generateBailoutMethod(work, graph);
generatedBailoutCode[element] = code;
optimizer.prepareForSpeculativeOptimizations(work, graph);
optimizer.optimize(work, graph, true);
}
js.Expression code = generator.generateCode(work, graph);
generatedCode[element] = code;
invalidateAfterCodegen.forEach(eagerRecompile);
invalidateAfterCodegen.clear();
}
native.NativeEnqueuer nativeResolutionEnqueuer(Enqueuer world) {
return new native.NativeResolutionEnqueuer(world, compiler);
}
native.NativeEnqueuer nativeCodegenEnqueuer(Enqueuer world) {
return new native.NativeCodegenEnqueuer(world, compiler, emitter);
}
/**
* Unit test hook that returns code of an element as a String.
*
* Invariant: [element] must be a declaration element.
*/
String assembleCode(Element element) {
assert(invariant(element, element.isDeclaration));
return js.prettyPrint(generatedCode[element], compiler).getText();
}
void assembleProgram() {
emitter.assembleProgram();
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [element] must be a declaration element.
*/
void scheduleForRecompilation(Element element) {
assert(invariant(element, element.isDeclaration));
if (compiler.phase == Compiler.PHASE_COMPILING) {
invalidateAfterCodegen.add(element);
}
}
/**
* Register a dynamic invocation and collect the provided types for the
* named selector.
*/
void registerDynamicInvocation(HInvokeDynamic node,
Selector selector,
HTypeMap types) {
HTypeList providedTypes =
new HTypeList.fromDynamicInvocation(node, selector, types);
argumentTypes.registerDynamicInvocation(providedTypes, selector);
}
/**
* Register a static invocation and collect the provided types for the
* named selector.
*/
void registerStaticInvocation(HInvokeStatic node, HTypeMap types) {
argumentTypes.registerStaticInvocation(node, types);
}
/**
* Register that a static is used for something else than a direct call
* target.
*/
void registerNonCallStaticUse(HStatic node) {
argumentTypes.registerNonCallStaticUse(node);
}
/**
* Retrieve the types of the parameters used for calling the [element]
* function. The types are optimistic in the sense as they are based on the
* possible invocations of the function seen so far.
*
* Invariant: [element] must be a declaration element.
*/
HTypeList optimisticParameterTypes(
FunctionElement element,
OptionalParameterTypes defaultValueTypes) {
assert(invariant(element, element.isDeclaration));
if (element.parameterCount(compiler) == 0) return HTypeList.ALL_UNKNOWN;
return argumentTypes.parameterTypes(element, defaultValueTypes);
}
/**
* Register that the function [element] has been optimized under the
* assumptions that the types [parameterType] will be used for calling it.
* The passed [defaultValueTypes] holds the types of default values for
* the optional parameters. If this assumption fail the function will be
* scheduled for recompilation.
*
* Invariant: [element] must be a declaration element.
*/
registerParameterTypesOptimization(
FunctionElement element,
HTypeList parameterTypes,
OptionalParameterTypes defaultValueTypes) {
assert(invariant(element, element.isDeclaration));
if (element.parameterCount(compiler) == 0) return;
argumentTypes.registerOptimizedFunction(
element, parameterTypes, defaultValueTypes);
}
registerFieldTypesOptimization(FunctionElement element,
Element field,
HType type) {
fieldTypes.registerOptimizedFunction(element, field, type);
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [element] must be a declaration element.
*/
void registerReturnType(FunctionElement element, HType returnType) {
assert(invariant(element, element.isDeclaration));
ReturnInfo info = returnInfo[element];
if (info != null) {
info.update(returnType, scheduleForRecompilation, compiler);
} else {
returnInfo[element] = new ReturnInfo(returnType);
}
}
/**
* Retrieve the return type of the function [callee]. The type is optimistic
* in the sense that is is based on the compilation of [callee]. If [callee]
* is recompiled the return type might change to someting broader. For that
* reason [caller] is registered for recompilation if this happens. If the
* function [callee] has not yet been compiled the returned type is [null].
*
* Invariant: Both [caller] and [callee] must be declaration elements.
*/
HType optimisticReturnTypesWithRecompilationOnTypeChange(
Element caller, FunctionElement callee) {
assert(invariant(callee, callee.isDeclaration));
returnInfo.putIfAbsent(callee, () => new ReturnInfo.unknownType());
ReturnInfo info = returnInfo[callee];
HType returnType = info.returnType;
if (returnType != HType.UNKNOWN && returnType != null && caller != null) {
assert(invariant(caller, caller.isDeclaration));
info.addCompiledFunction(caller);
}
return info.returnType;
}
void dumpReturnTypes() {
returnInfo.forEach((Element element, ReturnInfo info) {
if (info.returnType != HType.UNKNOWN) {
print("Inferred $element has return type ${info.returnType}");
}
});
}
void registerConstructor(Element element) {
fieldTypes.registerConstructor(element);
}
void registerFieldInitializer(Element field, HType type) {
fieldTypes.registerFieldInitializer(field, type);
}
void registerFieldConstructor(Element field, HType type) {
fieldTypes.registerFieldConstructor(field, type);
}
void registerFieldSetter(FunctionElement element, Element field, HType type) {
fieldTypes.registerFieldSetter(element, field, type);
}
void addedDynamicSetter(Selector setter, HType type) {
fieldTypes.addedDynamicSetter(setter, type);
}
HType optimisticFieldType(Element element) {
return fieldTypes.optimisticFieldType(element);
}
/**
* Return the checked mode helper name that will be needed to do a
* type check on [type] at runtime. Note that this method is being
* called both by the resolver with interface types (int, String,
* ...), and by the SSA backend with implementation types (JSInt,
* JSString, ...).
*/
SourceString getCheckedModeHelper(DartType type) {
Element element = type.element;
bool nativeCheck =
emitter.nativeEmitter.requiresNativeIsCheck(element);
if (type.isMalformed) {
// Check for malformed types first, because the type may be a list type
// with a malformed argument type.
return const SourceString('malformedTypeCheck');
} else if (type == compiler.types.voidType) {
return const SourceString('voidTypeCheck');
} else if (element == jsStringClass || element == compiler.stringClass) {
return const SourceString('stringTypeCheck');
} else if (element == jsDoubleClass || element == compiler.doubleClass) {
return const SourceString('doubleTypeCheck');
} else if (element == jsNumberClass || element == compiler.numClass) {
return const SourceString('numTypeCheck');
} else if (element == jsBoolClass || element == compiler.boolClass) {
return const SourceString('boolTypeCheck');
} else if (element == jsFunctionClass
|| element == compiler.functionClass) {
return const SourceString('functionTypeCheck');
} else if (element == jsIntClass || element == compiler.intClass) {
return const SourceString('intTypeCheck');
} else if (Elements.isNumberOrStringSupertype(element, compiler)) {
return nativeCheck
? const SourceString('numberOrStringSuperNativeTypeCheck')
: const SourceString('numberOrStringSuperTypeCheck');
} else if (Elements.isStringOnlySupertype(element, compiler)) {
return nativeCheck
? const SourceString('stringSuperNativeTypeCheck')
: const SourceString('stringSuperTypeCheck');
} else if (element == compiler.listClass || element == jsArrayClass) {
return const SourceString('listTypeCheck');
} else {
if (Elements.isListSupertype(element, compiler)) {
return nativeCheck
? const SourceString('listSuperNativeTypeCheck')
: const SourceString('listSuperTypeCheck');
} else {
return nativeCheck
? const SourceString('callTypeCheck')
: const SourceString('propertyTypeCheck');
}
}
}
void dumpInferredTypes() {
print("Inferred argument types:");
print("------------------------");
argumentTypes.dump();
print("");
print("Inferred return types:");
print("----------------------");
dumpReturnTypes();
print("");
print("Inferred field types:");
print("------------------------");
fieldTypes.dump();
print("");
}
Element getExceptionUnwrapper() {
return compiler.findHelper(const SourceString('unwrapException'));
}
Element getThrowRuntimeError() {
return compiler.findHelper(const SourceString('throwRuntimeError'));
}
Element getThrowMalformedSubtypeError() {
return compiler.findHelper(
const SourceString('throwMalformedSubtypeError'));
}
Element getThrowAbstractClassInstantiationError() {
return compiler.findHelper(
const SourceString('throwAbstractClassInstantiationError'));
}
Element getClosureConverter() {
return compiler.findHelper(const SourceString('convertDartClosureToJS'));
}
Element getTraceFromException() {
return compiler.findHelper(const SourceString('getTraceFromException'));
}
Element getMapMaker() {
return compiler.findHelper(const SourceString('makeLiteralMap'));
}
Element getSetRuntimeTypeInfo() {
return compiler.findHelper(const SourceString('setRuntimeTypeInfo'));
}
Element getGetRuntimeTypeInfo() {
return compiler.findHelper(const SourceString('getRuntimeTypeInfo'));
}
Element getGetRuntimeTypeArgument() {
return compiler.findHelper(const SourceString('getRuntimeTypeArgument'));
}
/**
* Remove [element] from the set of generated code, and put it back
* into the worklist.
*
* Invariant: [element] must be a declaration element.
*/
void eagerRecompile(Element element) {
assert(invariant(element, element.isDeclaration));
generatedCode.remove(element);
generatedBailoutCode.remove(element);
compiler.enqueuer.codegen.addToWorkList(element);
}
}