| // 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 dart2js.js_emitter.program_builder; |
| |
| import 'js_emitter.dart' show computeMixinClass; |
| import 'model.dart'; |
| |
| import '../common.dart'; |
| import '../js/js.dart' as js; |
| |
| import '../js_backend/js_backend.dart' show |
| Namer, |
| JavaScriptBackend, |
| JavaScriptConstantCompiler; |
| |
| import '../closure.dart' show ClosureFieldElement; |
| |
| import 'js_emitter.dart' as emitterTask show |
| CodeEmitterTask, |
| Emitter, |
| InterceptorStubGenerator, |
| TypeTestGenerator, |
| TypeTestProperties; |
| |
| import '../universe/universe.dart' show Universe; |
| import '../deferred_load.dart' show DeferredLoadTask, OutputUnit; |
| |
| part 'registry.dart'; |
| |
| class ProgramBuilder { |
| final Compiler _compiler; |
| final Namer namer; |
| final emitterTask.CodeEmitterTask _task; |
| |
| final Registry _registry; |
| |
| ProgramBuilder(Compiler compiler, |
| this.namer, |
| this._task) |
| : this._compiler = compiler, |
| this._registry = new Registry(compiler); |
| |
| JavaScriptBackend get backend => _compiler.backend; |
| Universe get universe => _compiler.codegenWorld; |
| |
| /// Mapping from [ClassElement] to constructed [Class]. We need this to |
| /// update the superclass in the [Class]. |
| final Map<ClassElement, Class> _classes = <ClassElement, Class>{}; |
| |
| /// Mapping from [OutputUnit] to constructed [Output]. We need this to |
| /// generate the deferredLoadingMap (to know which hunks to load). |
| final Map<OutputUnit, Output> _outputs = <OutputUnit, Output>{}; |
| |
| /// Mapping from [ConstantValue] to constructed [Constant]. We need this to |
| /// update field-initializers to point to the ConstantModel. |
| final Map<ConstantValue, Constant> _constants = <ConstantValue, Constant>{}; |
| |
| Program buildProgram() { |
| _task.outputClassLists.forEach(_registry.registerElements); |
| _task.outputStaticLists.forEach(_registry.registerElements); |
| _task.outputConstantLists.forEach(_registerConstants); |
| |
| // TODO(kasperl): There's code that implicitly needs access to the special |
| // $ holder so we have to register that. Can we track if we have to? |
| _registry.registerHolder(r'$'); |
| |
| MainOutput mainOutput = _buildMainOutput(_registry.mainFragment); |
| Iterable<Output> deferredOutputs = _registry.deferredFragments |
| .map((fragment) => _buildDeferredOutput(mainOutput, fragment)); |
| |
| List<Output> outputs = new List<Output>(_registry.fragmentCount); |
| outputs[0] = mainOutput; |
| outputs.setAll(1, deferredOutputs); |
| |
| Program result = |
| new Program(outputs, _task.outputContainsConstantList, _buildLoadMap()); |
| |
| // Resolve the superclass references after we've processed all the classes. |
| _classes.forEach((ClassElement element, Class c) { |
| if (element.superclass != null) { |
| c.setSuperclass(_classes[element.superclass]); |
| } |
| if (element.isMixinApplication) { |
| MixinApplication mixinApplication = c; |
| mixinApplication.setMixinClass(_classes[computeMixinClass(element)]); |
| } |
| }); |
| |
| _markEagerClasses(); |
| |
| return result; |
| } |
| |
| void _markEagerClasses() { |
| _markEagerInterceptorClasses(); |
| } |
| |
| /// Builds a map from loadId to outputs-to-load. |
| Map<String, List<Output>> _buildLoadMap() { |
| List<OutputUnit> convertHunks(List<OutputUnit> hunks) { |
| return hunks.map((OutputUnit unit) => _outputs[unit]) |
| .toList(growable: false); |
| } |
| |
| Map<String, List<Output>> loadMap = <String, List<Output>>{}; |
| _compiler.deferredLoadTask.hunksToLoad |
| .forEach((String loadId, List<OutputUnit> outputUnits) { |
| loadMap[loadId] = outputUnits |
| .map((OutputUnit unit) => _outputs[unit]) |
| .toList(growable: false); |
| }); |
| return loadMap; |
| } |
| |
| MainOutput _buildMainOutput(Fragment fragment) { |
| // Construct the main output from the libraries and the registered holders. |
| MainOutput result = new MainOutput( |
| "", // The empty string is the name for the main output file. |
| backend.emitter.staticFunctionAccess(_compiler.mainFunction), |
| _buildLibraries(fragment), |
| _buildStaticNonFinalFields(fragment), |
| _buildStaticLazilyInitializedFields(fragment), |
| _buildConstants(fragment), |
| _registry.holders.toList(growable: false)); |
| _outputs[fragment.outputUnit] = result; |
| return result; |
| } |
| |
| /// Returns a name composed of the main output file name and [name]. |
| String _outputFileName(String name) { |
| assert(name != ""); |
| String outPath = _compiler.outputUri != null |
| ? _compiler.outputUri.path |
| : "out"; |
| String outName = outPath.substring(outPath.lastIndexOf('/') + 1); |
| return "${outName}_$name"; |
| } |
| |
| DeferredOutput _buildDeferredOutput(MainOutput mainOutput, |
| Fragment fragment) { |
| DeferredOutput result = new DeferredOutput( |
| _outputFileName(fragment.name), fragment.name, |
| mainOutput, |
| _buildLibraries(fragment), |
| _buildStaticNonFinalFields(fragment), |
| _buildStaticLazilyInitializedFields(fragment), |
| _buildConstants(fragment)); |
| _outputs[fragment.outputUnit] = result; |
| return result; |
| } |
| |
| List<Constant> _buildConstants(Fragment fragment) { |
| List<ConstantValue> constantValues = |
| _task.outputConstantLists[fragment.outputUnit]; |
| if (constantValues == null) return const <Constant>[]; |
| return constantValues.map((ConstantValue value) => _constants[value]) |
| .toList(growable: false); |
| } |
| |
| List<StaticField> _buildStaticNonFinalFields(Fragment fragment) { |
| // TODO(floitsch): handle static non-final fields correctly with deferred |
| // libraries. |
| if (fragment != _registry.mainFragment) return const <StaticField>[]; |
| Iterable<VariableElement> staticNonFinalFields = |
| backend.constants.getStaticNonFinalFieldsForEmission(); |
| return Elements.sortedByPosition(staticNonFinalFields) |
| .map(_buildStaticField) |
| .toList(growable: false); |
| } |
| |
| StaticField _buildStaticField(Element element) { |
| JavaScriptConstantCompiler handler = backend.constants; |
| ConstantValue initialValue = handler.getInitialValueFor(element).value; |
| js.Expression code = _task.emitter.constantReference(initialValue); |
| String name = namer.getNameOfGlobalField(element); |
| bool isFinal = false; |
| bool isLazy = false; |
| return new StaticField(element, |
| name, _registry.registerHolder(r'$'), code, |
| isFinal, isLazy); |
| } |
| |
| List<StaticField> _buildStaticLazilyInitializedFields(Fragment fragment) { |
| // TODO(floitsch): lazy fields should just be in their respective |
| // libraries. |
| if (fragment != _registry.mainFragment) return const <StaticField>[]; |
| |
| JavaScriptConstantCompiler handler = backend.constants; |
| List<VariableElement> lazyFields = |
| handler.getLazilyInitializedFieldsForEmission(); |
| return Elements.sortedByPosition(lazyFields) |
| .map(_buildLazyField) |
| .where((field) => field != null) // Happens when the field was unused. |
| .toList(growable: false); |
| } |
| |
| StaticField _buildLazyField(Element element) { |
| JavaScriptConstantCompiler handler = backend.constants; |
| js.Expression code = backend.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; |
| |
| String name = namer.getNameOfGlobalField(element); |
| bool isFinal = element.isFinal; |
| bool isLazy = true; |
| return new StaticField(element, |
| name, _registry.registerHolder(r'$'), code, |
| isFinal, isLazy); |
| } |
| |
| List<Library> _buildLibraries(Fragment fragment) { |
| List<Library> libraries = new List<Library>(fragment.length); |
| int count = 0; |
| fragment.forEach((LibraryElement library, List<Element> elements) { |
| libraries[count++] = _buildLibrary(library, elements); |
| }); |
| return libraries; |
| } |
| |
| // Note that a library-element may have multiple [Library]s, if it is split |
| // into multiple output units. |
| Library _buildLibrary(LibraryElement library, List<Element> elements) { |
| String uri = library.canonicalUri.toString(); |
| |
| List<StaticMethod> statics = elements |
| .where((e) => e is FunctionElement).map(_buildStaticMethod).toList(); |
| |
| statics.addAll(elements |
| .where((e) => e is FunctionElement) |
| .where((e) => universe.staticFunctionsNeedingGetter.contains(e)) |
| .map(_buildStaticMethodTearOff)); |
| |
| if (library == backend.interceptorsLibrary) { |
| statics.addAll(_generateGetInterceptorMethods()); |
| statics.addAll(_generateOneShotInterceptors()); |
| } |
| |
| List<Class> classes = elements |
| .where((e) => e is ClassElement) |
| .map(_buildClass) |
| .toList(growable: false); |
| |
| return new Library(library, uri, statics, classes); |
| } |
| |
| Class _buildClass(ClassElement element) { |
| List<Method> methods = []; |
| List<InstanceField> fields = []; |
| |
| void visitMember(ClassElement enclosing, Element member) { |
| assert(invariant(element, member.isDeclaration)); |
| assert(invariant(element, element == enclosing)); |
| |
| if (Elements.isNonAbstractInstanceMember(member)) { |
| js.Expression code = backend.generatedCode[member]; |
| // TODO(kasperl): Figure out under which conditions code is null. |
| if (code != null) methods.add(_buildMethod(member, code)); |
| } else if (member.isField && !member.isStatic) { |
| fields.add(_buildInstanceField(member, enclosing)); |
| } |
| } |
| |
| ClassElement implementation = element.implementation; |
| |
| // MixinApplications run through the members of their mixin. Here, we are |
| // only interested in direct members. |
| if (!element.isMixinApplication) { |
| implementation.forEachMember(visitMember, includeBackendMembers: true); |
| } |
| |
| emitterTask.TypeTestGenerator generator = |
| new emitterTask.TypeTestGenerator(_compiler, _task, namer); |
| emitterTask.TypeTestProperties typeTests = |
| generator.generateIsTests(element); |
| |
| // At this point a mixin application must not have any methods or fields. |
| // Type-tests might be added to mixin applications, too. |
| assert(!element.isMixinApplication || methods.isEmpty); |
| assert(!element.isMixinApplication || fields.isEmpty); |
| |
| // TODO(floitsch): we should not add the code here, but have a list of |
| // is/as classes in the Class object. |
| // The individual emitters should then call the type test generator to |
| // generate the code. |
| typeTests.properties.forEach((String name, js.Node code) { |
| methods.add(_buildStubMethod(name, code)); |
| }); |
| |
| String name = namer.getNameOfClass(element); |
| String holderName = namer.globalObjectFor(element); |
| Holder holder = _registry.registerHolder(holderName); |
| bool onlyForRti = _task.typeTestRegistry.rtiNeededClasses.contains(element); |
| bool isInstantiated = |
| _compiler.codegenWorld.directlyInstantiatedClasses.contains(element); |
| |
| Class result; |
| if (element.isMixinApplication) { |
| result = new MixinApplication(element, |
| name, holder, methods, fields, |
| isDirectlyInstantiated: isInstantiated, |
| onlyForRti: onlyForRti); |
| } else { |
| result = new Class(element, |
| name, holder, methods, fields, |
| isDirectlyInstantiated: isInstantiated, |
| onlyForRti: onlyForRti); |
| } |
| _classes[element] = result; |
| return result; |
| } |
| |
| Method _buildMethod(FunctionElement element, js.Expression code) { |
| String name = namer.getNameOfInstanceMember(element); |
| return new Method(element, name, code); |
| } |
| |
| Method _buildStubMethod(String name, js.Expression code) { |
| return new StubMethod(name, code); |
| } |
| |
| // The getInterceptor methods directly access the prototype of classes. |
| // We must evaluate these classes eagerly so that the prototype is |
| // accessible. |
| void _markEagerInterceptorClasses() { |
| Map<String, Set<ClassElement>> specializedGetInterceptors = |
| backend.specializedGetInterceptors; |
| for (Set<ClassElement> classes in specializedGetInterceptors.values) { |
| for (ClassElement element in classes) { |
| Class cls = _classes[element]; |
| if (cls != null) cls.isEager = true; |
| } |
| } |
| } |
| |
| Iterable<StaticMethod> _generateGetInterceptorMethods() { |
| emitterTask.InterceptorStubGenerator stubGenerator = |
| new emitterTask.InterceptorStubGenerator(_compiler, namer, backend); |
| |
| String holderName = namer.globalObjectFor(backend.interceptorsLibrary); |
| Holder holder = _registry.registerHolder(holderName); |
| |
| Map<String, Set<ClassElement>> specializedGetInterceptors = |
| backend.specializedGetInterceptors; |
| List<String> names = specializedGetInterceptors.keys.toList()..sort(); |
| return names.map((String name) { |
| Set<ClassElement> classes = specializedGetInterceptors[name]; |
| js.Expression code = stubGenerator.generateGetInterceptorMethod(classes); |
| return new StaticStubMethod(name, holder, code); |
| }); |
| } |
| |
| bool _fieldNeedsGetter(VariableElement field) { |
| assert(field.isField); |
| if (_fieldAccessNeverThrows(field)) return false; |
| return backend.shouldRetainGetter(field) |
| || _compiler.codegenWorld.hasInvokedGetter(field, _compiler.world); |
| } |
| |
| bool _fieldNeedsSetter(VariableElement field) { |
| assert(field.isField); |
| if (_fieldAccessNeverThrows(field)) return false; |
| return (!field.isFinal && !field.isConst) |
| && (backend.shouldRetainSetter(field) |
| || _compiler.codegenWorld.hasInvokedSetter(field, _compiler.world)); |
| } |
| |
| // We never access a field in a closure (a captured variable) without knowing |
| // that it is there. Therefore we don't need to use a getter (that will throw |
| // if the getter method is missing), but can always access the field directly. |
| bool _fieldAccessNeverThrows(VariableElement field) { |
| return field is ClosureFieldElement; |
| } |
| |
| InstanceField _buildInstanceField(VariableElement field, |
| ClassElement holder) { |
| assert(invariant(field, field.isDeclaration)); |
| String name = namer.fieldPropertyName(field); |
| |
| int getterFlags = 0; |
| if (_fieldNeedsGetter(field)) { |
| bool isIntercepted = backend.fieldHasInterceptedGetter(field); |
| if (isIntercepted) { |
| getterFlags += 2; |
| if (backend.isInterceptorClass(holder)) { |
| getterFlags += 1; |
| } |
| } else { |
| getterFlags = 1; |
| } |
| } |
| |
| int setterFlags = 0; |
| if (_fieldNeedsSetter(field)) { |
| bool isIntercepted = backend.fieldHasInterceptedSetter(field); |
| if (isIntercepted) { |
| setterFlags += 2; |
| if (backend.isInterceptorClass(holder)) { |
| setterFlags += 1; |
| } |
| } else { |
| setterFlags = 1; |
| } |
| } |
| |
| return new InstanceField(field, name, getterFlags, setterFlags); |
| } |
| |
| Iterable<StaticMethod> _generateOneShotInterceptors() { |
| emitterTask.InterceptorStubGenerator stubGenerator = |
| new emitterTask.InterceptorStubGenerator(_compiler, namer, backend); |
| |
| String holderName = namer.globalObjectFor(backend.interceptorsLibrary); |
| Holder holder = _registry.registerHolder(holderName); |
| |
| List<String> names = backend.oneShotInterceptors.keys.toList()..sort(); |
| return names.map((String name) { |
| js.Expression code = stubGenerator.generateOneShotInterceptor(name); |
| return new StaticStubMethod(name, holder, code); |
| }); |
| } |
| |
| StaticMethod _buildStaticMethod(FunctionElement element) { |
| String name = namer.getNameOfMember(element); |
| String holder = namer.globalObjectFor(element); |
| js.Expression code = backend.generatedCode[element]; |
| return new StaticMethod(element, |
| name, _registry.registerHolder(holder), code); |
| } |
| |
| StaticMethod _buildStaticMethodTearOff(FunctionElement element) { |
| String name = namer.getStaticClosureName(element); |
| String holder = namer.globalObjectFor(element); |
| // TODO(kasperl): This clearly doesn't work yet. |
| js.Expression code = js.string("<<unimplemented>>"); |
| return new StaticMethod(element, |
| name, _registry.registerHolder(holder), code); |
| } |
| |
| void _registerConstants(OutputUnit outputUnit, |
| List<ConstantValue> constantValues) { |
| if (constantValues == null) return; |
| for (ConstantValue constantValue in constantValues) { |
| assert(!_constants.containsKey(constantValue)); |
| String name = namer.constantName(constantValue); |
| String constantObject = namer.globalObjectForConstant(constantValue); |
| Holder holder = _registry.registerHolder(constantObject); |
| Constant constant = new Constant(name, holder, constantValue); |
| _constants[constantValue] = constant; |
| }; |
| } |
| } |