blob: b8fa032d80e5530d90643eef59232bdb439b2655 [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 dart2js.js_emitter;
const USE_LAZY_EMITTER = const bool.fromEnvironment("dart2js.use.lazy.emitter");
/**
* Generates the code for all used classes in the program. Static fields (even
* in classes) are ignored, since they can be treated as non-class elements.
*
* The code for the containing (used) methods must exist in the `universe`.
*/
class CodeEmitterTask extends CompilerTask {
// TODO(floitsch): the code-emitter task should not need a namer.
final Namer namer;
final TypeTestRegistry typeTestRegistry;
NativeEmitter nativeEmitter;
MetadataCollector metadataCollector;
OldEmitter oldEmitter;
Emitter emitter;
final Set<ClassElement> neededClasses = new Set<ClassElement>();
Set<ClassElement> classesOnlyNeededForRti;
final Map<OutputUnit, List<ClassElement>> outputClassLists =
new Map<OutputUnit, List<ClassElement>>();
final Map<OutputUnit, List<ConstantValue>> outputConstantLists =
new Map<OutputUnit, List<ConstantValue>>();
final Map<OutputUnit, List<Element>> outputStaticLists =
new Map<OutputUnit, List<Element>>();
final Map<OutputUnit, List<VariableElement>> outputStaticNonFinalFieldLists =
new Map<OutputUnit, List<VariableElement>>();
final Map<OutputUnit, Set<LibraryElement>> outputLibraryLists =
new Map<OutputUnit, Set<LibraryElement>>();
/// True, if the output contains a constant list.
///
/// This flag is updated in [computeNeededConstants].
bool outputContainsConstantList = false;
final List<ClassElement> nativeClassesAndSubclasses = <ClassElement>[];
/// Records if a type variable is read dynamically for type tests.
final Set<TypeVariableElement> readTypeVariables =
new Set<TypeVariableElement>();
List<TypedefElement> typedefsNeededForReflection;
JavaScriptBackend get backend => compiler.backend;
CodeEmitterTask(Compiler compiler, Namer namer, bool generateSourceMap)
: super(compiler),
this.namer = namer,
this.typeTestRegistry = new TypeTestRegistry(compiler) {
nativeEmitter = new NativeEmitter(this);
oldEmitter = new OldEmitter(compiler, namer, generateSourceMap, this);
emitter = USE_LAZY_EMITTER
? new lazy_js_emitter.Emitter(compiler, namer, nativeEmitter)
: oldEmitter;
metadataCollector = new MetadataCollector(compiler, emitter);
}
String get name => 'Code emitter';
/// Returns the closure expression of a static function.
jsAst.Expression isolateStaticClosureAccess(FunctionElement element) {
return emitter.isolateStaticClosureAccess(element);
}
/// Returns the JS function that must be invoked to get the value of the
/// lazily initialized static.
jsAst.Expression isolateLazyInitializerAccess(FieldElement element) {
return emitter.isolateLazyInitializerAccess(element);
}
/// Returns the JS code for accessing the embedded [global].
jsAst.Expression generateEmbeddedGlobalAccess(String global) {
return emitter.generateEmbeddedGlobalAccess(global);
}
/// Returns the JS code for accessing the given [constant].
jsAst.Expression constantReference(ConstantValue constant) {
return emitter.constantReference(constant);
}
jsAst.Expression staticFieldAccess(FieldElement e) {
return emitter.staticFieldAccess(e);
}
/// Returns the JS function representing the given function.
///
/// The function must be invoked and can not be used as closure.
jsAst.Expression staticFunctionAccess(FunctionElement e) {
return emitter.staticFunctionAccess(e);
}
/// Returns the JS constructor of the given element.
///
/// The returned expression must only be used in a JS `new` expression.
jsAst.Expression constructorAccess(ClassElement e) {
return emitter.constructorAccess(e);
}
/// Returns the JS prototype of the given class [e].
jsAst.Expression prototypeAccess(ClassElement e,
{bool hasBeenInstantiated: false}) {
return emitter.prototypeAccess(e, hasBeenInstantiated);
}
/// Returns the JS prototype of the given interceptor class [e].
jsAst.Expression interceptorPrototypeAccess(ClassElement e) {
return jsAst.js('#.prototype', interceptorClassAccess(e));
}
/// Returns the JS constructor of the given interceptor class [e].
jsAst.Expression interceptorClassAccess(ClassElement e) {
return emitter.interceptorClassAccess(e);
}
/// Returns the JS expression representing the type [e].
///
/// The given type [e] might be a Typedef.
jsAst.Expression typeAccess(Element e) {
return emitter.typeAccess(e);
}
/// Returns the JS template for the given [builtin].
jsAst.Template builtinTemplateFor(JsBuiltin builtin) {
return emitter.templateForBuiltin(builtin);
}
void registerReadTypeVariable(TypeVariableElement element) {
readTypeVariables.add(element);
}
Set<ClassElement> computeInterceptorsReferencedFromConstants() {
Set<ClassElement> classes = new Set<ClassElement>();
JavaScriptConstantCompiler handler = backend.constants;
List<ConstantValue> constants = handler.getConstantsForEmission();
for (ConstantValue constant in constants) {
if (constant is InterceptorConstantValue) {
InterceptorConstantValue interceptorConstant = constant;
classes.add(interceptorConstant.dispatchedType.element);
}
}
return classes;
}
/**
* Return a function that returns true if its argument is a class
* that needs to be emitted.
*/
Function computeClassFilter() {
if (backend.isTreeShakingDisabled) return (ClassElement cls) => true;
Set<ClassElement> unneededClasses = new Set<ClassElement>();
// The [Bool] class is not marked as abstract, but has a factory
// constructor that always throws. We never need to emit it.
unneededClasses.add(compiler.boolClass);
// Go over specialized interceptors and then constants to know which
// interceptors are needed.
Set<ClassElement> needed = new Set<ClassElement>();
backend.specializedGetInterceptors.forEach(
(_, Iterable<ClassElement> elements) {
needed.addAll(elements);
}
);
// Add interceptors referenced by constants.
needed.addAll(computeInterceptorsReferencedFromConstants());
// Add unneeded interceptors to the [unneededClasses] set.
for (ClassElement interceptor in backend.interceptedClasses) {
if (!needed.contains(interceptor)
&& interceptor != compiler.objectClass) {
unneededClasses.add(interceptor);
}
}
// These classes are just helpers for the backend's type system.
unneededClasses.add(backend.jsMutableArrayClass);
unneededClasses.add(backend.jsFixedArrayClass);
unneededClasses.add(backend.jsExtendableArrayClass);
unneededClasses.add(backend.jsUInt32Class);
unneededClasses.add(backend.jsUInt31Class);
unneededClasses.add(backend.jsPositiveIntClass);
return (ClassElement cls) => !unneededClasses.contains(cls);
}
/**
* Compute all the constants that must be emitted.
*/
void computeNeededConstants() {
// Make sure we retain all metadata of all elements. This could add new
// constants to the handler.
if (backend.mustRetainMetadata) {
// TODO(floitsch): verify that we don't run through the same elements
// multiple times.
for (Element element in backend.generatedCode.keys) {
if (backend.isAccessibleByReflection(element)) {
bool shouldRetainMetadata = backend.retainMetadataOf(element);
if (shouldRetainMetadata &&
(element.isFunction || element.isConstructor ||
element.isSetter)) {
FunctionElement function = element;
function.functionSignature.forEachParameter(
backend.retainMetadataOf);
}
}
}
for (ClassElement cls in neededClasses) {
final onlyForRti = classesOnlyNeededForRti.contains(cls);
if (!onlyForRti) {
backend.retainMetadataOf(cls);
oldEmitter.classEmitter.visitFields(cls, false,
(Element member,
jsAst.Name name,
jsAst.Name accessorName,
bool needsGetter,
bool needsSetter,
bool needsCheckedSetter) {
bool needsAccessor = needsGetter || needsSetter;
if (needsAccessor && backend.isAccessibleByReflection(member)) {
backend.retainMetadataOf(member);
}
});
}
}
typedefsNeededForReflection.forEach(backend.retainMetadataOf);
}
JavaScriptConstantCompiler handler = backend.constants;
List<ConstantValue> constants = handler.getConstantsForEmission(
compiler.hasIncrementalSupport ? null : emitter.compareConstants);
for (ConstantValue constant in constants) {
if (emitter.isConstantInlinedOrAlreadyEmitted(constant)) continue;
if (constant.isList) outputContainsConstantList = true;
OutputUnit constantUnit =
compiler.deferredLoadTask.outputUnitForConstant(constant);
if (constantUnit == null) {
// The back-end introduces some constants, like "InterceptorConstant" or
// some list constants. They are emitted in the main output-unit.
// TODO(sigurdm): We should track those constants.
constantUnit = compiler.deferredLoadTask.mainOutputUnit;
}
outputConstantLists.putIfAbsent(
constantUnit, () => new List<ConstantValue>()).add(constant);
}
}
/// Compute all the classes and typedefs that must be emitted.
void computeNeededDeclarations(Set<ClassElement> rtiNeededClasses) {
// Compute needed typedefs.
typedefsNeededForReflection = Elements.sortedByPosition(
compiler.world.allTypedefs
.where(backend.isAccessibleByReflection)
.toList());
// Compute needed classes.
Set<ClassElement> instantiatedClasses =
compiler.codegenWorld.directlyInstantiatedClasses
.where(computeClassFilter()).toSet();
void addClassWithSuperclasses(ClassElement cls) {
neededClasses.add(cls);
for (ClassElement superclass = cls.superclass;
superclass != null;
superclass = superclass.superclass) {
neededClasses.add(superclass);
}
}
void addClassesWithSuperclasses(Iterable<ClassElement> classes) {
for (ClassElement cls in classes) {
addClassWithSuperclasses(cls);
}
}
// 1. We need to generate all classes that are instantiated.
addClassesWithSuperclasses(instantiatedClasses);
// 2. Add all classes used as mixins.
Set<ClassElement> mixinClasses = neededClasses
.where((ClassElement element) => element.isMixinApplication)
.map(computeMixinClass)
.toSet();
neededClasses.addAll(mixinClasses);
// 3. Find all classes needed for rti.
// It is important that this is the penultimate step, at this point,
// neededClasses must only contain classes that have been resolved and
// codegen'd. The rtiNeededClasses may contain additional classes, but
// these are thought to not have been instantiated, so we neeed to be able
// to identify them later and make sure we only emit "empty shells" without
// fields, etc.
classesOnlyNeededForRti = rtiNeededClasses.difference(neededClasses);
neededClasses.addAll(classesOnlyNeededForRti);
// TODO(18175, floitsch): remove once issue 18175 is fixed.
if (neededClasses.contains(backend.jsIntClass)) {
neededClasses.add(compiler.intClass);
}
if (neededClasses.contains(backend.jsDoubleClass)) {
neededClasses.add(compiler.doubleClass);
}
if (neededClasses.contains(backend.jsNumberClass)) {
neededClasses.add(compiler.numClass);
}
if (neededClasses.contains(backend.jsStringClass)) {
neededClasses.add(compiler.stringClass);
}
if (neededClasses.contains(backend.jsBoolClass)) {
neededClasses.add(compiler.boolClass);
}
if (neededClasses.contains(backend.jsArrayClass)) {
neededClasses.add(compiler.listClass);
}
// 4. Finally, sort the classes.
List<ClassElement> sortedClasses = Elements.sortedByPosition(neededClasses);
for (ClassElement element in sortedClasses) {
if (Elements.isNativeOrExtendsNative(element) &&
!classesOnlyNeededForRti.contains(element)) {
// For now, native classes and related classes cannot be deferred.
nativeClassesAndSubclasses.add(element);
assert(invariant(element,
!compiler.deferredLoadTask.isDeferred(element)));
outputClassLists.putIfAbsent(compiler.deferredLoadTask.mainOutputUnit,
() => new List<ClassElement>()).add(element);
} else {
outputClassLists.putIfAbsent(
compiler.deferredLoadTask.outputUnitForElement(element),
() => new List<ClassElement>())
.add(element);
}
}
}
void computeNeededStatics() {
bool isStaticFunction(Element element) =>
!element.isInstanceMember && !element.isField;
Iterable<Element> elements =
backend.generatedCode.keys.where(isStaticFunction);
for (Element element in Elements.sortedByPosition(elements)) {
List<Element> list = outputStaticLists.putIfAbsent(
compiler.deferredLoadTask.outputUnitForElement(element),
() => new List<Element>());
list.add(element);
}
}
void computeNeededStaticNonFinalFields() {
JavaScriptConstantCompiler handler = backend.constants;
Iterable<VariableElement> staticNonFinalFields = handler
.getStaticNonFinalFieldsForEmission()
.where(compiler.codegenWorld.allReferencedStaticFields.contains);
for (Element element in Elements.sortedByPosition(staticNonFinalFields)) {
List<VariableElement> list = outputStaticNonFinalFieldLists.putIfAbsent(
compiler.deferredLoadTask.outputUnitForElement(element),
() => new List<VariableElement>());
list.add(element);
}
}
void computeNeededLibraries() {
void addSurroundingLibraryToSet(Element element) {
OutputUnit unit = compiler.deferredLoadTask.outputUnitForElement(element);
LibraryElement library = element.library;
outputLibraryLists.putIfAbsent(unit, () => new Set<LibraryElement>())
.add(library);
}
backend.generatedCode.keys.forEach(addSurroundingLibraryToSet);
neededClasses.forEach(addSurroundingLibraryToSet);
}
void computeAllNeededEntities() {
// Compute the required type checks to know which classes need a
// 'is$' method.
typeTestRegistry.computeRequiredTypeChecks();
// Compute the classes needed by RTI.
Set<ClassElement> rtiClasses = typeTestRegistry.computeRtiNeededClasses();
computeNeededDeclarations(rtiClasses);
computeNeededConstants();
computeNeededStatics();
computeNeededStaticNonFinalFields();
computeNeededLibraries();
}
int assembleProgram() {
return measure(() {
emitter.invalidateCaches();
computeAllNeededEntities();
ProgramBuilder programBuilder = new ProgramBuilder(compiler, namer, this);
return emitter.emitProgram(programBuilder);
});
}
}
abstract class Emitter {
/// Uses the [programBuilder] to generate a model of the program, emits
/// the program, and returns the size of the generated output.
int emitProgram(ProgramBuilder programBuilder);
/// Returns the JS function that must be invoked to get the value of the
/// lazily initialized static.
jsAst.Expression isolateLazyInitializerAccess(FieldElement element);
/// Returns the closure expression of a static function.
jsAst.Expression isolateStaticClosureAccess(FunctionElement element);
/// Returns the JS code for accessing the embedded [global].
jsAst.Expression generateEmbeddedGlobalAccess(String global);
/// Returns the JS function representing the given function.
///
/// The function must be invoked and can not be used as closure.
jsAst.Expression staticFunctionAccess(FunctionElement element);
jsAst.Expression staticFieldAccess(FieldElement element);
/// Returns the JS constructor of the given element.
///
/// The returned expression must only be used in a JS `new` expression.
jsAst.Expression constructorAccess(ClassElement e);
/// Returns the JS prototype of the given class [e].
jsAst.Expression prototypeAccess(ClassElement e, bool hasBeenInstantiated);
/// Returns the JS constructor of the given interceptor class [e].
jsAst.Expression interceptorClassAccess(ClassElement e);
/// Returns the JS expression representing the type [e].
jsAst.Expression typeAccess(Element e);
/// Returns the JS expression representing a function that returns 'null'
jsAst.Expression generateFunctionThatReturnsNull();
int compareConstants(ConstantValue a, ConstantValue b);
bool isConstantInlinedOrAlreadyEmitted(ConstantValue constant);
/// Returns the JS code for accessing the given [constant].
jsAst.Expression constantReference(ConstantValue constant);
/// Returns the JS template for the given [builtin].
jsAst.Template templateForBuiltin(JsBuiltin builtin);
void invalidateCaches();
}