Index: pkg/compiler/lib/src/js_emitter/new_emitter/model_emitter.dart |
diff --git a/pkg/compiler/lib/src/js_emitter/new_emitter/model_emitter.dart b/pkg/compiler/lib/src/js_emitter/new_emitter/model_emitter.dart |
deleted file mode 100644 |
index 6a05e29f6d1782f59d45aeaa62af46b71baf7e92..0000000000000000000000000000000000000000 |
--- a/pkg/compiler/lib/src/js_emitter/new_emitter/model_emitter.dart |
+++ /dev/null |
@@ -1,1247 +0,0 @@ |
-// 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 '../../constants/values.dart' show ConstantValue, FunctionConstantValue; |
-import '../../dart2jslib.dart' show Compiler; |
-import '../../elements/elements.dart' show ClassElement, FunctionElement; |
-import '../../js/js.dart' as js; |
-import '../../js_backend/js_backend.dart' show |
- JavaScriptBackend, |
- Namer, |
- ConstantEmitter; |
- |
-import '../js_emitter.dart' show AstContainer, NativeEmitter; |
- |
-import 'package:js_runtime/shared/embedded_names.dart' show |
- CREATE_NEW_ISOLATE, |
- 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, |
- TYPES; |
- |
-import '../js_emitter.dart' show NativeGenerator, buildTearOffCode; |
-import '../model.dart'; |
- |
-class ModelEmitter { |
- final Compiler compiler; |
- final Namer namer; |
- 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"; |
- |
- static const String typeNameProperty = r"builtin$cls"; |
- |
- ModelEmitter(Compiler compiler, Namer namer, this.nativeEmitter) |
- : this.compiler = compiler, |
- this.namer = namer { |
- |
- this.constantEmitter = new ConstantEmitter( |
- compiler, namer, this.generateConstantReference, |
- constantListGenerator); |
- } |
- |
- js.Expression constantListGenerator(js.Expression array) { |
- // TODO(floitsch): remove hard-coded name. |
- return js.js('makeConstList(#)', [array]); |
- } |
- |
- js.Expression generateEmbeddedGlobalAccess(String global) { |
- return js.js(generateEmbeddedGlobalAccessString(global)); |
- } |
- |
- String generateEmbeddedGlobalAccessString(String global) { |
- // TODO(floitsch): don't use 'init' as global embedder storage. |
- return 'init.$global'; |
- } |
- |
- bool isConstantInlinedOrAlreadyEmitted(ConstantValue constant) { |
- if (constant.isFunction) return true; // Already emitted. |
- if (constant.isPrimitive) return true; // Inlined. |
- if (constant.isDummy) return true; // Inlined. |
- // The name is null when the constant is already a JS constant. |
- // TODO(floitsch): every constant should be registered, so that we can |
- // share the ones that take up too much space (like some strings). |
- if (namer.constantName(constant) == null) return true; |
- return false; |
- } |
- |
- // TODO(floitsch): copied from OldEmitter. Adjust or share. |
- int compareConstants(ConstantValue a, ConstantValue b) { |
- // Inlined constants don't affect the order and sometimes don't even have |
- // names. |
- int cmp1 = isConstantInlinedOrAlreadyEmitted(a) ? 0 : 1; |
- int cmp2 = isConstantInlinedOrAlreadyEmitted(b) ? 0 : 1; |
- if (cmp1 + cmp2 < 2) return cmp1 - cmp2; |
- |
- // Emit constant interceptors first. Constant interceptors for primitives |
- // might be used by code that builds other constants. See Issue 18173. |
- if (a.isInterceptor != b.isInterceptor) { |
- return a.isInterceptor ? -1 : 1; |
- } |
- |
- // Sorting by the long name clusters constants with the same constructor |
- // which compresses a tiny bit better. |
- int r = namer.constantLongName(a).compareTo(namer.constantLongName(b)); |
- if (r != 0) return r; |
- // Resolve collisions in the long name by using the constant name (i.e. JS |
- // name) which is unique. |
- return namer.constantName(a).compareTo(namer.constantName(b)); |
- } |
- |
- js.Expression generateStaticClosureAccess(FunctionElement element) { |
- return js.js('#.#()', |
- [namer.globalObjectFor(element), namer.staticClosureName(element)]); |
- } |
- |
- js.Expression generateConstantReference(ConstantValue value) { |
- if (value.isFunction) { |
- FunctionConstantValue functionConstant = value; |
- return generateStaticClosureAccess(functionConstant.element); |
- } |
- |
- // We are only interested in the "isInlined" part, but it does not hurt to |
- // test for the other predicates. |
- if (isConstantInlinedOrAlreadyEmitted(value)) { |
- return constantEmitter.generate(value); |
- } |
- return js.js('#.#()', [namer.globalObjectForConstant(value), |
- namer.constantName(value)]); |
- } |
- |
- int emitProgram(Program program) { |
- List<Fragment> fragments = program.fragments; |
- MainFragment mainFragment = fragments.first; |
- |
- int totalSize = 0; |
- |
- // We have to emit the deferred fragments first, since we need their |
- // deferred hash (which depends on the output) when emitting the main |
- // fragment. |
- List<js.Expression> fragmentsCode = fragments.skip(1).map( |
- (DeferredFragment deferredUnit) { |
- js.Expression types = |
- program.metadataTypesForOutputUnit(deferredUnit.outputUnit); |
- return emitDeferredFragment(types, deferredUnit, program.holders); |
- }).toList(); |
- |
- js.Statement mainAst = emitMainFragment(program); |
- |
- js.TokenCounter counter = new js.TokenCounter(); |
- fragmentsCode.forEach(counter.countTokens); |
- counter.countTokens(mainAst); |
- |
- program.finalizers.forEach((js.TokenFinalizer f) => f.finalizeTokens()); |
- |
- for (int i = 0; i < fragmentsCode.length; ++i) { |
- String code = js.prettyPrint(fragmentsCode[i], compiler).getText(); |
- totalSize += code.length; |
- compiler.outputProvider(fragments[i+1].outputFileName, deferredExtension) |
- ..add(code) |
- ..close(); |
- } |
- |
- String mainCode = js.prettyPrint(mainAst, compiler).getText(); |
- compiler.outputProvider(mainFragment.outputFileName, 'js') |
- ..add(buildGeneratedBy(compiler)) |
- ..add(mainCode) |
- ..close(); |
- totalSize += mainCode.length; |
- |
- return totalSize; |
- } |
- |
- /// Returns a [js.Literal] that represents the string result of unparsing |
- /// [value]. |
- /// |
- /// The returned node will, when its string value is requested, pretty-print |
- /// the given [value] and, if [protectForEval] is true, wrap the resulting |
- /// string in parenthesis. The result is also escaped. |
- /// |
- /// See [_UnparsedNode] for details. |
- js.Literal unparse(Compiler compiler, js.Node value, |
- {bool protectForEval: true}) { |
- return new js.UnparsedNode(value, compiler, protectForEval); |
- } |
- |
- 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)); |
- elements.add(emitConstants(fragment.constants)); |
- |
- js.Expression code = new js.ArrayInitializer(elements); |
- |
- Map<String, dynamic> holes = |
- {'deferredInitializer': emitDeferredInitializerGlobal(program.loadMap), |
- 'holders': emitHolders(program.holders), |
- 'tearOff': buildTearOffCode(backend), |
- 'parseFunctionDescriptor': |
- js.js.statement(parseFunctionDescriptorBoilerplate, |
- {'argumentCount': js.string(namer.requiredParameterField), |
- 'defaultArgumentValues': js.string(namer.defaultValuesField), |
- 'callName': js.string(namer.callNameField)}), |
- |
- 'cyclicThrow': |
- backend.emitter.staticFunctionAccess(backend.getCyclicThrowHelper()), |
- 'outputContainsConstantList': program.outputContainsConstantList, |
- 'embeddedGlobals': emitEmbeddedGlobals(program), |
- 'readMetadataTypeFunction': readMetadataTypeFunction, |
- 'staticNonFinals': |
- emitStaticNonFinalFields(fragment.staticNonFinalFields), |
- 'operatorIsPrefix': js.string(namer.operatorIsPrefix), |
- 'callName': js.string(namer.callNameField), |
- 'argumentCount': js.string(namer.requiredParameterField), |
- 'defaultArgumentValues': js.string(namer.defaultValuesField), |
- 'eagerClasses': emitEagerClassInitializations(fragment.libraries), |
- 'invokeMain': fragment.invokeMain, |
- '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 = NativeGenerator.buildNativeInfoHandler( |
- nativeInfoAccess, |
- constructorAccess, |
- subclassReadGenerator, |
- interceptorsByTagAccess, |
- leafTagsAccess); |
- |
- nativeHoles['needsNativeSupport'] = program.needsNativeSupport; |
- nativeHoles['needsNoNativeSupport'] = !program.needsNativeSupport; |
- 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); |
- } |
- |
- js.Block emitEmbeddedGlobals(Program program) { |
- List<js.Property> globals = <js.Property>[]; |
- |
- if (program.loadMap.isNotEmpty) { |
- globals.addAll(emitEmbeddedGlobalsForDeferredLoading(program.loadMap)); |
- } |
- |
- if (program.typeToInterceptorMap != null) { |
- globals.add(new js.Property(js.string(TYPE_TO_INTERCEPTOR_MAP), |
- program.typeToInterceptorMap)); |
- } |
- |
- if (program.hasIsolateSupport) { |
- String isolateName = namer.currentIsolate; |
- globals.add( |
- new js.Property(js.string(CREATE_NEW_ISOLATE), |
- js.js('function () { return $isolateName; }'))); |
- // TODO(floitsch): add remaining isolate functions. |
- } |
- |
- globals.add(emitMangledGlobalNames()); |
- |
- globals.add(emitGetTypeFromName()); |
- |
- globals.addAll(emitMetadata(program)); |
- |
- if (program.needsNativeSupport) { |
- 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.quoteName(namer.className(element)), |
- js.string(element.name))); |
- }); |
- |
- return new js.Property(js.string(MANGLED_GLOBAL_NAMES), |
- new js.ObjectInitializer(names)); |
- } |
- |
- 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);"""); |
- } |
- |
- Iterable<js.Property> emitEmbeddedGlobalsForDeferredLoading( |
- Map<String, List<Fragment>> loadMap) { |
- |
- List<js.Property> globals = <js.Property>[]; |
- |
- js.ArrayInitializer fragmentUris(List<Fragment> fragments) { |
- return js.stringArray(fragments.map((DeferredFragment fragment) => |
- "${fragment.outputFileName}.$deferredExtension")); |
- } |
- js.ArrayInitializer fragmentHashes(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), fragmentUris(fragmentList)); |
- hashes[count] = |
- new js.Property(js.string(loadId), fragmentHashes(fragmentList)); |
- count++; |
- }); |
- |
- globals.add(new js.Property(js.string(DEFERRED_LIBRARY_URIS), |
- new js.ObjectInitializer(uris))); |
- globals.add(new js.Property(js.string(DEFERRED_LIBRARY_HASHES), |
- new js.ObjectInitializer(hashes))); |
- |
- js.Expression isHunkLoadedFunction = |
- js.js("function(hash) { return !!$deferredInitializersGlobal[hash]; }"); |
- globals.add(new js.Property(js.string(IS_HUNK_LOADED), |
- isHunkLoadedFunction)); |
- |
- js.Expression isHunkInitializedFunction = |
- js.js("function(hash) { return false; }"); |
- globals.add(new js.Property(js.string(IS_HUNK_INITIALIZED), |
- isHunkInitializedFunction)); |
- |
- js.Expression typesAccess = generateEmbeddedGlobalAccess(TYPES); |
- |
- /// See [emitEmbeddedGlobalsForDeferredLoading] for the format of the |
- /// deferred hunk. |
- js.Expression initializeLoadedHunkFunction = |
- js.js(""" |
- function(hash) { |
- var hunk = $deferredInitializersGlobal[hash]; |
- $setupProgramName(hunk[0], #typesAccess.length); |
- eval(hunk[1]); |
- var deferredTypes = eval(hunk[2]); |
- #typesAccess.push.apply(#typesAccess, deferredTypes); |
- }""", {'typesAccess': typesAccess}); |
- |
- globals.add(new js.Property(js.string(INITIALIZE_LOADED_HUNK), |
- initializeLoadedHunkFunction)); |
- |
- return globals; |
- } |
- |
- 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); |
- } |
- |
- static final String readMetadataTypeName = "readMetadataType"; |
- |
- js.Statement get readMetadataTypeFunction { |
- // Types are non-evaluated and must be compiled at first use. |
- // Compiled strings are guaranteed not to be strings, and it's thus safe |
- // to use a type-test to determine if a type has already been compiled. |
- return js.js.statement('''function $readMetadataTypeName(index) { |
- var type = #typesAccess[index]; |
- if (typeof type == 'string') { |
- type = expressionCompile(type); |
- #typesAccess[index] = type; |
- } |
- return type; |
- }''', {"typesAccess": generateEmbeddedGlobalAccess(TYPES)}); |
- } |
- |
- js.Template get templateForReadType { |
- // TODO(floitsch): make sure that no local variable shadows the access to |
- // the readMetadataType function. |
- return js.js.expressionTemplateFor('$readMetadataTypeName(#)'); |
- } |
- |
- static final String readMetadataName = "readLazyMetadata"; |
- static final String lazyMetadataName = "lazy_$METADATA"; |
- |
- js.Statement get readMetadataFunction { |
- // Types are non-evaluated and must be compiled at first use. |
- // Compiled strings are guaranteed not to be strings, and it's thus safe |
- // to use a type-test to determine if a type has already been compiled. |
- return js.js.statement('''function $readMetadataName(index) { |
- var lazyMetadata = #lazyMetadataAccess[index]; |
- if (typeof lazyMetadata == 'string') { |
- #metadataAccess[index] = expressionCompile(lazyMetadata); |
- #lazyMetadataAccess[index] = null; |
- } |
- return #metadataAccess[index]; |
- }''', { |
- "lazyMetadataAccess": generateEmbeddedGlobalAccess(lazyMetadataName), |
- "metadataAccess": generateEmbeddedGlobalAccess(METADATA) |
- }); |
- } |
- |
- js.Template get templateForReadMetadata { |
- // TODO(floitsch): make sure that no local variable shadows the access to |
- // the readMetadata function. |
- return js.js.expressionTemplateFor('$readMetadataName(#)'); |
- } |
- |
- List<js.Property> emitMetadata(Program program) { |
- List<js.Property> metadataGlobals = <js.Property>[]; |
- |
- js.Property createGlobal(js.Expression metadata, String global) { |
- return new js.Property(js.string(global), metadata); |
- } |
- |
- metadataGlobals.add(createGlobal(program.metadata, METADATA)); |
- js.Expression types = |
- program.metadataTypesForOutputUnit(program.mainFragment.outputUnit); |
- metadataGlobals.add(createGlobal(types, TYPES)); |
- |
- return metadataGlobals; |
- } |
- |
- js.Expression emitDeferredFragment(js.Expression deferredTypes, |
- DeferredFragment fragment, |
- List<Holder> holders) { |
- // TODO(floitsch): initialize eager classes. |
- // TODO(floitsch): the hash must depend on the output. |
- int hash = fragment.hashCode; |
- |
- List<js.Expression> deferredCode = |
- fragment.libraries.map(emitLibrary).toList(); |
- |
- deferredCode.add( |
- emitLazilyInitializedStatics(fragment.staticLazilyInitializedFields)); |
- deferredCode.add(emitConstants(fragment.constants)); |
- |
- js.ArrayInitializer deferredArray = new js.ArrayInitializer(deferredCode); |
- |
- // This is the code that must be evaluated after all deferred classes have |
- // been setup. |
- js.Statement immediateCode = new js.Block([ |
- emitStaticNonFinalFields(fragment.staticNonFinalFields), |
- emitEagerClassInitializations(fragment.libraries)]); |
- |
- |
- js.Literal immediateString = unparse(compiler, immediateCode); |
- |
- js.ArrayInitializer hunk = |
- new js.ArrayInitializer([deferredArray, immediateString, |
- deferredTypes]); |
- |
- return js.js("$deferredInitializersGlobal[$hash] = #", hunk); |
- } |
- |
- // This string should be referenced wherever JavaScript code makes assumptions |
- // on the constants format. |
- static final String constantsDescription = |
- "The constants are encoded as a follows:" |
- " [constantsHolderIndex, name, code, name2, code2, ...]." |
- "The array is completely empty if there is no constant at all."; |
- |
- js.ArrayInitializer emitConstants(List<Constant> constants) { |
- List<js.Expression> data = <js.Expression>[]; |
- if (constants.isNotEmpty) { |
- int holderIndex = constants.first.holder.index; |
- data.add(js.number(holderIndex)); |
- data.addAll(constants.expand((Constant constant) { |
- assert(constant.holder.index == holderIndex); |
- js.Expression code = constantEmitter.generate(constant.value); |
- return [js.quoteName(constant.name), unparse(compiler, code)]; |
- })); |
- } |
- return new js.ArrayInitializer(data); |
- } |
- |
- 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.quoteName(field.name), |
- js.quoteName(namer.deriveLazyInitializerName(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.Literal name = js.quoteName(cls.name); |
- js.LiteralNumber holderIndex = js.number(cls.holder.index); |
- js.Expression emittedClass = emitClass(cls); |
- js.Expression nativeInfo = NativeGenerator.encodeNativeInfo(cls); |
- if (nativeInfo == null) { |
- return [name, emittedClass, holderIndex]; |
- } else { |
- return [name, emittedClass, 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<js.Name> fieldNames = const <js.Name>[]; |
- |
- // 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(); |
- } |
- js.Name name = cls.name; |
- |
- Iterable<js.Name> assignments = fieldNames.map((js.Name field) { |
- return js.js("this.#field = #field", {"field": field}); |
- }); |
- |
- return js.js('function #(#) { # }', [name, fieldNames, assignments]); |
- } |
- |
- 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.quoteName(field.name); |
- js.Expression code = js.js(getterTemplateFor(field.getterFlags), fieldName); |
- js.Name getterName = namer.deriveGetterName(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.quoteName(field.name); |
- js.Expression code = js.js(setterTemplateFor(field.setterFlags), fieldName); |
- js.Name setterName = namer.deriveSetterName(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 a reference to their mixin class at the place of the usual" |
- "constructor. If they are instantiated the constructor follows the" |
- "reference."; |
- |
- js.Expression emitClass(Class cls) { |
- List elements = [js.quoteName(cls.superclassName, allowNull: true), |
- js.number(cls.superclassHolderIndex)]; |
- |
- if (cls.isMixinApplication) { |
- MixinApplication mixin = cls; |
- elements.add(js.quoteName(mixin.mixinClass.name)); |
- elements.add(js.number(mixin.mixinClass.holder.index)); |
- if (cls.isDirectlyInstantiated) { |
- elements.add(_generateConstructor(cls)); |
- } |
- } else { |
- elements.add(_generateConstructor(cls)); |
- } |
- Iterable<Method> methods = cls.methods; |
- Iterable<Method> isChecks = cls.isChecks; |
- Iterable<Method> callStubs = cls.callStubs; |
- Iterable<Method> typeVariableReaderStubs = cls.typeVariableReaderStubs; |
- Iterable<Method> noSuchMethodStubs = cls.noSuchMethodStubs; |
- Iterable<Method> gettersSetters = _generateGettersSetters(cls); |
- Iterable<Method> allMethods = |
- [methods, isChecks, callStubs, typeVariableReaderStubs, |
- 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: |
- /// |
- /// * [InstanceMethod.aliasName] (optional). |
- /// * [Method.code] |
- /// * [DartMethod.callName] |
- /// * isInterceptedMethod (optional, present if [DartMethod.needsTearOff]). |
- /// * [DartMethod.tearOffName] (optional, present if |
- /// [DartMethod.needsTearOff]). |
- /// * functionType (optional, present if [DartMethod.needsTearOff]). |
- /// |
- /// followed by |
- /// |
- /// * [ParameterStubMethod.name] |
- /// * [ParameterStubMethod.callName] |
- /// * [ParameterStubMethod.code] |
- /// |
- /// for each stub in [DartMethod.parameterStubs]. |
- /// |
- /// If the closure could be used in `Function.apply` (i.e. |
- /// [DartMethod.canBeApplied] is true) then the following fields are appended: |
- /// |
- /// * [DartMethod.requiredParameterCount] |
- /// * [DartMethod.optionalParameterDefaultValues] |
- |
- static final String parseFunctionDescriptorBoilerplate = r""" |
-function parseFunctionDescriptor(proto, name, descriptor, typesOffset) { |
- if (descriptor instanceof Array) { |
- // 'pos' points to the last read entry. |
- var f, pos = -1; |
- var aliasOrFunction = descriptor[++pos]; |
- if (typeof aliasOrFunction == "string") { |
- // Install the alias for super calls on the prototype chain. |
- proto[aliasOrFunction] = f = descriptor[++pos]; |
- } else { |
- f = aliasOrFunction; |
- } |
- |
- proto[name] = f; |
- var funs = [f]; |
- f[#callName] = descriptor[++pos]; |
- |
- var isInterceptedOrParameterStubName = descriptor[pos + 1]; |
- var isIntercepted, tearOffName, reflectionInfo; |
- if (typeof isInterceptedOrParameterStubName == "boolean") { |
- isIntercepted = descriptor[++pos]; |
- tearOffName = descriptor[++pos]; |
- reflectionInfo = descriptor[++pos]; |
- if (typeof reflectionInfo == "number") { |
- reflectionInfo = reflectionInfo + typesOffset; |
- } |
- } |
- |
- // We iterate in blocks of 3 but have to stop before we reach the (optional) |
- // two trailing items. To accomplish this, we only iterate until we reach |
- // length - 2. |
- for (++pos; pos < descriptor.length - 2; pos += 3) { |
- var stub = descriptor[pos + 2]; |
- stub[#callName] = descriptor[pos + 1]; |
- proto[descriptor[pos]] = stub; |
- funs.push(stub); |
- } |
- |
- if (tearOffName) { |
- proto[tearOffName] = |
- tearOff(funs, reflectionInfo, false, name, isIntercepted); |
- } |
- if (pos < descriptor.length) { |
- f[#argumentCount] = descriptor[pos]; |
- f[#defaultArgumentValues] = descriptor[pos + 1]; |
- } |
- } else { |
- proto[name] = descriptor; |
- } |
-} |
-"""; |
- |
- js.Expression _encodeOptionalParameterDefaultValues(DartMethod method) { |
- // TODO(herhut): Replace [js.LiteralNull] with [js.ArrayHole]. |
- if (method.optionalParameterDefaultValues is List) { |
- List<ConstantValue> defaultValues = method.optionalParameterDefaultValues; |
- Iterable<js.Expression> elements = |
- defaultValues.map(generateConstantReference); |
- return new js.ArrayInitializer(elements.toList()); |
- } else { |
- Map<String, ConstantValue> defaultValues = |
- method.optionalParameterDefaultValues; |
- List<js.Property> properties = <js.Property>[]; |
- defaultValues.forEach((String name, ConstantValue value) { |
- properties.add(new js.Property(js.string(name), |
- generateConstantReference(value))); |
- }); |
- return new js.ObjectInitializer(properties); |
- } |
- } |
- |
- Iterable<js.Expression> emitInstanceMethod(Method method) { |
- |
- List<js.Expression> makeNameCodePair(Method method) { |
- return [js.quoteName(method.name), method.code]; |
- } |
- |
- List<js.Expression> makeNameCallNameCodeTriplet(ParameterStubMethod stub) { |
- js.Expression callName = stub.callName == null |
- ? new js.LiteralNull() |
- : js.quoteName(stub.callName); |
- return [js.quoteName(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, [aliasName, function, callName, isIntercepted, tearOffName, |
- // functionType, stub1_name, stub1_callName, stub1_code, ...] |
- var data = []; |
- if (method.aliasName != null) { |
- data.add(js.quoteName(method.aliasName)); |
- } |
- data.add(method.code); |
- data.add(js.quoteName(method.callName, allowNull: true)); |
- |
- if (method.needsTearOff) { |
- bool isIntercepted = backend.isInterceptedMethod(method.element); |
- data.add(new js.LiteralBool(isIntercepted)); |
- data.add(js.quoteName(method.tearOffName)); |
- data.add((method.functionType)); |
- } |
- |
- data.addAll(method.parameterStubs.expand(makeNameCallNameCodeTriplet)); |
- if (method.canBeApplied) { |
- data.add(js.number(method.requiredParameterCount)); |
- data.add(_encodeOptionalParameterDefaultValues(method)); |
- } |
- return [js.quoteName(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.quoteName(method.name)); |
- output.add(holderIndex); |
- output.add(unparsed); |
- } |
- |
- List<js.Expression> makeNameCallNameCodeTriplet(ParameterStubMethod stub) { |
- js.Expression callName = stub.callName == null |
- ? new js.LiteralNull() |
- : js.quoteName(stub.callName); |
- return [js.quoteName(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.quoteName(method.callName)); |
- data.add(js.quoteName(method.tearOffName)); |
- data.add(method.functionType); |
- data.addAll(method.parameterStubs.expand(makeNameCallNameCodeTriplet)); |
- if (method.canBeApplied) { |
- data.add(js.number(method.requiredParameterCount)); |
- data.add(_encodeOptionalParameterDefaultValues(method)); |
- } |
- return [js.quoteName(method.name), holderIndex, |
- new js.ArrayInitializer(data)]; |
- } else { |
- method.parameterStubs.forEach(_addMethod); |
- } |
- } |
- return output; |
- } |
- |
- static final String setupProgramName = "setupProgram"; |
- |
- 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 $setupProgramName(program, typesOffset) { |
- for (var i = 0; i < program.length - 2; i++) { |
- setupLibrary(program[i], typesOffset); |
- } |
- setupLazyStatics(program[i]); |
- setupConstants(program[i + 1]); |
- } |
- |
- function setupLibrary(library, typesOffset) { |
- 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], |
- typesOffset); |
- } |
- |
- var classes = library[1]; |
- for (var i = 0; i < classes.length; i += 3) { |
- var name = classes[i]; |
- var cls = classes[i + 1]; |
- |
- if (#needsNativeSupport) { |
- // $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 (#needsNoNativeSupport) { |
- var holderIndex = classes[i + 2]; |
- } |
- |
- holdersMap[name] = holders[holderIndex]; |
- setupClass(name, holders[holderIndex], cls, typesOffset); |
- } |
- } |
- |
- 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 setupConstants(constants) { |
- // $constantsDescription. |
- if (constants.length == 0) return; |
- // We assume that all constants are in the same holder. |
- var holder = holders[constants[0]]; |
- for (var i = 1; i < constants.length; i += 2) { |
- var name = constants[i]; |
- var initializer = constants[i + 1]; |
- setupConstant(name, holder, initializer); |
- } |
- } |
- |
- function setupStatic(name, holder, descriptor, typesOffset) { |
- if (typeof descriptor == 'string') { |
- holder[name] = function() { |
- if (descriptor == null) { |
- // Already compiled. This happens when we have calls to the static as |
- // arguments to the static: `foo(foo(499))`; |
- return holder[name].apply(this, arguments); |
- } |
- var method = compile(name, descriptor); |
- holder[name] = method; |
- descriptor = null; // GC the descriptor. |
- return method.apply(this, arguments); |
- }; |
- } else { |
- // Parse the tear off information and generate compile handlers. |
- // TODO(herhut): Share parser with instance methods. |
- function compileAllStubs(typesOffset) { |
- var funs; |
- var fun = compile(name, descriptor[0]); |
- fun[#callName] = descriptor[1]; |
- holder[name] = fun; |
- funs = [fun]; |
- // We iterate in blocks of 3 but have to stop before we reach the |
- // (optional) two trailing items. To accomplish this, we only iterate |
- // until we reach length - 2. |
- for (var pos = 4; pos < descriptor.length - 2; 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. |
- var reflectionInfo = descriptor[3]; |
- if (typeof reflectionInfo == "number") { |
- reflectionInfo = reflectionInfo + typesOffset; |
- } |
- holder[descriptor[2]] = |
- tearOff(funs, reflectionInfo, true, name, false); |
- } |
- if (pos < descriptor.length) { |
- fun[#argumentCount] = descriptor[pos]; |
- fun[#defaultArgumentValues] = descriptor[pos + 1]; |
- } |
- } |
- |
- function setupCompileAllAndDelegateStub(name, typesOffset) { |
- holder[name] = function() { |
- // The descriptor is null if we already compiled this function. This |
- // happens when we have calls to the static as arguments to the |
- // static: `foo(foo(499))`; |
- if (descriptor != null) { |
- compileAllStubs(typesOffset); |
- descriptor = null; // GC the descriptor. |
- } |
- return holder[name].apply(this, arguments); |
- }; |
- } |
- |
- setupCompileAllAndDelegateStub(name, typesOffset); |
- for (var pos = 4; pos < descriptor.length; pos += 3) { |
- setupCompileAllAndDelegateStub(descriptor[pos], typesOffset); |
- } |
- if (descriptor[2] != null) { // tear-off name. |
- setupCompileAllAndDelegateStub(descriptor[2], typesOffset) |
- } |
- } |
- } |
- |
- 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; |
- } |
- // TODO(floitsch): the function should probably be unique for each |
- // static. |
- holder[getterName] = function() { return this[name]; }; |
- } |
- return result; |
- }; |
- } |
- |
- function setupConstant(name, holder, descriptor) { |
- var c; |
- holder[name] = function() { |
- if (descriptor !== null) { |
- c = compile(name, descriptor); |
- name = null; |
- descriptor = null; |
- } |
- return c; |
- }; |
- } |
- |
- function setupClass(name, holder, descriptor, typesOffset) { |
- var patch = function() { |
- if (patch.ensureResolved == patch) { |
- // We have not yet been compiled. |
- var constructor = compileConstructor(name, descriptor, typesOffset); |
- holder[name] = constructor; |
- name = holder = descriptor = null; // GC the captured arguments. |
- // Make sure we can invoke 'ensureResolved' multiple times on the patch |
- // function. |
- patch.ensureResolved = function() { return constructor; }; |
- constructor.ensureResolved = function() { return this; }; |
- } else { |
- // This can happen when arguments to the constructor are of the same |
- // class, like in `new A(new A(null))`. |
- constructor = patch.ensureResolved(); |
- } |
- // If the patch has been called as "ensureResolved" return. |
- if (this === patch) return constructor; |
- var object = new constructor(); |
- constructor.apply(object, arguments); |
- return object; |
- }; |
- |
- // We store the patch function on itself to make it |
- // possible to resolve superclass references without constructing instances. |
- patch.ensureResolved = patch; |
- holder[name] = patch; |
- } |
- |
- #tearOff; |
- |
- #parseFunctionDescriptor; |
- |
- function compileConstructor(name, descriptor, typesOffset) { |
- descriptor = compile(name, descriptor); |
- var prototype = determinePrototype(descriptor); |
- var constructor; |
- var functionsIndex; |
- // $mixinFormatDescription. |
- if (typeof descriptor[2] !== 'function') { |
- fillPrototypeWithMixedIn(descriptor[2], descriptor[3], prototype); |
- // descriptor[4] contains the constructor if the mixin application is |
- // directly instantiated. |
- if (typeof descriptor[4] === 'function') { |
- constructor = descriptor[4]; |
- functionsIndex = 5; |
- } else { |
- constructor = function() {}; |
- functionsIndex = 4; |
- } |
- } else { |
- constructor = descriptor[2]; |
- functionsIndex = 3; |
- } |
- |
- for (var i = functionsIndex; i < descriptor.length; i += 2) { |
- parseFunctionDescriptor(prototype, descriptor[i], descriptor[i + 1], |
- typesOffset); |
- } |
- |
- constructor.$typeNameProperty = name; // Needed for RTI. |
- constructor.prototype = prototype; |
- prototype[#operatorIsPrefix + name] = constructor; |
- prototype.constructor = constructor; |
- return constructor; |
- } |
- |
- function fillPrototypeWithMixedIn(mixinName, mixinHolderIndex, prototype) { |
- 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]; |
- } |
- } |
- |
- 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 (#needsNativeSupport) { |
- function handleNativeClassInfos() { |
- for (var nativeClass in nativeInfos) { |
- var constructor = holdersMap[nativeClass][nativeClass].ensureResolved(); |
- var nativeInfo = nativeInfos[nativeClass]; |
- #nativeInfoHandler; |
- } |
- } |
- } |
- |
- $setupProgramName(program, 0); |
- |
- // Initialize globals. |
- #embeddedGlobals; |
- |
- function expressionCompile(__s__) { |
- 'use strict'; |
- return eval('(' + __s__ + ')'); |
- } |
- |
- #readMetadataTypeFunction; |
- |
- // 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 (#needsNativeSupport) 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.'); |
- |
- #invokeMain; // Start main. |
- |
-})(Date.now(), #code) |
-}"""; |
- |
-} |