Merge remote-tracking branch 'redux/master' into sh4-pool
[tamarin-stm.git] / esc / src / emit.es
blob6ceeca4f7e790d15a692e5898064332d8f4ae64c
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
4  *
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/
9  *
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
13  * License.
14  *
15  * The Original Code is [Open Source Virtual Machine.].
16  *
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.
21  *
22  * Contributor(s):
23  *   Adobe AS3 Team
24  *
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.
36  *
37  * ***** END LICENSE BLOCK ***** */
39 /* Rough sketch:
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
53    done on the AVM.
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,
64     namespace Emit;
66 use namespace Abc,
67     namespace Asm,
68     namespace Ast, // goes away
69     namespace Util;
71 class ABCEmitter
73     var file, constants;
74     /*private*/ var scripts = [];
76     function ABCEmitter() {
77         file = new ABCFile;
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);
83     }
85     function newScript(): Script {
86         var s = new Script(this);
87         scripts.push(s);
88         return s;
89     }
91     function finalize() {
92         function f(s) { s.finalize() }
93         forEach(f, scripts);
94         return file;
95     }
97     var Object_name;
98     var Array_name;
99     var RegExp_name;
100     var meta_construct_name;
102     /* AVM information.
103      *
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.
107      *
108      * The compiler inserts two definitions at the top of the file:
109      *
110      *   <magic> namespace internal = <magic>
111      *   internal namespace public = <the real public>
112      */
113     function namespace( ns: Ast::Namespace) {
114         switch type ( ns ) {
115         case (pn: Ast::PrivateNamespace) {
116             return constants.namespace(CONSTANT_PrivateNamespace, constants.symbolUtf8(pn.name));
117         }
118         case (pn: Ast::ProtectedNamespace) {
119             return constants.namespace(CONSTANT_ProtectedNamespace, constants.symbolUtf8(pn.name));
120         }
121         case (pn: Ast::PublicNamespace) {
122             return constants.namespace(CONSTANT_Namespace, constants.symbolUtf8(pn.name));
123         }
124         case (int_ns: Ast::InternalNamespace) {
125             return constants.namespace(CONSTANT_PackageInternalNS, constants.symbolUtf8(int_ns.name));
126         }
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));
130         }
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));
134         }
135         case (x:*) {
136             internalError("", 0, "Unimplemented namespace " + ns);
137         }
138         }
139     }
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.
144     //
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) {
155         var new_nss = [];
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));
159         return new_nss;
160     }
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));
167         }
168         return cached_id[h];
169     }
171     function multiname(mname, is_attr) {
172         let {nss, ident} = mname;
173         return constants.Multiname(namespaceSetList(nss), constants.symbolUtf8(ident), is_attr);
174     }
176     function qname(qn, is_attr) {
177         let {ns, id} = qn;
178         return constants.QName(this.namespace(ns), constants.symbolUtf8(id), is_attr);
179     }
181     function nameFromIdent(id) {
182         return constants.QName(constants.namespace(CONSTANT_PackageNamespace, constants.symbolUtf8(Token::sym_EMPTY)),
183                                constants.symbolUtf8(id),false);
184     }
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) {
194         switch type (e) {
195         case (id: Ast::Identifier) { 
196             return multiname(id,false);
197         }
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);
204             }
205             case (lr: Ast::ForgeableNamespace) {
206                 return qname(new Ast::Name(lr, qi.ident), false);
207             }
208             case (lr: Ast::UnforgeableNamespace) {
209                 return qname(new Ast::Name(lr, qi.ident), false);
210             }
211             case( e:* ) {
212                 internalError("", 0, "Unimplemented: nameFromIdentExpr " + e);
213             }
214             }
215             return multiname(id,false);
216         }
217         case (x:*) { 
218             internalError("", 0, "Unimplemented: nameFromIdentExpr " + e);
219         }
220         }
221     }
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
231         switch type (t) {
232         case (tn: Ast::TypeName) {
233             switch type( tn.ident ){
234             case(i: Ast::Identifier) {
235                 let name = i.ident;
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);
242                 }
243                 else if( name==Token::sym_string ) {
244                     return nameFromIdent(Token::sym_String);
245                 }
246                 else if( name==Token::sym_boolean ) {
247                     return nameFromIdent(Token::sym_Boolean);
248                 }
249                 else {
250                     //print ("warning: unknown type name " + t + ", using Object");
251                     return nameFromIdent(Token::sym_Object);
252                 }
253             }
254             }
255         }
256         case (x:*) { 
257             // print ("warning: Unimplemented: typeFromTypeExpr " + t + ", using *");
258         }
259         }
260         return 0;
261     }
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
267         switch type (t) {
268         case (tn: Ast::TypeName) {
269             return nameFromIdentExpr(tn.ident);
270         }
271         case (st: Ast::SpecialType) {
272             return 0;
273         }
274         case (x:*) { 
275             internalError("", 0, "Unimplemented: realTypeName " + t + ", using *");
276         }
277         }
278         return 0;
279     }
281     function fixtureNameToName(fn) {
282         switch type (fn) {
283         case (pn: Ast::PropName) {
284             return qname(pn.name, false);
285         }
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
288         }
289         case (x:*) { 
290             internalError("", 0, "Not a valid fixture name " + x);
291         }
292         }
293     }
294         
295     function fixtureTypeToType(fix) {
296         switch type (fix) {
297         case (vf: Ast::ValFixture) {
298             return vf.ty != null ? typeFromTypeExpr(vf.ty) : 0 ;
299         }
300         case (mf: Ast::MethodFixture) {
301             return 0;
302         }
303         case(x:*) { 
304             internalError("", 0, "Unimplemented: fixtureTypeToType " + x);
305         }
306         }
307     }
308         
309     function defaultLiteralExpr(lit)
310     {
311         switch type (lit) {
312         case(ln: Ast::LiteralNull) {
313             return {val:CONSTANT_Null, kind:CONSTANT_Null}
314         }
315         case(lu: Ast::LiteralUndefined) {
316             return {val:0, kind:0}
317         }
318         case(ld: Ast::LiteralDouble) {
319             let val = constants.float64(ld.doubleValue);
320             return {val:val, kind:CONSTANT_Double};
321         }
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};
326         }
327         case(li: Ast::LiteralInt) {
328             let val = constants.int32(li.intValue);
329             return {val:val, kind:CONSTANT_Integer};
330         }
331         case(lu: Ast::LiteralUInt) {
332             let val = constants.uint32(lu.uintValue);
333             return {val:val, kind:CONSTANT_UInt};
334         }
335         case(lb: Ast::LiteralBoolean) {
336             let val = (lb.booleanValue ? CONSTANT_True : CONSTANT_False);
337             return {val:val, kind:val};
338         }
339         case(ls: Ast::LiteralString) {
340             let val = constants.symbolUtf8(ls.strValue);
341             return {val:val, kind:CONSTANT_Utf8};
342         }
343         case(ln: Ast::LiteralNamespace) {
344             let val = constants.namespace(ln.namespaceValue);
345             return {val:val, kind:CONSTANT_Namespace};
346         }
347         case(x:*) {
348             syntaxError("", 0, "Default expression must be a constant value " + x); // FIXME: source pos
349         }
350         }
351     }
353     function defaultExpr(expr) {
354         // FIXME: This outlaws ~0, -1, and so on.  ES4 default expression is a general expr.
355         switch type (expr) {
356         case(le: LiteralExpr) {
357             return defaultLiteralExpr(le);
358         }
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());
363             }
364         }
365         }
366         syntaxError("", 0, "Default expression must be a constant value " + expr); // FIXME: source pos
367     }
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.)
379 class TraitsTable 
381     var traits = [];
383     // Here we probably want: newVar, newConst, ... instead?
385     function addTrait(t)
386         traits.push(t);
388     function hasTrait(name, kind) {
389         for (let i=0, limit=traits.length ; i < limit ; i++) {
390             let t = traits[i];
391             if(t.name == name && ((t.kind&15)==kind))
392                 return true;
393         }
394         return false;
395     }
397     function probeTrait(name) {
398         for (let i=0, limit=traits.length ; i < limit ; i++) {
399             let t = traits[i];
400             if(t.name == name)
401                 return [true, t.kind & 15];
402         }
403         return [false, 0];
404     }
407 class Script extends TraitsTable
409     var e, init;
411     function Script(e:ABCEmitter) {
412         this.e = e;
413         this.init = new Method(e,[], 0, true, new Ast::FuncAttr(null));
414     }
416     function newClass(name, basename, interfaces, flags, protectedns=null) {
417         return new Emit::Class(this, name, basename, interfaces, flags, protectedns);
418     }
420     function newInterface(ifacename, methname, interfaces) {
421         return new Emit::Interface(this, ifacename, methname, interfaces);
422     }
424     function addException(e) {
425         return init.addException(e);
426     }
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);
434     }
436     
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) {
442         this.s = script;
443         this.name = name;
444         this.basename = basename;
445         this.interfaces = interfaces;
446         this.flags = flags;
447         this.protectedns = protectedns;
449         var asm = script.init;
450     }
452     function getCInit() {
453         if(cinit == null )
454             cinit = new Method(s.e, [], s.e.constants.stringUtf8("$cinit"), true, new Ast::FuncAttr(null));
455         return cinit;
456     }
458     function getInstance() {
459         if( this.instance == null )
460             this.instance = new Instance(s, name, basename, interfaces, flags, protectedns);
461             
462         return this.instance;
463     }
464         
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]);
471             
472         let clsidx = s.e.file.addClass(clsinfo);
473             
474         assert(clsidx == instidx);
476         return clsidx;
477     }
479     
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) 
495         : script=script
496         , ifacename=ifacename
497         , methname=methname
498         , interfaces=interfaces 
499     {
500         assert(methname is Number);
501     }
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);
514             
515         var iinitm = new Method(script.e, [], methname_idx, false, new Ast::FuncAttr(null), true);
516         iinit.setIInit(iinitm.finalize());
517         iinit.finalize();
519         return clsidx;
521     }
524 class Instance extends TraitsTable
526     var s, name, basename, flags, interfaces, iinit, protectedns;
527         
528     function Instance(s:Script, name, basename, interfaces, flags, protectedns=null) 
529         : s=s
530         , name=name
531         , basename=basename 
532         , interfaces=interfaces
533         , flags=flags
534         , protectedns=protectedns
535     {
536     }
537         
538     function setIInit(method) {
539         iinit = method;
540     }
541         
542     function finalize() {
543         if (protectedns != null) {
544             flags |= CONSTANT_ClassProtectedNs;
545             pnsid = s.e.namespace(protectedns);
546         }
547         else
548             pnsid = 0;
549         var instinfo = new ABCInstanceInfo(name, basename, flags, pnsid, interfaces);
550             
551         instinfo.setIInit(iinit);
552             
553         for(let i = 0, limit=traits.length ; i < limit ; i++)
554             instinfo.addTrait(traits[i]);
555             
556         return s.e.file.addInstance(instinfo);
557     }
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;
568         this.e = e;
569         this.name = name;
570         this.attr = attr;
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) {
578                 asm.I_getlocal_0();
579                 asm.I_pushscope();
580             }
581         }
582     }
584     function setDefaults(d) {
585         defaults = d;
586     }
588     function addException(e) {
589         return exceptions.push(e)-1;
590     }
592     function finalize() {
593         if (finalized)
594             return;
595         finalized = true;
597         var flags = 0;
599         if (!bodyless && !attr.is_native) {
600             // Standard epilogue for lazy clients.
601             asm.I_returnvoid();
602             flags = asm.flags;
603         } 
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]);
617             
618             for ( var i=0, limit=exceptions.length ; i < limit ; i++ )
619                 body.addException(exceptions[i]);
620             
621             e.file.addMethodBody(body);
622         }
624         return meth;
625     }