added missing opcodes to VM
[gaemu.git] / gaem / runner / compiler.d
blob1eedafc0bf4b97fc960e2f67392e240a01b7e3e3
1 /* GML runner
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module gaem.runner.compiler is aliced;
20 import std.traits;
22 import gaem.parser;
24 import gaem.runner.strpool;
25 import gaem.runner.value;
26 import gaem.runner.opcodes;
27 import gaem.runner.vm;
28 import gaem.runner.objects;
31 // ////////////////////////////////////////////////////////////////////////// //
32 void compile (NodeFunc fn) {
33 import std.stdio : stdout;
34 auto spc = VM.code.length;
35 doCompileFunc(fn);
36 while (spc < VM.code.length) spc += dumpInstr(stdout, spc, VM.code);
40 // ////////////////////////////////////////////////////////////////////////// //
41 private:
42 __gshared NodeFunc curfn;
43 __gshared bool[256] slots;
44 __gshared uint firstFreeSlot;
45 __gshared uint maxUsedSlot;
46 __gshared ubyte[string] locals;
47 __gshared uint[string] globals;
48 __gshared Loc[string] vdecls; // for error messages
49 __gshared ubyte maxArgUsed; // maximum `argumentX` we've seen
50 __gshared string[16] aaliases; // argument aliases
51 __gshared uint breakChain; // current jump chain for `break`
52 __gshared uint contChain; // current jump chain for `continue`
53 __gshared bool contChainIsAddr; // is `contChain` an address, not a chain?
57 void setupSlots () {
58 slots[] = false;
59 //foreach (immutable idx; 0..VM.Slot.max+1) slots[idx] = true; // used
60 slots[0..VM.Slot.max+1] = true; // used
61 firstFreeSlot = VM.Slot.max+1;
62 maxUsedSlot = firstFreeSlot-1;
63 locals.clear;
64 globals.clear;
65 vdecls.clear;
66 maxArgUsed = 0;
67 aaliases[] = null;
68 breakChain = 0;
69 contChain = 0;
70 contChainIsAddr = false;
74 bool isLocalSlot (ubyte slot) {
75 //TODO: use AA for mapping?
76 foreach (ubyte r; locals.byValue) if (r == slot) return true;
77 return false;
81 ubyte allocSlot (Loc loc, int ddest=-1) {
82 if (ddest >= 0) {
83 assert(ddest < slots.length);
84 return cast(ubyte)ddest;
86 foreach (immutable idx; firstFreeSlot..slots.length) {
87 if (!slots[idx]) {
88 if (idx > maxUsedSlot) maxUsedSlot = cast(uint)idx;
89 slots[idx] = true;
90 return cast(ubyte)idx;
93 compileError(loc, "out of free slots");
94 assert(0);
98 ubyte allocSlots (Loc loc, int count) {
99 assert(count > 0 && count < slots.length);
100 foreach (immutable idx; firstFreeSlot..slots.length-count) {
101 bool ok = true;
102 foreach (immutable c; idx..idx+count) if (slots[c]) { ok = false; break; }
103 if (ok) {
104 if (idx+count-1 > maxUsedSlot) maxUsedSlot = cast(uint)idx+count-1;
105 foreach (immutable c; idx..idx+count) slots[c] = true;
106 return cast(ubyte)idx;
109 compileError(loc, "out of free slots");
110 assert(0);
114 ubyte reserveCallSlots (Loc loc, uint resnum) {
115 foreach_reverse (immutable idx, bool v; slots) {
116 if (v) {
117 if (idx+resnum+1 > slots.length) compileError(loc, "out of free slots");
118 return cast(ubyte)(idx+1);
121 compileError(loc, "out of free slots");
122 assert(0);
126 void freeSlot (ubyte num) {
127 if (num >= firstFreeSlot) {
128 assert(slots[num]);
129 slots[num] = false;
134 void freeSlots (ubyte num, int count) {
135 while (count-- > 0) {
136 freeSlot(num);
137 if (++num == 0) break;
142 // ////////////////////////////////////////////////////////////////////////// //
143 int argvar (string s) {
144 switch (s) {
145 case "argument0": return 0;
146 case "argument1": return 1;
147 case "argument2": return 2;
148 case "argument3": return 3;
149 case "argument4": return 4;
150 case "argument5": return 5;
151 case "argument6": return 6;
152 case "argument7": return 7;
153 case "argument8": return 8;
154 case "argument9": return 9;
155 case "argument10": return 10;
156 case "argument11": return 11;
157 case "argument12": return 12;
158 case "argument13": return 13;
159 case "argument14": return 14;
160 case "argument15": return 15;
161 default:
163 return -1;
167 void compileError(A...) (Loc loc, A args) {
168 if (curfn.pp !is null) {
169 curfn.pp.error(loc, args);
170 } else {
171 import std.stdio : stderr;
172 stderr.writeln("ERROR at ", loc, ": ", args);
173 string msg;
174 foreach (immutable a; args) {
175 import std.string : format;
176 msg ~= "%s".format(a);
178 throw new ErrorAt(loc, msg);
183 uint sid4name (string name) {
184 if (auto sptr = name in VM.scripts) {
185 return *sptr;
186 } else {
187 auto sid = cast(uint)VM.scriptPCs.length;
188 if (sid > 32767) compileError(curfn.loc, "too many scripts");
189 assert(VM.scriptASTs.length == sid);
190 // reserve slots
191 VM.scriptPCs ~= 0;
192 VM.scriptASTs ~= null;
193 VM.scriptNum2Name[sid] = name;
194 VM.scripts[name] = sid;
195 return sid;
200 uint pc () { pragma(inline, true); return cast(uint)VM.code.length; }
201 void setpc (uint pc) { pragma(inline, true); VM.code.length = pc; VM.code.assumeSafeAppend; }
204 uint emit (Op op, ubyte dest=0, ubyte op0=0, ubyte op1=0) {
205 auto res = cast(uint)VM.code.length;
206 VM.code ~= (op1<<24)|(op0<<16)|(dest<<8)|cast(ubyte)op;
207 return res;
211 uint emit3Bytes (Op op, uint val) {
212 assert(val <= 0xffffff);
213 auto res = cast(uint)VM.code.length;
214 VM.code ~= (val<<8)|cast(ubyte)op;
215 return res;
219 uint emit2Bytes (Op op, ubyte dest, short val) {
220 auto res = cast(uint)VM.code.length;
221 VM.code ~= (val<<16)|(dest<<8)|cast(ubyte)op;
222 return res;
226 uint emitJumpTo (uint addr, Op op=Op.jump) {
227 assert(addr <= 0xffffff);
228 auto res = cast(uint)VM.code.length;
229 VM.code ~= cast(uint)op|(addr<<8);
230 return res;
234 // this starts "jump chain", return new chain id
235 uint emitJumpChain (uint chain, Op op=Op.jump) {
236 assert(chain <= 0xffffff);
237 auto res = cast(uint)VM.code.length;
238 VM.code ~= cast(uint)op|(chain<<8);
239 return res;
243 void fixJumpChain (uint chain, uint addr) {
244 assert(chain <= 0xffffff);
245 assert(addr <= 0xffffff);
246 while (chain) {
247 auto nc = op3Byte(VM.code[chain]);
248 VM.code[chain] = (VM.code[chain]&0xff)|(addr<<8);
249 chain = nc;
254 void opReplace (uint pc, Op op, ubyte dest) {
255 VM.code[pc] = (VM.code[pc]&0xff_ff_00_00)|(dest<<8)|cast(ubyte)op;
259 // ////////////////////////////////////////////////////////////////////////// //
260 void findUninitialized () {
261 bool[string] inited;
262 bool[string] used;
264 void processExpr (Node n, bool asAss=false) {
265 if (n is null) return;
266 visitNodes(n, (Node nn) {
267 if (auto id = cast(NodeId)nn) {
268 if (!asAss && argvar(id.name) < 0) {
269 if (id.name in locals && id.name !in inited) {
270 compileError(nn.loc, "using uninitialized variable; declared at ", vdecls[id.name].toStringNoFile);
273 inited[id.name] = true;
274 used[id.name] = true;
275 return VisitRes.SkipChildren;
277 if (auto n = cast(NodeFCall)nn) {
278 if (cast(NodeId)n.fe is null) compileError(n.loc, "invalid function call");
279 if (n.args.length > 16) compileError(n.loc, "too many arguments in function call");
280 foreach (immutable idx, Node a; n.args) {
281 // no assignments allowed there
282 processExpr(a);
284 return VisitRes.SkipChildren;
286 return VisitRes.Continue;
290 void processStatement (Node nn) {
291 if (nn is null) return;
292 return selectNode!void(nn,
293 (NodeVarDecl n) {},
294 (NodeBlock n) {
295 foreach (Node st; n.stats) {
296 if (cast(NodeStatementBreakCont)st !is null) break;
297 processStatement(st);
298 if (cast(NodeReturn)st !is null) break;
301 (NodeStatementEmpty n) {},
302 (NodeStatementAss n) {
303 if (cast(NodeId)n.el is null && cast(NodeDot)n.el is null && cast(NodeIndex)n.el is null) {
304 compileError(nn.loc, "assignment to rvalue");
305 return;
307 processExpr(n.er); // it is calculated first
308 processExpr(n.el, asAss:true);
310 (NodeStatementExpr n) { processExpr(n.e); },
311 (NodeReturn n) { processExpr(n.e); },
312 (NodeWith n) {
313 processExpr(n.e); // can't contain assignments
314 // body can be executed zero times, so...
315 auto before = inited.dup;
316 processStatement(n.ebody);
317 inited = before;
319 (NodeIf n) {
320 processExpr(n.ec);
321 auto before = inited.dup;
322 processStatement(n.et);
323 auto tset = inited.dup;
324 inited = before.dup;
325 processStatement(n.ef);
326 // now copy to `before` all items that are set both in `tset` and in `inited`
327 foreach (string name; inited.byKey) {
328 if (name in tset) before[name] = true;
330 inited = before;
332 (NodeStatementBreakCont n) {},
333 (NodeFor n) {
334 processStatement(n.einit);
335 // "next" and "cond" can't contain assignments, so it's safe here
336 processExpr(n.econd);
337 processStatement(n.enext);
338 // yet body can be executed zero times, so...
339 auto before = inited.dup;
340 processStatement(n.ebody);
341 inited = before;
343 (NodeWhile n) {
344 // "cond" can't contain assignments, so it's safe here
345 processExpr(n.econd);
346 // yet body can be executed zero times, so...
347 auto before = inited.dup;
348 processStatement(n.ebody);
349 inited = before;
351 (NodeDoUntil n) {
352 // "cond" can't contain assignments, so it's safe here
353 processExpr(n.econd);
354 // body is guaranteed to execute at least one time
355 processStatement(n.ebody);
357 (NodeRepeat n) {
358 // "count" can't contain assignments, so it's safe here
359 processExpr(n.ecount);
360 // yet body can be executed zero times, so...
361 auto before = inited.dup;
362 processStatement(n.ebody);
363 inited = before;
365 (NodeSwitch n) {
366 // "expr" can't contain assignments, so it's safe here
367 processExpr(n.e);
368 auto before = inited.dup;
369 foreach (ref ci; n.cases) {
370 processExpr(ci.e); // can't contain assignments
371 // and this one can
372 if (ci.st !is null) {
373 inited = before.dup;
374 processStatement(ci.st);
377 inited = before;
379 () { assert(0, "unimplemented node: "~typeid(nn).name); },
383 processStatement(curfn.ebody);
385 // now show (and remove) unused locals
386 //static struct Info { Loc loc; string name; }
387 //Info[] unusedLocs;
388 foreach (string name; locals.keys) {
389 if (name !in used) {
390 { import std.stdio; writeln("removing unused local '", name, "'"); }
391 //unusedLocs ~= Info(vdecls[name], name);
392 locals.remove(name);
395 //import std.algorithm : sort;
396 //unusedLocs.sort!((ref a, ref b) { if (a.loc.line < b.loc.line) return true; if (a.loc.line > b.loc.line) return false; return (a.loc.col < b.loc.col); });
397 //foreach (ref nfo; unusedLocs) compileError(nfo.loc, "unused local '", nfo.name, "'");
401 // ////////////////////////////////////////////////////////////////////////// //
402 void emitPLit (Loc loc, ubyte dest, Real v) {
403 uint vpidx = uint.max;
404 if (v.isReal) {
405 // number
406 import core.stdc.math : lrint;
407 if (lrint(v) == v && lrint(v) >= short.min && lrint(v) <= short.max) {
408 emit2Bytes(Op.ilit, dest, cast(short)lrint(v));
409 return;
411 //FIXME: speed it up!
412 foreach (immutable idx, Real vp; VM.vpool) if (vp == v) { vpidx = cast(uint)idx; break; }
413 } else if (v.isString) {
414 // string
415 //FIXME: speed it up!
416 auto sid = v.getStrId;
417 if (sid > short.max) compileError(loc, "too many strings");
418 //foreach (immutable idx, Real vp; VM.vpool) if (vp.isString && vp.getStrId == sid) { vpidx = cast(uint)idx; break; }
419 emit2Bytes(Op.slit, dest, cast(short)sid);
420 return;
421 } else {
422 assert(0, "wtf?!");
424 if (vpidx == uint.max) {
425 vpidx = cast(uint)VM.vpool.length;
426 if (vpidx >= 0xffffff) compileError(loc, "too many constants");
427 VM.vpool ~= v;
429 if (vpidx < ushort.max) {
430 emit2Bytes(Op.plit, dest, cast(ushort)vpidx);
431 } else {
432 // special form
433 emit2Bytes(Op.plit, dest, cast(short)ushort.max);
434 emit3Bytes(Op.skip, vpidx);
439 uint allocStrConst (string s, Loc loc) { return newInternalStr(s); }
442 int varSlot (string name) {
443 auto avn = argvar(name);
444 if (avn >= 0) return VM.Slot.Argument0+avn;
445 switch (name) {
446 case "self": return VM.Slot.Self;
447 case "other": return VM.Slot.Other;
448 default:
450 // argument aliases
451 foreach (immutable idx, string an; aaliases) if (an == name) return cast(int)VM.Slot.Argument0+idx;
452 // locals
453 if (auto v = name in locals) return *v;
454 return -1;
458 // returns slot number or -1
459 int isKnownSlot (Node nn) {
460 if (auto n = cast(NodeId)nn) {
461 // keep track of maximum argument we've seen
462 if (maxArgUsed < 15) {
463 if (auto ai = argvar(n.name)) {
464 if (ai > maxArgUsed) maxArgUsed = cast(ubyte)ai;
467 auto vsl = varSlot(n.name);
468 if (vsl >= 0) return vsl; // this is local variable
470 return -1; // alas
474 // asCond: it is used as condition, so we should emit `oval` for objects
475 ubyte compileVarAccess (Node nn, int ddest=-1, bool asCond=false, bool noLocalsCheck=false) {
476 return selectNode!ubyte(nn,
477 (NodeId n) {
478 if (!noLocalsCheck) {
479 auto ksl = isKnownSlot(n);
480 if (ksl >= 0) {
481 // this is local variable
482 if (ddest < 0 || ddest == ksl) return ksl; // just use this slot directly
483 auto dest = allocSlot(n.loc, ddest);
484 assert(dest != ksl);
485 emit(Op.copy, dest, cast(ubyte)ksl, 1);
486 return dest;
488 // this may be object, or sprite, or background...
489 if (auto oid = objectByName(n.name)) {
490 // object!
491 auto dest = allocSlot(n.loc, ddest);
492 if (asCond) {
493 emit2Bytes(Op.oval, dest, cast(short)oid);
494 } else {
495 //emit2Bytes(Op.ilit, dest, cast(short)oid); //FIXME: introduce new opcode for this?
496 emitPLit(n.loc, dest, oid);
498 return dest;
502 // it's not local, so generate field access (Op.fval)
503 // this is `self` field
504 auto dest = allocSlot(n.loc, ddest);
505 auto fid = allocSlot(n.loc);
506 emit2Bytes(Op.xlit, fid, cast(short)allocateFieldId(n.name));
507 freeSlot(fid);
508 emit(Op.fval, dest, VM.Slot.Self, fid);
509 return dest;
512 (NodeDot n) {
513 // field access -- Op.fval
514 auto dest = allocSlot(n.loc, ddest);
515 if (auto oid = cast(NodeId)n.e) {
516 // we know object name directly
517 if (oid.name == "self" || oid.name == "other" || oid.name == "global") {
518 // well-known name
519 auto fid = allocSlot(n.loc);
520 emit2Bytes(Op.xlit, fid, cast(short)allocateFieldId(n.name));
521 if (oid.name == "global") {
522 auto oids = allocSlot(n.loc);
523 emit2Bytes(Op.ilit, oids, -666);
524 freeSlot(oids);
525 emit(Op.fval, dest, oids, fid);
526 } else {
527 emit(Op.fval, dest, (oid.name == "self" ? VM.Slot.Self : VM.Slot.Other), fid);
529 freeSlot(fid);
530 return dest;
533 // this is some complex expression
534 auto oids = compileExpr(n.e);
535 freeSlot(oids);
536 auto fid = allocSlot(n.loc);
537 emit2Bytes(Op.xlit, fid, cast(short)allocateFieldId(n.name));
538 emit(Op.fval, dest, oids, fid);
539 return dest;
541 (NodeIndex n) {
542 assert(n.ei0 !is null);
543 assert(ddest < 0 || ddest > VM.Slot.max);
544 auto dest = allocSlot(n.loc, ddest);
545 auto islots = allocSlots(n.loc, 2+(n.ei1 !is null ? 1 : 0)); // field id, index
546 compileExpr(n.ei0, islots+1, noLocalsCheck:noLocalsCheck);
547 if (n.ei1 !is null) compileExpr(n.ei1, islots+2, noLocalsCheck:noLocalsCheck);
548 // `islots+0` should be free here -- our slot alloc scheme guarantees it
549 auto xfd = compileExpr(n.e, asCond:true, noLocalsCheck:noLocalsCheck); // asCond, to produce `oval`
550 if (xfd <= VM.Slot.max || isLocalSlot(xfd)) { compileError(n.loc, "indexing locals is not supported yet"); assert(0); }
551 freeSlot(xfd); // don't need it
552 switch (VM.code[pc-1].opCode) {
553 case Op.oval: compileError(n.loc, "can't index object"); assert(0);
554 case Op.fval:
555 auto oid = VM.code[pc-1].opOp0;
556 auto fid = VM.code[pc-1].opOp1;
557 setpc(pc-1); // remove this opcode
558 // copy field id
559 if (fid != islots) emit(Op.copy, islots, fid, 1);
560 // generate new opcode
561 emit((n.ei1 is null ? Op.i1fval : Op.i2fval), dest, oid, islots);
562 break;
563 case Op.copy: compileError(n.loc, "indexing locals is not supported yet"); assert(0); // just in case, should not be reached
564 default: compileError(n.loc, "can't index something"); assert(0);
566 freeSlots(islots, 2+(n.ei1 !is null ? 1 : 0));
567 return dest;
569 () { assert(0, "unimplemented node: "~typeid(nn).name); },
574 // assVal: "new value" slot
575 // i0, i1: indexes
576 void compileVarStore (Node nn, ubyte assVal, bool noLocalsCheck=false) {
577 selectNode!void(nn,
578 (NodeId n) {
579 if (!noLocalsCheck) {
580 auto ksl = isKnownSlot(n);
581 if (ksl >= 0) {
582 // this is local variable
583 if (assVal != ksl) emit(Op.copy, cast(ubyte)ksl, assVal, 1);
584 return;
586 // this may be object, or sprite, or background...
587 if (objectByName(n.name)) { compileError(n.loc, "can't assign value to object"); assert(0); }
590 // it's not local, so generate field store (Op.fval)
591 // this is `self` field
592 auto fid = allocSlot(n.loc);
593 emit2Bytes(Op.xlit, fid, cast(short)allocateFieldId(n.name));
594 freeSlot(fid);
595 emit(Op.fstore, assVal, VM.Slot.Self, fid); // store value *from* dest into field; op0: obj id; op1: int! reg (field id); can create fields
596 return;
598 assert(0);
600 (NodeDot n) { assert(0, "internal compiler error: NodeDot should not be here"); },
601 (NodeIndex n) {
602 assert(n.ei0 !is null);
603 auto islots = allocSlots(n.loc, 2+(n.ei1 !is null ? 1 : 0)); // field id, index
604 compileExpr(n.ei0, islots+1, noLocalsCheck:noLocalsCheck);
605 if (n.ei1 !is null) compileExpr(n.ei1, islots+2, noLocalsCheck:noLocalsCheck);
606 // `islots+0` should be free here -- our slot alloc scheme guarantees it
607 auto spc = pc;
608 compileVarStore(n.e, assVal, noLocalsCheck:noLocalsCheck);
609 // storing to local can generate no code (it can't, but...)
610 if (pc == spc) { compileError(n.loc, "indexing locals is not supported yet"); assert(0); }
611 switch (VM.code[pc-1].opCode) {
612 case Op.fstore:
613 auto oid = VM.code[pc-1].opOp0;
614 auto fid = VM.code[pc-1].opOp1;
615 setpc(pc-1); // remove this opcode
616 // copy field id
617 if (fid != islots) emit(Op.copy, islots, fid, 1);
618 // generate new opcode
619 emit((n.ei1 is null ? Op.i1fstore : Op.i2fstore), assVal, oid, islots);
620 break;
621 case Op.copy: compileError(n.loc, "indexing locals is not supported yet"); assert(0);
622 default: compileError(n.loc, "can't index something"); assert(0);
624 freeSlots(islots, 2+(n.ei1 !is null ? 1 : 0));
626 () { assert(0, "unimplemented node: "~typeid(nn).name); },
631 // returns dest slot
632 // can put value in desired dest
633 ubyte compileExpr (Node nn, int ddest=-1, bool asCond=false, bool noLocalsCheck=false) {
634 import core.stdc.math : lrint;
635 import std.math : NaN, isNaN;
637 ubyte doUnOp (Op op, NodeUnary n) {
638 auto dest = allocSlot(n.loc, ddest);
639 auto o0 = compileExpr(n.e);
640 emit(op, dest, o0);
641 freeSlot(o0);
642 return dest;
645 ubyte doBinOp (Op op, NodeBinary n) {
646 auto dest = allocSlot(n.loc, ddest);
647 auto o0 = compileExpr(n.el);
648 auto o1 = compileExpr(n.er);
649 emit(op, dest, o0, o1);
650 freeSlot(o0);
651 freeSlot(o1);
652 return dest;
655 // returns NaN for non-nums
656 Real getNumArg (Node n) {
657 if (auto lit = cast(NodeLiteralNum)n) return lit.val;
658 return NaN(-1); // arbitrary value
661 bool isStrArg (Node n) {
662 pragma(inline, true);
663 return (cast(NodeLiteralStr)n !is null);
666 nn.pcs = pc;
667 scope(exit) nn.pce = pc;
668 return selectNode!ubyte(nn,
669 (NodeLiteralNum n) {
670 auto dest = allocSlot(n.loc, ddest);
671 emitPLit(n.loc, dest, n.val);
672 return dest;
674 (NodeLiteralStr n) {
675 auto dest = allocSlot(n.loc, ddest);
676 auto sid = allocStrConst(n.val, n.loc);
677 emitPLit(n.loc, dest, buildStrId(sid));
678 return dest;
680 (NodeUnaryParens n) => compileExpr(n.e, ddest, asCond, noLocalsCheck),
681 (NodeUnaryNot n) => doUnOp(Op.lnot, n),
682 (NodeUnaryNeg n) => doUnOp(Op.neg, n),
683 (NodeUnaryBitNeg n) => doUnOp(Op.bneg, n),
684 (NodeBinaryAdd n) => doBinOp(Op.add, n),
685 (NodeBinarySub n) => doBinOp(Op.sub, n),
686 (NodeBinaryMul n) => doBinOp(Op.mul, n),
687 (NodeBinaryRDiv n) => doBinOp(Op.rdiv, n),
688 (NodeBinaryDiv n) => doBinOp(Op.div, n),
689 (NodeBinaryMod n) => doBinOp(Op.mod, n),
690 (NodeBinaryBitOr n) => doBinOp(Op.bor, n),
691 (NodeBinaryBitAnd n) => doBinOp(Op.band, n),
692 (NodeBinaryBitXor n) => doBinOp(Op.bxor, n),
693 (NodeBinaryLShift n) => doBinOp(Op.shl, n),
694 (NodeBinaryRShift n) => doBinOp(Op.shr, n),
695 (NodeBinaryLess n) => doBinOp(Op.lt, n),
696 (NodeBinaryLessEqu n) => doBinOp(Op.le, n),
697 (NodeBinaryGreat n) => doBinOp(Op.gt, n),
698 (NodeBinaryGreatEqu n) => doBinOp(Op.ge, n),
699 (NodeBinaryEqu n) => doBinOp(Op.eq, n),
700 (NodeBinaryNotEqu n) => doBinOp(Op.ne, n),
701 (NodeBinaryLogOr n) => doBinOp(Op.lor, n),
702 (NodeBinaryLogAnd n) => doBinOp(Op.land, n),
703 (NodeBinaryLogXor n) => doBinOp(Op.lxor, n),
704 (NodeFCall n) {
705 if (cast(NodeId)n.fe is null) compileError(n.loc, "invalid function call");
706 if (n.args.length > 16) compileError(n.loc, "too many arguments in function call");
707 auto dest = allocSlot(n.loc, ddest);
708 // preallocate frame
709 // we can do this, as current slot allocation scheme guarantees
710 // that we won't have used slots with higher numbert after compiling
711 // argument expressions
712 // `reserveCallSlots()` won't mark slots as used
713 auto frameSize = cast(uint)n.args.length+VM.Slot.Argument0;
714 auto fcs = reserveCallSlots(n.loc, frameSize+1); // +1 for script id
715 // put arguments where we want 'em to be
716 foreach (immutable idx, Node a; n.args) {
717 // reserve result slot, so it won't be overwritten
718 assert(!slots[fcs+VM.Slot.Argument0+idx]);
719 slots[fcs+VM.Slot.Argument0+idx] = true;
720 auto dp = compileExpr(a, fcs+VM.Slot.Argument0+idx, noLocalsCheck:noLocalsCheck);
721 if (dp != fcs+VM.Slot.Argument0+idx) assert(0, "internal compiler error");
723 // now free result slots
724 foreach (immutable idx; 0..n.args.length) freeSlot(cast(ubyte)(fcs+VM.Slot.Argument0+idx));
725 // make sure that our invariant holds
726 if (reserveCallSlots(n.loc, 1) != fcs) assert(0, "internal compiler error");
727 // put script id
728 // emit call
729 uint sid = sid4name((cast(NodeId)n.fe).name);
730 emit2Bytes(Op.xlit, cast(ubyte)(fcs+VM.Slot.Argument0+n.args.length), cast(short)sid);
731 emit(Op.call, dest, fcs, cast(ubyte)n.args.length);
732 return dest;
734 // only variable access is left
735 (Node n) => compileVarAccess(n, ddest, asCond, noLocalsCheck),
740 bool hasDotAccess (Node nn) {
741 if (nn is null) return false;
742 auto rn = visitNodes(nn, (Node n) {
743 if (cast(NodeDot)n) return VisitRes.Stop;
744 return VisitRes.Continue;
746 return (rn !is null);
750 // compile dotted assign as series of `with`
751 void lowerLeftAss (Node el, ubyte assVal) {
752 Node curelem = el;
753 Node[] withStack;
754 while (curelem !is null) {
755 if (auto dot = cast(NodeDot)curelem) {
756 withStack ~= new NodeId(dot.loc, dot.name);
757 curelem = dot.e;
758 } else if (auto idx = cast(NodeIndex)curelem) {
759 // `e` should be NodeDot or NodeId
760 if (auto dot = cast(NodeDot)idx.e) {
761 auto x = new NodeIndex(idx.loc, new NodeId(dot.loc, dot.name));
762 x.ei0 = idx.ei0;
763 x.ei1 = idx.ei1;
764 withStack ~= x;
765 curelem = dot.e;
766 } else if (auto id = cast(NodeId)idx.e) {
767 withStack ~= idx;
768 curelem = null;
769 } else {
770 compileError(curelem.loc, "something strange is happened here (0)");
772 } else if (auto id = cast(NodeId)curelem) {
773 withStack ~= id;
774 curelem = null;
775 } else {
776 compileError(curelem.loc, "something strange is happened here (1)");
779 assert(withStack.length);
781 foreach (immutable idx, Node n; withStack; reverse) {
782 import std.stdio;
783 writeln(idx, ": ", n.toString);
786 if (cast(NodeIndex)withStack[$-1]) compileError(withStack[$-1].loc, "invalid indexing");
787 // generate iterators (in reverse order) for [1..$]; [0] is field
788 ubyte[] its;
789 uint[] spc;
790 uint[] jchain;
791 foreach (Node n; withStack[1..$]; reverse) {
792 its ~= allocSlot(n.loc);
793 auto xid = compileVarAccess(n, asCond:false, noLocalsCheck:true);
794 freeSlot(xid);
795 emit(Op.siter, its[$-1], xid); // start instance iterator; dest: iterid; op0: objid or instid; next is jump over loop
796 jchain ~= emitJumpChain(0);
797 spc ~= pc;
799 // trivial body of the inner `with`
800 compileVarStore(withStack[0], assVal, noLocalsCheck:true);
801 // unwind `with`
802 foreach (immutable idx; 0..its.length; reverse) {
803 emit(Op.niter, its[idx]);
804 emitJumpTo(spc[idx]);
805 fixJumpChain(jchain[idx], pc);
806 emit(Op.kiter, its[idx]);
807 freeSlot(its[idx]);
812 void compileStatement (Node nn) {
813 assert(nn !is null);
814 nn.pcs = pc;
815 scope(exit) nn.pce = pc;
816 return selectNode!void(nn,
817 (NodeVarDecl n) {},
818 (NodeBlock n) {
819 foreach (Node st; n.stats) compileStatement(st);
821 (NodeStatementEmpty n) {},
822 (NodeStatementAss n) {
823 // assignment
824 // lower `a.b = 42;` to `with`
825 // note that `a.b[42] = 666;` is `NodeIndex` which contains `NodeDot`
826 if (cast(NodeId)n.el is null && cast(NodeDot)n.el is null && cast(NodeIndex)n.el is null) compileError(n.loc, "assignment to rvalue");
827 auto ksl = isKnownSlot(n.el);
828 if (ksl >= 0) {
829 // this is known slot, just put value into it directly
830 auto src = compileExpr(n.er, ksl);
831 assert(src == ksl);
832 } else {
833 auto src = compileExpr(n.er);
834 // known object?
835 if (auto dot = cast(NodeDot)n.el) {
836 if (auto oid = cast(NodeId)dot) {
837 // we know object name directly
838 if (oid.name == "self" || oid.name == "other" || oid.name == "global") {
839 // well-known name
840 auto fid = allocSlot(dot.loc);
841 emit2Bytes(Op.xlit, fid, cast(short)allocateFieldId(dot.name));
842 if (oid.name == "global") {
843 auto oids = allocSlot(dot.loc);
844 emit2Bytes(Op.ilit, oids, -666);
845 freeSlot(oids);
846 emit(Op.fstore, src, oids, fid);
847 } else {
848 emit(Op.fstore, src, (oid.name == "self" ? VM.Slot.Self : VM.Slot.Other), fid);
850 freeSlot(fid);
851 freeSlot(src);
852 return;
856 // dot access need lowering
857 if (hasDotAccess(n.el)) {
858 lowerLeftAss(n.el, src);
859 } else {
860 compileVarStore(n.el, src);
862 freeSlot(src);
865 (NodeStatementExpr n) { freeSlot(compileExpr(n.e)); },
866 (NodeReturn n) {
867 if (n.e is null) {
868 emitPLit(n.loc, 0, 0);
869 emit(Op.ret, 0);
870 } else {
871 auto dest = compileExpr(n.e);
872 emit(Op.ret, dest);
873 freeSlot(dest);
876 (NodeWith n) {
877 auto obc = breakChain;
878 auto occ = contChain;
879 auto cca = contChainIsAddr;
880 scope(exit) { breakChain = obc; contChain = occ; contChainIsAddr = cca; }
881 // object
882 auto iid = allocSlot(n.loc);
883 auto obs = compileExpr(n.e);
884 freeSlot(obs);
885 // iteration start
886 emit(Op.siter, iid, obs); // start instance iterator; dest: iterid; op0: objid or instid; next is jump over loop
887 breakChain = emitJumpChain(0); // jump over the loop
888 contChain = 0;
889 contChainIsAddr = false;
890 // loop body
891 auto bpc = pc;
892 compileStatement(n.ebody);
893 // continue point
894 fixJumpChain(contChain, pc);
895 emit(Op.niter, iid);
896 emitJumpTo(bpc);
897 // end of loop, break point
898 fixJumpChain(breakChain, pc);
899 emit(Op.kiter, iid);
900 freeSlot(iid);
902 (NodeIf n) {
903 auto cs = compileExpr(n.ec, asCond:true);
904 freeSlot(cs); // yep, free it here
905 emit(Op.xtrue, cs);
906 uint jfc = 0;
907 // simple optimization
908 jfc = emitJumpChain(0, Op.jump);
909 compileStatement(n.et);
910 if (n.ef !is null) {
911 auto exc = emitJumpChain(0, Op.jump);
912 fixJumpChain(jfc, pc);
913 jfc = exc;
914 compileStatement(n.ef);
916 fixJumpChain(jfc, pc);
918 (NodeStatementBreak n) {
919 breakChain = emitJumpChain(breakChain);
921 (NodeStatementContinue n) {
922 if (contChainIsAddr) {
923 emitJumpTo(contChain);
924 } else {
925 contChain = emitJumpChain(contChain);
928 (NodeFor n) {
929 compileStatement(n.einit);
930 // generate code like this:
931 // jump to "continue"
932 // body
933 // continue:
934 // cond
935 // jumptostart
936 auto obc = breakChain;
937 auto occ = contChain;
938 auto cca = contChainIsAddr;
939 scope(exit) { breakChain = obc; contChain = occ; contChainIsAddr = cca; }
940 // jump to "continue"
941 contChain = emitJumpChain(0); // start new chain
942 contChainIsAddr = false;
943 breakChain = 0; // start new chain
944 auto stpc = pc;
945 // increment
946 compileStatement(n.enext);
947 // body
948 compileStatement(n.ebody);
949 // fix "continue"
950 fixJumpChain(contChain, pc);
951 // condition
952 auto dest = compileExpr(n.econd, asCond:true);
953 freeSlot(dest); // yep, right here
954 emit(Op.xfalse, dest); // skip jump on false
955 emitJumpTo(stpc);
956 // "break" is here
957 fixJumpChain(breakChain, pc);
959 (NodeWhile n) {
960 // nothing fancy
961 auto obc = breakChain;
962 auto occ = contChain;
963 auto cca = contChainIsAddr;
964 scope(exit) { breakChain = obc; contChain = occ; contChainIsAddr = cca; }
965 // new break chain
966 breakChain = 0;
967 // "continue" is here
968 contChain = pc;
969 contChainIsAddr = true;
970 // condition
971 auto dest = compileExpr(n.econd, asCond:true);
972 freeSlot(dest); // yep, right here
973 emit(Op.xfalse, dest); // skip jump on false
974 breakChain = emitJumpChain(breakChain); // get out of here
975 // body
976 compileStatement(n.ebody);
977 // and again
978 emitJumpTo(contChain);
979 // "break" is here
980 fixJumpChain(breakChain, pc);
982 (NodeDoUntil n) {
983 // nothing fancy
984 auto obc = breakChain;
985 auto occ = contChain;
986 auto cca = contChainIsAddr;
987 scope(exit) { breakChain = obc; contChain = occ; contChainIsAddr = cca; }
988 auto stpc = pc;
989 // new break chain
990 breakChain = 0;
991 // new continue chain
992 contChain = 0;
993 contChainIsAddr = false;
994 // body
995 compileStatement(n.ebody);
996 // "continue" is here
997 fixJumpChain(contChain, pc);
998 // condition
999 auto dest = compileExpr(n.econd, asCond:true);
1000 freeSlot(dest); // yep, right here
1001 emit(Op.xfalse, dest); // skip jump on false
1002 // and again
1003 emitJumpTo(stpc);
1004 // "break" is here
1005 fixJumpChain(breakChain, pc);
1007 (NodeRepeat n) { //TODO: don't allow object names here
1008 // allocate node for counter
1009 auto cnt = compileExpr(n.ecount);
1010 // allocate "1" constant (we will need it)
1011 auto one = allocSlot(n.loc);
1012 emit2Bytes(Op.ilit, one, cast(short)1);
1013 // alice in chains
1014 auto obc = breakChain;
1015 auto occ = contChain;
1016 auto cca = contChainIsAddr;
1017 scope(exit) { breakChain = obc; contChain = occ; contChainIsAddr = cca; }
1018 // new break chain
1019 breakChain = 0;
1020 // "continue" is here
1021 contChain = pc;
1022 contChainIsAddr = true;
1023 // check and decrement counter
1024 auto ck = allocSlot(n.ecount.loc);
1025 freeSlot(ck); // we don't need that slot anymore, allow body to reuse it
1026 emit(Op.ge, ck, cnt, one);
1027 emit(Op.xtrue, ck);
1028 breakChain = emitJumpChain(breakChain); // get out of here
1029 // decrement counter in-place
1030 emit(Op.sub, cnt, cnt, one);
1031 // body
1032 compileStatement(n.ebody);
1033 // and again
1034 emitJumpTo(contChain);
1035 // "break" is here
1036 fixJumpChain(breakChain, pc);
1037 // free used slots
1038 freeSlot(one);
1039 freeSlot(cnt);
1041 (NodeSwitch n) {
1042 // switch expression
1043 auto expr = compileExpr(n.e);
1044 if (n.cases.length) {
1045 // has some cases
1046 uint defaultBodyAddr = 0; // address of "default" node body (even if it is empty)
1047 uint lastFalltrhuJump = 0; // this is the address of the Op.jump at the end of the previous case node
1048 uint lastCaseSkipJumpAddr = 0; // this is the address of the Op.jump at the failed condition of the previous case node
1049 // new "break" chain
1050 auto obc = breakChain;
1051 scope(exit) breakChain = obc;
1052 breakChain = 0;
1053 // now generate code for case nodes, skipping "default" by the way
1054 foreach (immutable idx, ref ci; n.cases) {
1055 uint nodeSkipChain = 0;
1056 // check condition
1057 if (ci.e !is null) {
1058 // jump here from the last failed condition
1059 fixJumpChain(lastCaseSkipJumpAddr, pc);
1060 auto cond = compileExpr(ci.e);
1061 // trick: reuse "cond" slot
1062 freeSlot(cond);
1063 emit(Op.eq, cond, cond, expr);
1064 emit(Op.xtrue, cond);
1065 // new skip chain
1066 lastCaseSkipJumpAddr = emitJumpChain(0);
1067 } else {
1068 // this is default node, jump over it
1069 nodeSkipChain = emitJumpChain(0);
1070 // and save info
1071 defaultBodyAddr = pc;
1073 // fix fallthru jump
1074 fixJumpChain(lastFalltrhuJump, pc);
1075 // the body is here
1076 compileStatement(ci.st);
1077 // new fallthru chain
1078 lastFalltrhuJump = (idx < n.cases.length-1 ? emitJumpChain(0) : 0);
1079 // fix "default skip" chain
1080 fixJumpChain(nodeSkipChain, pc);
1082 // we can free expression slot right here
1083 freeSlot(expr);
1084 // do we have default node?
1085 if (defaultBodyAddr) {
1086 // jump there from the last failed condition
1087 fixJumpChain(lastCaseSkipJumpAddr, defaultBodyAddr);
1088 } else {
1089 // jump here from the last failed condition
1090 fixJumpChain(lastCaseSkipJumpAddr, pc);
1092 // fix last fallthru jump
1093 fixJumpChain(lastFalltrhuJump, pc);
1094 // fix "break" chain
1095 fixJumpChain(breakChain, pc);
1096 } else {
1097 freeSlot(expr);
1100 () { assert(0, "unimplemented node: "~typeid(nn).name); },
1104 // ////////////////////////////////////////////////////////////////////////// //
1105 void doCompileFunc (NodeFunc fn) {
1106 assert(fn !is null);
1107 assert(fn.ebody !is null);
1108 assert(fn.name.length);
1109 curfn = fn;
1110 scope(exit) { curfn = null; setupSlots(); }
1112 setupSlots();
1114 // collect var declarations (gml is not properly scoped)
1115 visitNodes(curfn.ebody, (Node n) {
1116 if (auto vd = cast(NodeVarDecl)n) {
1117 foreach (immutable idx, string name; vd.names) {
1118 if (name in locals) {
1119 if (vd.asGlobal) compileError(vd.locs[idx], "conflicting variable '", name, "' declaration (previous at ", vdecls[name].toStringNoFile, ")");
1120 } else if (name in globals) {
1121 if (!vd.asGlobal) compileError(vd.locs[idx], "conflicting variable '", name, "' declaration (previous at ", vdecls[name].toStringNoFile, ")");
1123 vdecls[name] = vd.locs[idx];
1124 if (vd.asGlobal) {
1125 globals[name] = 0;
1126 } else {
1127 // don't allocate slots for locals here, we can remove some locals due to arguments aliasing later
1128 //firstFreeSlot = allocSlot(vd.locs[idx]);
1129 //locals[name] = cast(ubyte)firstFreeSlot;
1130 //++firstFreeSlot;
1131 locals[name] = 42; // temporary value
1135 return VisitRes.Continue;
1138 findUninitialized();
1140 /* here we will do very simple analysis for code like
1141 * var m, n;
1142 * m = argument0;
1143 * n = argument1;
1144 * ...no `arument0` and `argument1` usage after this point
1145 * we can just alias `m` to `arument0`, and `n` to `argument1` then
1149 uint firstBadStatement = 0;
1150 foreach (immutable idx, Node st; curfn.ebody.stats) {
1151 if (cast(NodeStatementEmpty)st || cast(NodeStatementExpr)st || cast(NodeVarDecl)st) {
1152 firstBadStatement = cast(uint)idx+1;
1153 } else {
1154 break;
1157 if (firstBadStatement > 0) {
1158 bool[string] varsused;
1159 // scan statements, find assignments
1160 foreach (immutable idx, Node st; curfn.ebody.stats[0..firstBadStatement]) {
1161 if (auto ass = cast(NodeStatementAss)st) {
1162 // wow, assignment
1163 auto lv = cast(NodeId)ass.el;
1164 auto rv = cast(NodeId)ass.er;
1165 if (lv !is null && rv !is null) {
1166 // "a = b"
1167 { import std.stdio : stderr; stderr.writeln("found assignment: '", lv.name, "' = '", rv.name, "'"); }
1168 if (argvar(rv.name) >= 0 && argvar(lv.name) < 0) {
1169 // "a = argumentx"
1170 if (lv.name in varsused || rv.name in varsused) continue; // no wai
1171 if (lv.name !in locals) continue; // not a local
1172 auto ai = argvar(rv.name);
1173 if (aaliases[ai].length && aaliases[ai] != lv.name) continue; // already have an alias (TODO)
1174 aaliases[ai] = lv.name; // possible alias
1175 } else {
1176 // check for reassignment
1177 if (lv.name !in varsused) {
1178 // not used before, but used now; remove it from aliases
1179 foreach (ref an; aaliases) if (an == lv.name) an = null;
1180 varsused[lv.name] = true;
1186 // now check if we have any assignment to aliased argument
1187 foreach (immutable idx, string an; aaliases) {
1188 if (an.length == 0) continue;
1189 visitNodes(curfn.ebody, (Node n) {
1190 if (auto ass = cast(NodeStatementAss)n) {
1191 if (auto id = cast(NodeId)ass.el) {
1192 auto ai = argvar(id.name);
1193 if (ai >= 0) aaliases[idx] = null;
1194 return VisitRes.Stop;
1197 return VisitRes.Continue;
1200 // remove aliases from locals (we don't need slots for 'em)
1201 foreach (immutable idx, string an; aaliases) {
1202 if (an.length == 0) continue;
1203 locals.remove(an);
1205 // dump aliases
1207 import std.stdio : stderr;
1208 foreach (immutable idx, string an; aaliases) {
1209 if (an.length) stderr.writeln("'argument", idx, "' is aliased to '", an, "'");
1215 // now assign slots to locals
1216 foreach (string name; locals.keys) {
1217 firstFreeSlot = allocSlot(vdecls[name]);
1218 locals[name] = cast(ubyte)firstFreeSlot;
1219 ++firstFreeSlot;
1222 if (auto sid = curfn.name in VM.scripts) {
1223 if (VM.scriptPCs[*sid] < 0) return; // can't override built-in function
1226 uint sid = sid4name(curfn.name);
1227 /*debug(vm_exec)*/ { import std.stdio; writeln("compiling '", curfn.name, "' (", sid, ")..."); }
1228 auto startpc = emit(Op.enter);
1229 curfn.pcs = pc;
1230 compileStatement(curfn.ebody);
1231 emitPLit(curfn.loc, 0, 0);
1232 emit(Op.ret, 0);
1233 curfn.pce = pc;
1234 // patch enter
1235 VM.code[startpc] = (locals.length<<24)|((maxUsedSlot+1)<<16)|(maxArgUsed<<8)|cast(ubyte)Op.enter;
1236 VM.scriptPCs[sid] = startpc;
1237 VM.scriptASTs[sid] = curfn;