| // 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.new_js_emitter.model_emitter; |
| |
| import '../../dart2jslib.dart' show Compiler; |
| import '../../dart_types.dart' show DartType; |
| import '../../elements/elements.dart' show ClassElement; |
| import '../../js/js.dart' as js; |
| import '../../js_backend/js_backend.dart' show |
| JavaScriptBackend, |
| Namer, |
| ConstantEmitter; |
| |
| import '../js_emitter.dart' show |
| NativeEmitter; |
| |
| import 'package:_internal/compiler/js_lib/shared/embedded_names.dart' show |
| DEFERRED_LIBRARY_URIS, |
| DEFERRED_LIBRARY_HASHES, |
| GET_TYPE_FROM_NAME, |
| INITIALIZE_LOADED_HUNK, |
| INTERCEPTORS_BY_TAG, |
| IS_HUNK_INITIALIZED, |
| IS_HUNK_LOADED, |
| LEAF_TAGS, |
| MANGLED_GLOBAL_NAMES, |
| METADATA, |
| TYPE_TO_INTERCEPTOR_MAP; |
| |
| import '../js_emitter.dart' show NativeGenerator, buildTearOffCode; |
| import '../model.dart'; |
| |
| |
| |
| class ModelEmitter { |
| final Compiler compiler; |
| final Namer namer; |
| final ConstantEmitter constantEmitter; |
| final NativeEmitter nativeEmitter; |
| |
| JavaScriptBackend get backend => compiler.backend; |
| |
| /// For deferred loading we communicate the initializers via this global var. |
| static const String deferredInitializersGlobal = |
| r"$__dart_deferred_initializers__"; |
| |
| static const String deferredExtension = "part.js"; |
| |
| ModelEmitter(Compiler compiler, Namer namer, this.nativeEmitter) |
| : this.compiler = compiler, |
| this.namer = namer, |
| constantEmitter = |
| new ConstantEmitter(compiler, namer, makeConstantListTemplate); |
| |
| js.Expression generateEmbeddedGlobalAccess(String global) { |
| // TODO(floitsch): We should not use "init" for globals. |
| return js.js("init.$global"); |
| } |
| |
| int emitProgram(Program program) { |
| List<Fragment> fragments = program.fragments; |
| MainFragment mainFragment = fragments.first; |
| js.Statement mainAst = emitMainFragment(program); |
| String mainCode = js.prettyPrint(mainAst, compiler).getText(); |
| compiler.outputProvider(mainFragment.outputFileName, 'js') |
| ..add(buildGeneratedBy(compiler)) |
| ..add(mainCode) |
| ..close(); |
| int totalSize = mainCode.length; |
| |
| fragments.skip(1).forEach((DeferredFragment deferredUnit) { |
| js.Expression ast = |
| emitDeferredFragment(deferredUnit, mainFragment.holders); |
| String code = js.prettyPrint(ast, compiler).getText(); |
| totalSize += code.length; |
| compiler.outputProvider(deferredUnit.outputFileName, deferredExtension) |
| ..add(code) |
| ..close(); |
| }); |
| return totalSize; |
| } |
| |
| js.LiteralString unparse(Compiler compiler, js.Expression value) { |
| String text = js.prettyPrint(value, compiler).getText(); |
| if (value is js.Fun) text = '($text)'; |
| return js.js.escapedString(text); |
| } |
| |
| String buildGeneratedBy(compiler) { |
| var suffix = ''; |
| if (compiler.hasBuildId) suffix = ' version: ${compiler.buildId}'; |
| return '// Generated by dart2js, the Dart to JavaScript compiler$suffix.\n'; |
| } |
| |
| js.Statement emitMainFragment(Program program) { |
| MainFragment fragment = program.fragments.first; |
| List<js.Expression> elements = fragment.libraries.map(emitLibrary).toList(); |
| elements.add( |
| emitLazilyInitializedStatics(fragment.staticLazilyInitializedFields)); |
| |
| js.Expression code = new js.ArrayInitializer(elements); |
| |
| Map<String, dynamic> holes = |
| {'deferredInitializer': emitDeferredInitializerGlobal(program.loadMap), |
| 'holders': emitHolders(fragment.holders), |
| 'tearOff': buildTearOffCode(backend), |
| 'parseFunctionDescriptor': |
| js.js.statement(parseFunctionDescriptorBoilerplate), |
| 'cyclicThrow': |
| backend.emitter.staticFunctionAccess(backend.getCyclicThrowHelper()), |
| 'outputContainsConstantList': program.outputContainsConstantList, |
| 'embeddedGlobals': emitEmbeddedGlobals(program), |
| 'constants': emitConstants(fragment.constants), |
| 'staticNonFinals': |
| emitStaticNonFinalFields(fragment.staticNonFinalFields), |
| 'operatorIsPrefix': js.string(namer.operatorIsPrefix), |
| 'eagerClasses': emitEagerClassInitializations(fragment.libraries), |
| 'main': fragment.main, |
| 'code': code}; |
| |
| holes.addAll(nativeHoles(program)); |
| |
| return js.js.statement(boilerplate, holes); |
| } |
| |
| Map<String, dynamic> nativeHoles(Program program) { |
| Map<String, dynamic> nativeHoles = <String, dynamic>{}; |
| |
| js.Statement nativeIsolateAffinityTagInitialization; |
| if (NativeGenerator.needsIsolateAffinityTagInitialization(backend)) { |
| nativeIsolateAffinityTagInitialization = |
| NativeGenerator.generateIsolateAffinityTagInitialization( |
| backend, |
| generateEmbeddedGlobalAccess, |
| // TODO(floitsch): convertToFastObject. |
| js.js("(function(x) { return x; })", [])); |
| } else { |
| nativeIsolateAffinityTagInitialization = js.js.statement(";"); |
| } |
| nativeHoles['nativeIsolateAffinityTagInitialization'] = |
| nativeIsolateAffinityTagInitialization; |
| |
| |
| js.Expression nativeInfoAccess = js.js('nativeInfo', []); |
| js.Expression constructorAccess = js.js('constructor', []); |
| Function subclassReadGenerator = (js.Expression subclass) { |
| return js.js('holdersMap[#][#].ensureResolved()', [subclass, subclass]); |
| }; |
| js.Expression interceptorsByTagAccess = |
| generateEmbeddedGlobalAccess(INTERCEPTORS_BY_TAG); |
| js.Expression leafTagsAccess = |
| generateEmbeddedGlobalAccess(LEAF_TAGS); |
| js.Statement nativeInfoHandler = nativeEmitter.buildNativeInfoHandler( |
| nativeInfoAccess, |
| constructorAccess, |
| subclassReadGenerator, |
| interceptorsByTagAccess, |
| leafTagsAccess); |
| |
| nativeHoles['hasNativeClasses'] = program.outputContainsNativeClasses; |
| nativeHoles['hasNoNativeClasses'] = !program.outputContainsNativeClasses; |
| nativeHoles['nativeInfoHandler'] = nativeInfoHandler; |
| |
| return nativeHoles; |
| } |
| |
| js.Block emitHolders(List<Holder> holders) { |
| // The top-level variables for holders must *not* be renamed by the |
| // JavaScript pretty printer because a lot of code already uses the |
| // non-renamed names. The generated code looks like this: |
| // |
| // var H = {}, ..., G = {}; |
| // var holders = [ H, ..., G ]; |
| // |
| // and it is inserted at the top of the top-level function expression |
| // that covers the entire program. |
| |
| List<js.Statement> statements = [ |
| new js.ExpressionStatement( |
| new js.VariableDeclarationList(holders.map((e) => |
| new js.VariableInitialization( |
| new js.VariableDeclaration(e.name, allowRename: false), |
| new js.ObjectInitializer(const []))).toList())), |
| js.js.statement('var holders = #', new js.ArrayInitializer( |
| holders.map((e) => new js.VariableUse(e.name)) |
| .toList(growable: false))), |
| js.js.statement('var holdersMap = Object.create(null)') |
| ]; |
| return new js.Block(statements); |
| } |
| |
| static js.Template get makeConstantListTemplate { |
| // TODO(floitsch): remove hard-coded name. |
| // TODO(floitsch): there is no harm in caching the template. |
| return js.js.uncachedExpressionTemplate('makeConstList(#)'); |
| } |
| |
| js.Block emitEmbeddedGlobals(Program program) { |
| List<js.Property> globals = <js.Property>[]; |
| |
| if (program.loadMap.isNotEmpty) { |
| globals.addAll(emitLoadUrisAndHashes(program.loadMap)); |
| globals.add(emitIsHunkLoadedFunction()); |
| globals.add(emitInitializeLoadedHunk()); |
| } |
| |
| if (program.typeToInterceptorMap != null) { |
| globals.add(new js.Property(js.string(TYPE_TO_INTERCEPTOR_MAP), |
| program.typeToInterceptorMap)); |
| } |
| |
| globals.add(emitMangledGlobalNames()); |
| |
| globals.add(emitGetTypeFromName()); |
| |
| globals.add(emitMetadata(program)); |
| |
| if (program.outputContainsNativeClasses) { |
| globals.add(new js.Property(js.string(INTERCEPTORS_BY_TAG), |
| js.js('Object.create(null)', []))); |
| globals.add(new js.Property(js.string(LEAF_TAGS), |
| js.js('Object.create(null)', []))); |
| } |
| |
| js.ObjectInitializer globalsObject = new js.ObjectInitializer(globals); |
| |
| List<js.Statement> statements = |
| [new js.ExpressionStatement( |
| new js.VariableDeclarationList( |
| [new js.VariableInitialization( |
| new js.VariableDeclaration("init", allowRename: false), |
| globalsObject)]))]; |
| return new js.Block(statements); |
| } |
| |
| js.Property emitMangledGlobalNames() { |
| List<js.Property> names = <js.Property>[]; |
| |
| // We want to keep the original names for the most common core classes when |
| // calling toString on them. |
| List<ClassElement> nativeClassesNeedingUnmangledName = |
| [compiler.intClass, compiler.doubleClass, compiler.numClass, |
| compiler.stringClass, compiler.boolClass, compiler.nullClass, |
| compiler.listClass]; |
| nativeClassesNeedingUnmangledName.forEach((element) { |
| names.add(new js.Property(js.string(namer.getNameOfClass(element)), |
| js.string(element.name))); |
| }); |
| |
| return new js.Property(js.string(MANGLED_GLOBAL_NAMES), |
| new js.ObjectInitializer(names)); |
| } |
| |
| List<js.Property> emitLoadUrisAndHashes(Map<String, List<Fragment>> loadMap) { |
| js.ArrayInitializer outputUris(List<Fragment> fragments) { |
| return js.stringArray(fragments.map((DeferredFragment fragment) => |
| "${fragment.outputFileName}$deferredExtension")); |
| } |
| js.ArrayInitializer outputHashes(List<Fragment> fragments) { |
| // TODO(floitsch): the hash must depend on the generated code. |
| return js.numArray( |
| fragments.map((DeferredFragment fragment) => fragment.hashCode)); |
| } |
| |
| List<js.Property> uris = new List<js.Property>(loadMap.length); |
| List<js.Property> hashes = new List<js.Property>(loadMap.length); |
| int count = 0; |
| loadMap.forEach((String loadId, List<Fragment> fragmentList) { |
| uris[count] = |
| new js.Property(js.string(loadId), outputUris(fragmentList)); |
| hashes[count] = |
| new js.Property(js.string(loadId), outputHashes(fragmentList)); |
| count++; |
| }); |
| |
| return <js.Property>[ |
| new js.Property(js.string(DEFERRED_LIBRARY_URIS), |
| new js.ObjectInitializer(uris)), |
| new js.Property(js.string(DEFERRED_LIBRARY_HASHES), |
| new js.ObjectInitializer(hashes)) |
| ]; |
| } |
| |
| js.Statement emitDeferredInitializerGlobal(Map loadMap) { |
| if (loadMap.isEmpty) return new js.Block.empty(); |
| |
| return js.js.statement(""" |
| if (typeof($deferredInitializersGlobal) === 'undefined') |
| var $deferredInitializersGlobal = Object.create(null);"""); |
| } |
| |
| js.Property emitIsHunkLoadedFunction() { |
| js.Expression function = |
| js.js("function(hash) { return !!$deferredInitializersGlobal[hash]; }"); |
| return new js.Property(js.string(IS_HUNK_LOADED), function); |
| } |
| |
| js.Property emitInitializeLoadedHunk() { |
| js.Expression function = |
| js.js("function(hash) { eval($deferredInitializersGlobal[hash]); }"); |
| return new js.Property(js.string(INITIALIZE_LOADED_HUNK), function); |
| } |
| |
| js.Property emitGetTypeFromName() { |
| js.Expression function = |
| js.js( """function(name) { |
| return holdersMap[name][name].ensureResolved(); |
| }"""); |
| return new js.Property(js.string(GET_TYPE_FROM_NAME), function); |
| } |
| |
| js.Property emitMetadata(Program program) { |
| String metadataList = "[${program.metadata.join(",")}]"; |
| js.Expression metadata = |
| js.js.uncachedExpressionTemplate(metadataList).instantiate([]); |
| |
| return new js.Property(js.string(METADATA), metadata); |
| } |
| |
| js.Expression emitDeferredFragment(DeferredFragment fragment, |
| List<Holder> holders) { |
| // TODO(floitsch): initialize eager classes. |
| // TODO(floitsch): the hash must depend on the output. |
| int hash = this.hashCode; |
| if (fragment.constants.isNotEmpty) { |
| throw new UnimplementedError("constants in deferred units"); |
| } |
| js.ArrayInitializer content = |
| new js.ArrayInitializer(fragment.libraries.map(emitLibrary) |
| .toList(growable: false)); |
| return js.js("$deferredInitializersGlobal[$hash] = #", content); |
| } |
| |
| js.Block emitConstants(List<Constant> constants) { |
| Iterable<js.Statement> statements = constants.map((Constant constant) { |
| js.Expression code = |
| constantEmitter.initializationExpression(constant.value); |
| return js.js.statement("#.# = #;", |
| [constant.holder.name, constant.name, code]); |
| }); |
| return new js.Block(statements.toList()); |
| } |
| |
| js.Block emitStaticNonFinalFields(List<StaticField> fields) { |
| Iterable<js.Statement> statements = fields.map((StaticField field) { |
| return js.js.statement("#.# = #;", |
| [field.holder.name, field.name, field.code]); |
| }); |
| return new js.Block(statements.toList()); |
| } |
| |
| js.Expression emitLazilyInitializedStatics(List<StaticField> fields) { |
| Iterable fieldDescriptors = fields.expand((field) => |
| [ js.string(field.name), |
| js.string("${namer.getterPrefix}${field.name}"), |
| js.number(field.holder.index), |
| emitLazyInitializer(field) ]); |
| return new js.ArrayInitializer(fieldDescriptors.toList(growable: false)); |
| } |
| |
| js.Block emitEagerClassInitializations(List<Library> libraries) { |
| js.Statement createInstantiation(Class cls) { |
| return js.js.statement('new #.#()', [cls.holder.name, cls.name]); |
| } |
| |
| List<js.Statement> instantiations = |
| libraries.expand((Library library) => library.classes) |
| .where((Class cls) => cls.isEager) |
| .map(createInstantiation) |
| .toList(growable: false); |
| return new js.Block(instantiations); |
| } |
| |
| // This string should be referenced wherever JavaScript code makes assumptions |
| // on the mixin format. |
| static final String nativeInfoDescription = |
| "A class is encoded as follows:" |
| " [name, class-code, holder-index], or " |
| " [name, class-code, native-info, holder-index]."; |
| |
| js.Expression emitLibrary(Library library) { |
| Iterable staticDescriptors = library.statics.expand(emitStaticMethod); |
| |
| Iterable classDescriptors = library.classes.expand((Class cls) { |
| js.LiteralString name = js.string(cls.name); |
| js.LiteralNumber holderIndex = js.number(cls.holder.index); |
| js.Expression emittedClass = emitClass(cls); |
| if (cls.nativeInfo == null) { |
| return [name, emittedClass, holderIndex]; |
| } else { |
| return [name, emittedClass, js.string(cls.nativeInfo), holderIndex]; |
| } |
| }); |
| |
| js.Expression staticArray = |
| new js.ArrayInitializer(staticDescriptors.toList(growable: false)); |
| js.Expression classArray = |
| new js.ArrayInitializer(classDescriptors.toList(growable: false)); |
| |
| return new js.ArrayInitializer([staticArray, classArray]); |
| } |
| |
| js.Expression _generateConstructor(Class cls) { |
| List<String> fieldNames = <String>[]; |
| |
| // If the class is not directly instantiated we only need it for inheritance |
| // or RTI. In either case we don't need its fields. |
| if (cls.isDirectlyInstantiated && !cls.isNative) { |
| fieldNames = cls.fields.map((Field field) => field.name).toList(); |
| } |
| String name = cls.name; |
| String parameters = fieldNames.join(', '); |
| String assignments = fieldNames |
| .map((String field) => "this.$field = $field;\n") |
| .join(); |
| String code = 'function $name($parameters) { $assignments }'; |
| js.Template template = js.js.uncachedExpressionTemplate(code); |
| return template.instantiate(const []); |
| } |
| |
| Method _generateGetter(Field field) { |
| String getterTemplateFor(int flags) { |
| switch (flags) { |
| case 1: return "function() { return this[#]; }"; |
| case 2: return "function(receiver) { return receiver[#]; }"; |
| case 3: return "function(receiver) { return this[#]; }"; |
| } |
| return null; |
| } |
| |
| js.Expression fieldName = js.string(field.name); |
| js.Expression code = js.js(getterTemplateFor(field.getterFlags), fieldName); |
| String getterName = "${namer.getterPrefix}${field.accessorName}"; |
| return new StubMethod(getterName, code); |
| } |
| |
| Method _generateSetter(Field field) { |
| String setterTemplateFor(int flags) { |
| switch (flags) { |
| case 1: return "function(val) { return this[#] = val; }"; |
| case 2: return "function(receiver, val) { return receiver[#] = val; }"; |
| case 3: return "function(receiver, val) { return this[#] = val; }"; |
| } |
| return null; |
| } |
| js.Expression fieldName = js.string(field.name); |
| js.Expression code = js.js(setterTemplateFor(field.setterFlags), fieldName); |
| String setterName = "${namer.setterPrefix}${field.accessorName}"; |
| return new StubMethod(setterName, code); |
| } |
| |
| Iterable<Method> _generateGettersSetters(Class cls) { |
| Iterable<Method> getters = cls.fields |
| .where((Field field) => field.needsGetter) |
| .map(_generateGetter); |
| |
| Iterable<Method> setters = cls.fields |
| .where((Field field) => field.needsUncheckedSetter) |
| .map(_generateSetter); |
| |
| return [getters, setters].expand((x) => x); |
| } |
| |
| // This string should be referenced wherever JavaScript code makes assumptions |
| // on the mixin format. |
| static final String mixinFormatDescription = |
| "Mixins have no constructor, but a reference to their mixin class."; |
| |
| js.Expression emitClass(Class cls) { |
| List elements = [js.string(cls.superclassName), |
| js.number(cls.superclassHolderIndex)]; |
| |
| if (cls.isMixinApplication) { |
| MixinApplication mixin = cls; |
| elements.add(js.string(mixin.mixinClass.name)); |
| elements.add(js.number(mixin.mixinClass.holder.index)); |
| } else { |
| elements.add(_generateConstructor(cls)); |
| } |
| Iterable<Method> methods = cls.methods; |
| Iterable<Method> isChecks = cls.isChecks; |
| Iterable<Method> callStubs = cls.callStubs; |
| Iterable<Method> noSuchMethodStubs = cls.noSuchMethodStubs; |
| Iterable<Method> gettersSetters = _generateGettersSetters(cls); |
| Iterable<Method> allMethods = |
| [methods, isChecks, callStubs, noSuchMethodStubs, gettersSetters] |
| .expand((x) => x); |
| elements.addAll(allMethods.expand(emitInstanceMethod)); |
| |
| return unparse(compiler, new js.ArrayInitializer(elements)); |
| } |
| |
| js.Expression emitLazyInitializer(StaticField field) { |
| assert(field.isLazy); |
| return unparse(compiler, field.code); |
| } |
| |
| /// JavaScript code template that implements parsing of a function descriptor. |
| /// Descriptors are used in place of the actual JavaScript function |
| /// definition in the output if additional information needs to be passed to |
| /// facilitate the generation of tearOffs at runtime. The format is an array |
| /// with the following fields: |
| /// |
| /// [Method.code] |
| /// [DartMethod.callName] |
| /// [DartMethod.tearOffName] |
| /// [JavaScriptBackend.isInterceptedMethod] |
| /// functionType |
| /// [InstanceMethod.aliasName] |
| /// |
| /// followed by |
| /// |
| /// [ParameterStubMethod.name] |
| /// [ParameterStubMethod.code] |
| /// |
| /// for each stub in [DartMethod.parameterStubs]. |
| |
| static final String parseFunctionDescriptorBoilerplate = r""" |
| function parseFunctionDescriptor(proto, name, descriptor) { |
| if (descriptor instanceof Array) { |
| proto[name] = descriptor[0]; |
| var funs = [descriptor[0]]; |
| funs[0].$callName = descriptor[1]; |
| for (var pos = 6; pos < descriptor.length; pos += 3) { |
| var stub = descriptor[pos + 2]; |
| stub.$callName = descriptor[pos + 1]; |
| proto[descriptor[pos]] = stub; |
| funs.push(stub); |
| } |
| if (descriptor[2] != null) { |
| var isIntercepted = descriptor[3]; |
| var reflectionInfo = descriptor[4]; |
| proto[descriptor[2]] = |
| tearOff(funs, reflectionInfo, false, name, isIntercepted); |
| } |
| // Install the alias for super calls on the prototype chain. |
| if (descriptor[5] != null) { |
| proto[descriptor[5]] = descriptor[0]; |
| } |
| } else { |
| proto[name] = descriptor; |
| } |
| } |
| """; |
| |
| js.Expression _generateFunctionType(DartType memberType) { |
| if (memberType.containsTypeVariables) { |
| js.Expression thisAccess = js.js(r'this.$receiver'); |
| return backend.rti.getSignatureEncoding(memberType, thisAccess); |
| } else { |
| return js.number(backend.emitter.metadataCollector.reifyType(memberType)); |
| } |
| } |
| |
| Iterable<js.Expression> emitInstanceMethod(Method method) { |
| |
| List<js.Expression> makeNameCodePair(Method method) { |
| return [js.string(method.name), method.code]; |
| } |
| |
| List<js.Expression> makeNameCallNameCodeTriplet(ParameterStubMethod stub) { |
| js.Expression callName = stub.callName == null |
| ? new js.LiteralNull() |
| : js.string(stub.callName); |
| return [js.string(stub.name), callName, stub.code]; |
| } |
| |
| if (method is InstanceMethod) { |
| if (method.needsTearOff || method.aliasName != null) { |
| /// See [parseFunctionDescriptorBoilerplate] for a full description of |
| /// the format. |
| // [name, [function, callName, tearOffName, isIntercepted, functionType, |
| // aliasName, stub1_name, stub1_callName, stub1_code, ...] |
| bool isIntercepted = backend.isInterceptedMethod(method.element); |
| var data = [method.code]; |
| data.add(js.string(method.callName)); |
| data.add(js.string(method.tearOffName)); |
| data.add(new js.LiteralBool(isIntercepted)); |
| data.add(_generateFunctionType(method.type)); |
| if (method.aliasName != null) { |
| data.add(js.string(method.aliasName)); |
| } else { |
| data.add(new js.LiteralNull()); |
| } |
| data.addAll(method.parameterStubs.expand(makeNameCallNameCodeTriplet)); |
| return [js.string(method.name), new js.ArrayInitializer(data)]; |
| } else { |
| // TODO(floitsch): not the most efficient way... |
| return ([method]..addAll(method.parameterStubs)) |
| .expand(makeNameCodePair); |
| } |
| } else { |
| return makeNameCodePair(method); |
| } |
| } |
| |
| Iterable<js.Expression> emitStaticMethod(StaticMethod method) { |
| js.Expression holderIndex = js.number(method.holder.index); |
| List<js.Expression> output = <js.Expression>[]; |
| |
| void _addMethod(Method method) { |
| js.Expression unparsed = unparse(compiler, method.code); |
| output.add(js.string(method.name)); |
| output.add(holderIndex); |
| output.add(unparsed); |
| } |
| |
| List<js.Expression> makeNameCallNameCodeTriplet(ParameterStubMethod stub) { |
| js.Expression callName = stub.callName == null |
| ? new js.LiteralNull() |
| : js.string(stub.callName); |
| return [js.string(stub.name), callName, unparse(compiler, stub.code)]; |
| } |
| |
| _addMethod(method); |
| // TODO(floitsch): can there be anything else than a StaticDartMethod? |
| if (method is StaticDartMethod) { |
| if (method.needsTearOff) { |
| /// The format emitted is the same as for the parser specified at |
| /// [parseFunctionDescriptorBoilerplate] except for the missing |
| /// field whether the method is intercepted. |
| // [name, [function, callName, tearOffName, functionType, |
| // stub1_name, stub1_callName, stub1_code, ...] |
| var data = [unparse(compiler, method.code)]; |
| data.add(js.string(method.callName)); |
| data.add(js.string(method.tearOffName)); |
| data.add(_generateFunctionType(method.type)); |
| data.addAll(method.parameterStubs.expand(makeNameCallNameCodeTriplet)); |
| return [js.string(method.name), holderIndex, |
| new js.ArrayInitializer(data)]; |
| } else { |
| method.parameterStubs.forEach(_addMethod); |
| } |
| } |
| return output; |
| } |
| |
| static final String boilerplate = """ |
| { |
| // Declare deferred-initializer global. |
| #deferredInitializer; |
| |
| !function(start, program) { |
| // Initialize holder objects. |
| #holders; |
| var nativeInfos = Object.create(null); |
| |
| // Counter to generate unique names for tear offs. |
| var functionCounter = 0; |
| |
| function setupProgram() { |
| for (var i = 0; i < program.length - 1; i++) { |
| setupLibrary(program[i]); |
| } |
| setupLazyStatics(program[i]); |
| } |
| |
| function setupLibrary(library) { |
| var statics = library[0]; |
| for (var i = 0; i < statics.length; i += 3) { |
| var holderIndex = statics[i + 1]; |
| setupStatic(statics[i], holders[holderIndex], statics[i + 2]); |
| } |
| |
| var classes = library[1]; |
| for (var i = 0; i < classes.length; i += 3) { |
| var name = classes[i]; |
| var cls = classes[i + 1]; |
| |
| if (#hasNativeClasses) { |
| // $nativeInfoDescription. |
| var indexOrNativeInfo = classes[i + 2]; |
| if (typeof indexOrNativeInfo == "number") { |
| var holderIndex = classes[i + 2]; |
| } else { |
| nativeInfos[name] = indexOrNativeInfo; |
| holderIndex = classes[i + 3]; |
| i++; |
| } |
| } |
| |
| if (#hasNoNativeClasses) { |
| var holderIndex = classes[i + 2]; |
| } |
| |
| holdersMap[name] = holders[holderIndex]; |
| setupClass(name, holders[holderIndex], cls); |
| } |
| } |
| |
| function setupLazyStatics(statics) { |
| for (var i = 0; i < statics.length; i += 4) { |
| var name = statics[i]; |
| var getterName = statics[i + 1]; |
| var holderIndex = statics[i + 2]; |
| var initializer = statics[i + 3]; |
| setupLazyStatic(name, getterName, holders[holderIndex], initializer); |
| } |
| } |
| |
| function setupStatic(name, holder, descriptor) { |
| if (typeof descriptor == 'string') { |
| holder[name] = function() { |
| var method = compile(name, descriptor); |
| holder[name] = method; |
| return method.apply(this, arguments); |
| }; |
| } else { |
| // Parse the tear off information and generate compile handlers. |
| // TODO(herhut): Share parser with instance methods. |
| function compileAllStubs() { |
| var funs; |
| var fun = compile(name, descriptor[0]); |
| fun.\$callName = descriptor[1]; |
| holder[name] = fun; |
| funs = [fun]; |
| for (var pos = 4; pos < descriptor.length; pos += 3) { |
| var stubName = descriptor[pos]; |
| fun = compile(stubName, descriptor[pos + 2]); |
| fun.\$callName = descriptor[pos + 1]; |
| holder[stubName] = fun; |
| funs.push(fun); |
| } |
| if (descriptor[2] != null) { // tear-off name. |
| // functions, reflectionInfo, isStatic, name, isIntercepted. |
| holder[descriptor[2]] = |
| tearOff(funs, descriptor[3], true, name, false); |
| } |
| } |
| |
| function setupCompileAllAndDelegateStub(name) { |
| holder[name] = function() { |
| compileAllStubs(); |
| return holder[name].apply(this, arguments); |
| }; |
| } |
| |
| setupCompileAllAndDelegateStub(name); |
| for (var pos = 4; pos < descriptor.length; pos += 3) { |
| setupCompileAllAndDelegateStub(descriptor[pos]); |
| } |
| if (descriptor[2] != null) { // tear-off name. |
| setupCompileAllAndDelegateStub(descriptor[2]) |
| } |
| } |
| } |
| |
| function setupLazyStatic(name, getterName, holder, descriptor) { |
| holder[name] = null; |
| holder[getterName] = function() { |
| var initializer = compile(name, descriptor); |
| holder[getterName] = function() { #cyclicThrow(name) }; |
| var result; |
| var sentinelInProgress = descriptor; |
| try { |
| result = holder[name] = sentinelInProgress; |
| result = holder[name] = initializer(); |
| } finally { |
| // Use try-finally, not try-catch/throw as it destroys the stack trace. |
| if (result === sentinelInProgress) { |
| // The lazy static (holder[name]) might have been set to a different |
| // value. According to spec we still have to reset it to null, if the |
| // initialization failed. |
| holder[name] = null; |
| } |
| holder[getterName] = function() { return this[name]; }; |
| } |
| return result; |
| }; |
| } |
| |
| function setupClass(name, holder, descriptor) { |
| var ensureResolved = function() { |
| var constructor = compileConstructor(name, descriptor); |
| holder[name] = constructor; |
| constructor.ensureResolved = function() { return this; }; |
| return constructor; |
| }; |
| |
| var patch = function() { |
| var constructor = ensureResolved(); |
| var object = new constructor(); |
| constructor.apply(object, arguments); |
| return object; |
| }; |
| |
| // We store the ensureResolved function on the patch function to make it |
| // possible to resolve superclass references without constructing instances. |
| patch.ensureResolved = ensureResolved; |
| holder[name] = patch; |
| } |
| |
| #tearOff; |
| |
| #parseFunctionDescriptor; |
| |
| function compileConstructor(name, descriptor) { |
| descriptor = compile(name, descriptor); |
| var prototype = determinePrototype(descriptor); |
| var constructor; |
| // $mixinFormatDescription. |
| if (typeof descriptor[2] !== 'function') { |
| constructor = compileMixinConstructor(name, prototype, descriptor); |
| for (var i = 4; i < descriptor.length; i += 2) { |
| parseFunctionDescriptor(prototype, descriptor[i], descriptor[i + 1]); |
| } |
| } else { |
| constructor = descriptor[2]; |
| for (var i = 3; i < descriptor.length; i += 2) { |
| parseFunctionDescriptor(prototype, descriptor[i], descriptor[i + 1]); |
| } |
| } |
| constructor.builtin\$cls = name; // Needed for RTI. |
| constructor.prototype = prototype; |
| prototype[#operatorIsPrefix + name] = constructor; |
| prototype.constructor = constructor; |
| return constructor; |
| } |
| |
| function compileMixinConstructor(name, prototype, descriptor) { |
| // $mixinFormatDescription. |
| var mixinName = descriptor[2]; |
| var mixinHolderIndex = descriptor[3]; |
| var mixin = holders[mixinHolderIndex][mixinName].ensureResolved(); |
| var mixinPrototype = mixin.prototype; |
| |
| // Fill the prototype with the mixin's properties. |
| var mixinProperties = Object.keys(mixinPrototype); |
| for (var i = 0; i < mixinProperties.length; i++) { |
| var p = mixinProperties[i]; |
| prototype[p] = mixinPrototype[p]; |
| } |
| // Since this is a mixin application the constructor will actually never |
| // be invoked. We only use its prototype for the application's subclasses. |
| var constructor = function() {}; |
| return constructor; |
| } |
| |
| function determinePrototype(descriptor) { |
| var superclassName = descriptor[0]; |
| if (!superclassName) return { }; |
| |
| // Look up the superclass constructor function in the right holder. |
| var holderIndex = descriptor[1]; |
| var superclass = holders[holderIndex][superclassName].ensureResolved(); |
| |
| // Create a new prototype object chained to the superclass prototype. |
| var intermediate = function() { }; |
| intermediate.prototype = superclass.prototype; |
| return new intermediate(); |
| } |
| |
| function compile(__name__, __s__) { |
| 'use strict'; |
| // TODO(floitsch): evaluate the performance impact of the string |
| // concatenations. |
| return eval(__s__ + "\\n//# sourceURL=" + __name__ + ".js"); |
| } |
| |
| if (#outputContainsConstantList) { |
| function makeConstList(list) { |
| // By assigning a function to the properties they become part of the |
| // hidden class. The actual values of the fields don't matter, since we |
| // only check if they exist. |
| list.immutable\$list = Array; |
| list.fixed\$length = Array; |
| return list; |
| } |
| } |
| |
| if (#hasNativeClasses) { |
| function handleNativeClassInfos() { |
| for (var nativeClass in nativeInfos) { |
| var constructor = holdersMap[nativeClass][nativeClass].ensureResolved(); |
| var nativeInfo = nativeInfos[nativeClass]; |
| #nativeInfoHandler; |
| } |
| } |
| } |
| |
| setupProgram(); |
| |
| // Initialize constants. |
| #constants; |
| |
| // Initialize globals. |
| #embeddedGlobals; |
| |
| // TODO(floitsch): this order means that native classes may not be |
| // referenced from constants. I'm mostly afraid of things like using them as |
| // generic arguments (which should be fine, but maybe there are other |
| // similar things). |
| // Initialize natives. |
| if (#hasNativeClasses) handleNativeClassInfos(); |
| |
| // Initialize static non-final fields. |
| #staticNonFinals; |
| |
| // Add native boilerplate code. |
| #nativeIsolateAffinityTagInitialization; |
| |
| // Initialize eager classes. |
| #eagerClasses; |
| |
| var end = Date.now(); |
| print('Setup: ' + (end - start) + ' ms.'); |
| |
| #main(); // Start main. |
| |
| }(Date.now(), #code) |
| }"""; |
| |
| } |