1 /* -*- mode: java; tab-width: 4; insert-tabs-mode: nil; indent-tabs-mode: nil -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is [Open Source Virtual Machine.].
17 * The Initial Developer of the Original Code is
18 * Adobe System Incorporated.
19 * Portions created by the Initial Developer are Copyright (C) 2004-2006
20 * the Initial Developer. All Rights Reserved.
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
41 The emitter provides abstractions for common code generation
42 patterns, and some arbitrary amount of utilities to help in code
43 generation. The client starts by creating an emitter, then
44 creating scripts on that emitter and classes and other traits on
45 those scripts, methods on the classes, and so on. Boilerplate code
46 is inserted for you, so code is generated for class creation when
47 you create a class on a script.
49 These sketches are particularly rough right now because the needs
50 of the code generator (verify.es) are not known precisely yet. But
51 I expect that there will be quite a bit of code in here, and it
52 will encapsulate a lot of useful knowledge about how things are
55 One thing I'm apparently unresolved on here is when to create structures
56 in the ABCFile; right now there's a mix of late and early. Since the
57 abcfile is not laid out until it is finalized this matters little, except
58 for the sake of clarity.
60 Sometimes this OO setup does not seem natural, other times it simplifies...
63 use default namespace Emit,
68 namespace Ast, // goes away
74 /*private*/ var scripts = [];
76 function ABCEmitter() {
78 constants = new ABCConstantPool;
79 file.addConstants(constants);
80 Object_name = nameFromIdent(Token::sym_Object);
81 Array_name = nameFromIdent(Token::sym_Array);
82 RegExp_name = nameFromIdent(Token::sym_RegExp);
85 function newScript(): Script {
86 var s = new Script(this);
92 function f(s) { s.finalize() }
100 var meta_construct_name;
104 * The "public public" namespace on the AVM, the one we wish to
105 * map to ES4 "public", is a CONSTANT_PackageNamespace with a name
106 * that is the empty string.
108 * The compiler inserts two definitions at the top of the file:
110 * <magic> namespace internal = <magic>
111 * internal namespace public = <the real public>
113 function namespace( ns: Ast::Namespace) {
115 case (pn: Ast::PrivateNamespace) {
116 return constants.namespace(CONSTANT_PrivateNamespace, constants.symbolUtf8(pn.name));
118 case (pn: Ast::ProtectedNamespace) {
119 return constants.namespace(CONSTANT_ProtectedNamespace, constants.symbolUtf8(pn.name));
121 case (pn: Ast::PublicNamespace) {
122 return constants.namespace(CONSTANT_Namespace, constants.symbolUtf8(pn.name));
124 case (int_ns: Ast::InternalNamespace) {
125 return constants.namespace(CONSTANT_PackageInternalNS, constants.symbolUtf8(int_ns.name));
127 case (un: Ast::ForgeableNamespace) {
128 /// return constants.namespace(CONSTANT_ExplicitNamespace, constants.stringUtf8(pn.name));
129 return constants.namespace(CONSTANT_Namespace, constants.symbolUtf8(un.name));
131 case (an: Ast::UnforgeableNamespace) {
132 /// return constants.namespace(CONSTANT_PackageInternalNS, constants.stringUtf8(an.name));
133 return constants.namespace(CONSTANT_Namespace, constants.symbolUtf8(an.name));
136 internalError("", 0, "Unimplemented namespace " + ns);
141 // The hit ratio of this cache is normally above 95%. It speeds
142 // up the back end by more than a factor of two. 128 is pretty
143 // random; a smaller number might work just as well.
145 // The reason it works so well is that the flattening of the
146 // namespace sets together with hashing them and looking them up
147 // to eliminate duplicates in the constant pool is quite
148 // expensive. Here we filter identical NamespaceSetLists so that
149 // the constant pool doesn't have to work so hard.
151 internal var cached_nssl = new Array(128);
152 internal var cached_id = new Array(128);
154 function flattenNamespaceSet(nssl: Ast::NamespaceSetList) {
156 for ( ; nssl != null ; nssl = nssl.link )
157 for ( let nss = nssl.nsset ; nss != null ; nss = nss.link )
158 new_nss.push(this.namespace(nss.ns));
162 function namespaceSetList(nssl) {
163 let h = nssl.hash & 127;
164 if (nssl !== cached_nssl[h]) {
165 cached_nssl[h] = nssl;
166 cached_id[h] = constants.namespaceset(flattenNamespaceSet(nssl));
171 function multiname(mname, is_attr) {
172 let {nss, ident} = mname;
173 return constants.Multiname(namespaceSetList(nss), constants.symbolUtf8(ident), is_attr);
176 function qname(qn, is_attr) {
178 return constants.QName(this.namespace(ns), constants.symbolUtf8(id), is_attr);
181 function nameFromIdent(id) {
182 return constants.QName(constants.namespace(CONSTANT_PackageNamespace, constants.symbolUtf8(Token::sym_EMPTY)),
183 constants.symbolUtf8(id),false);
186 function multinameL(nss, is_attr)
187 constants.MultinameL(namespaceSetList(nss), is_attr);
189 // This is a limited version of cgIdentExpr -- several pieces are
190 // just copies -- and all uses of this function should probably be replaced
191 // by uses of the other.
193 function nameFromIdentExpr(e) {
195 case (id: Ast::Identifier) {
196 return multiname(id,false);
198 case (qi: Ast::QualifiedIdentifier) {
199 switch type(qi.qual) {
200 case( lr: Ast::Identifier ) {
201 // FIXME: Hack to deal with namespaces for now.
202 // later we will have to implement a namespace lookup to resolve qualified typenames
203 return qname(new Ast::Name(new Ast::UnforgeableNamespace(lr.ident), qi.ident), false);
205 case (lr: Ast::ForgeableNamespace) {
206 return qname(new Ast::Name(lr, qi.ident), false);
208 case (lr: Ast::UnforgeableNamespace) {
209 return qname(new Ast::Name(lr, qi.ident), false);
212 internalError("", 0, "Unimplemented: nameFromIdentExpr " + e);
215 return multiname(id,false);
218 internalError("", 0, "Unimplemented: nameFromIdentExpr " + e);
223 function rtqname({ident:ident}, is_attr)
224 constants.RTQName(constants.symbolUtf8(ident), is_attr);
226 function rtqnameL(is_attr)
227 constants.RTQNameL(is_attr);
229 function typeFromTypeExpr(t) {
230 // not dealing with types for now
232 case (tn: Ast::TypeName) {
233 switch type( tn.ident ){
234 case(i: Ast::Identifier) {
236 if( name==Token::sym_String || name==Token::sym_Number ||
237 name==Token::sym_Boolean || name==Token::sym_int ||
238 name==Token::sym_uint || name==Token::sym_Object ||
239 name==Token::sym_Array || name==Token::sym_Class ||
240 name==Token::sym_Function) {
241 return nameFromIdent(name);
243 else if( name==Token::sym_string ) {
244 return nameFromIdent(Token::sym_String);
246 else if( name==Token::sym_boolean ) {
247 return nameFromIdent(Token::sym_Boolean);
250 //print ("warning: unknown type name " + t + ", using Object");
251 return nameFromIdent(Token::sym_Object);
257 // print ("warning: Unimplemented: typeFromTypeExpr " + t + ", using *");
263 // Use this only for places that need a QName, only works with basic class names
264 // as Tamarin doesn't support
265 function realTypeName(t) {
266 // not dealing with types for now
268 case (tn: Ast::TypeName) {
269 return nameFromIdentExpr(tn.ident);
271 case (st: Ast::SpecialType) {
275 internalError("", 0, "Unimplemented: realTypeName " + t + ", using *");
281 function fixtureNameToName(fn) {
283 case (pn: Ast::PropName) {
284 return qname(pn.name, false);
286 case (tn: Ast::TempName) {
287 return qname (new Ast::Name(Ast::publicNS, Token::intern("$t"+tn.index)),false); // FIXME allocate and access actual temps
290 internalError("", 0, "Not a valid fixture name " + x);
295 function fixtureTypeToType(fix) {
297 case (vf: Ast::ValFixture) {
298 return vf.ty != null ? typeFromTypeExpr(vf.ty) : 0 ;
300 case (mf: Ast::MethodFixture) {
304 internalError("", 0, "Unimplemented: fixtureTypeToType " + x);
309 function defaultLiteralExpr(lit)
312 case(ln: Ast::LiteralNull) {
313 return {val:CONSTANT_Null, kind:CONSTANT_Null}
315 case(lu: Ast::LiteralUndefined) {
316 return {val:0, kind:0}
318 case(ld: Ast::LiteralDouble) {
319 let val = constants.float64(ld.doubleValue);
320 return {val:val, kind:CONSTANT_Double};
322 case(ld: Ast::LiteralDecimal) {
323 // FIXME: emit a decimal constant here when we support decimal.
324 let val = constants.float64(ld.decimalValue);
325 return {val:val, kind:CONSTANT_Double};
327 case(li: Ast::LiteralInt) {
328 let val = constants.int32(li.intValue);
329 return {val:val, kind:CONSTANT_Integer};
331 case(lu: Ast::LiteralUInt) {
332 let val = constants.uint32(lu.uintValue);
333 return {val:val, kind:CONSTANT_UInt};
335 case(lb: Ast::LiteralBoolean) {
336 let val = (lb.booleanValue ? CONSTANT_True : CONSTANT_False);
337 return {val:val, kind:val};
339 case(ls: Ast::LiteralString) {
340 let val = constants.symbolUtf8(ls.strValue);
341 return {val:val, kind:CONSTANT_Utf8};
343 case(ln: Ast::LiteralNamespace) {
344 let val = constants.namespace(ln.namespaceValue);
345 return {val:val, kind:CONSTANT_Namespace};
348 syntaxError("", 0, "Default expression must be a constant value " + x); // FIXME: source pos
353 function defaultExpr(expr) {
354 // FIXME: This outlaws ~0, -1, and so on. ES4 default expression is a general expr.
356 case(le: LiteralExpr) {
357 return defaultLiteralExpr(le);
359 case(i: Ast::Identifier) {
360 if( i.ident == Token::sym_undefined ) {
361 // Handle defualt expr of (... arg = undefined ...)
362 return defaultLiteralExpr(new Ast::LiteralUndefined());
366 syntaxError("", 0, "Default expression must be a constant value " + expr); // FIXME: source pos
370 // Optimization? A brute-force hints table that maps both name and
371 // (name ^ kind) to true, allowing us to avoid searching the traits
372 // table if the hints table does not have an entry for whatever we're
373 // looking for, reduces the amount of searching effectively. But it
374 // does not improve running times very much, probably because most
375 // traits sets are small. (In ESC the largest number of traits in a
376 // scope is in the assembler, but compiling the assembler with that
377 // kind of hints structure slows code generation down.)
383 // Here we probably want: newVar, newConst, ... instead?
388 function hasTrait(name, kind) {
389 for (let i=0, limit=traits.length ; i < limit ; i++) {
391 if(t.name == name && ((t.kind&15)==kind))
397 function probeTrait(name) {
398 for (let i=0, limit=traits.length ; i < limit ; i++) {
401 return [true, t.kind & 15];
407 class Script extends TraitsTable
411 function Script(e:ABCEmitter) {
413 this.init = new Method(e,[], 0, true, new Ast::FuncAttr(null));
416 function newClass(name, basename, interfaces, flags, protectedns=null) {
417 return new Emit::Class(this, name, basename, interfaces, flags, protectedns);
420 function newInterface(ifacename, methname, interfaces) {
421 return new Emit::Interface(this, ifacename, methname, interfaces);
424 function addException(e) {
425 return init.addException(e);
428 function finalize() {
429 let id = init.finalize();
430 let si = new ABCScriptInfo(id);
431 for ( let i=0, limit=traits.length ; i < limit ; i++ )
432 si.addTrait(traits[i]);
433 e.file.addScript(si);
437 class Class extends TraitsTable
439 var s, name, basename, instance=null, cinit, interfaces, flags, protectedns;
441 function Class(script, name, basename, interfaces, flags, protectedns=null) {
444 this.basename = basename;
445 this.interfaces = interfaces;
447 this.protectedns = protectedns;
449 var asm = script.init;
452 function getCInit() {
454 cinit = new Method(s.e, [], s.e.constants.stringUtf8("$cinit"), true, new Ast::FuncAttr(null));
458 function getInstance() {
459 if( this.instance == null )
460 this.instance = new Instance(s, name, basename, interfaces, flags, protectedns);
462 return this.instance;
465 function finalize() {
466 let instidx = instance.finalize();
467 let clsinfo = new ABCClassInfo();
468 clsinfo.setCInit(getCInit().finalize());
469 for(let i = 0, limit=traits.length ; i < limit ; ++i)
470 clsinfo.addTrait(traits[i]);
472 let clsidx = s.e.file.addClass(clsinfo);
474 assert(clsidx == instidx);
480 // The documentation has issues here.
482 // The way ASC generates code:
483 // - the flags are ClassInterface|ClassSealed
484 // - the class init has a body that just executes "returnvoid"
485 // - there is a method_info entry for the instance initializer
486 // but no corresponding method_body
487 // - logic in cogen is responsible for generating global
488 // code that performs newclass/initproperty
490 class Interface extends TraitsTable
492 var script, ifacename, methname, interfaces;
494 function Interface(script, ifacename, methname, interfaces)
496 , ifacename=ifacename
498 , interfaces=interfaces
500 assert(methname is Number);
503 function finalize() {
504 var clsinfo = new ABCClassInfo();
505 var methname_idx = script.e.constants.stringUtf8(String(methname));
507 var iinit = new Instance(script, ifacename, 0, interfaces, CONSTANT_ClassInterface|CONSTANT_ClassSealed);
508 var cinit = (new Method(script.e, [], methname_idx, false, new Ast::FuncAttr(null))).finalize();
509 clsinfo.setCInit(cinit);
510 for(let i = 0, limit=traits.length ; i < limit ; ++i)
511 clsinfo.addTrait(traits[i]);
513 var clsidx = script.e.file.addClass(clsinfo);
515 var iinitm = new Method(script.e, [], methname_idx, false, new Ast::FuncAttr(null), true);
516 iinit.setIInit(iinitm.finalize());
524 class Instance extends TraitsTable
526 var s, name, basename, flags, interfaces, iinit, protectedns;
528 function Instance(s:Script, name, basename, interfaces, flags, protectedns=null)
532 , interfaces=interfaces
534 , protectedns=protectedns
538 function setIInit(method) {
542 function finalize() {
543 if (protectedns != null) {
544 flags |= CONSTANT_ClassProtectedNs;
545 pnsid = s.e.namespace(protectedns);
549 var instinfo = new ABCInstanceInfo(name, basename, flags, pnsid, interfaces);
551 instinfo.setIInit(iinit);
553 for(let i = 0, limit=traits.length ; i < limit ; i++)
554 instinfo.addTrait(traits[i]);
556 return s.e.file.addInstance(instinfo);
560 class Method extends TraitsTable // extends AVM2Assembler
562 var e, formals, name, asm, finalized=false, defaults = null, exceptions=[], attr=null, bodyless;
564 function Method(e:ABCEmitter, formals:Array, name, standardPrologue, attr, bodyless=false) {
565 assert( name is Number && name < 1073741824);
566 //super(e.constants, formals.length);
567 this.formals = formals;
571 this.bodyless = bodyless;
573 if (!bodyless && !attr.is_native) {
574 asm = new AVM2Assembler(e.constants, formals.length - (attr.uses_rest ? 1 : 0), attr);
575 // Standard prologue -- but is this always right?
576 // ctors don't need this - have a more complicated prologue
577 if(standardPrologue) {
584 function setDefaults(d) {
588 function addException(e) {
589 return exceptions.push(e)-1;
592 function finalize() {
599 if (!bodyless && !attr.is_native) {
600 // Standard epilogue for lazy clients.
604 else if (attr.is_native)
605 flags = METHOD_Native;
607 var meth = e.file.addMethod(new ABCMethodInfo(name, formals, 0, flags, defaults, null));
608 if (!bodyless && !attr.is_native) {
609 var body = new ABCMethodBodyInfo(meth);
610 body.setMaxStack(asm.maxStack);
611 body.setLocalCount(asm.maxLocal);
612 body.setInitScopeDepth(0);
613 body.setMaxScopeDepth(asm.maxScope);
614 body.setCode(asm.finalize());
615 for ( var i=0, limit=traits.length ; i < limit ; i++ )
616 body.addTrait(traits[i]);
618 for ( var i=0, limit=exceptions.length ; i < limit ; i++ )
619 body.addException(exceptions[i]);
621 e.file.addMethodBody(body);