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
;
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
;
36 while (spc
< VM
.code
.length
) spc
+= dumpInstr(stdout
, spc
, VM
.code
);
40 // ////////////////////////////////////////////////////////////////////////// //
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?
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;
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;
81 ubyte allocSlot (Loc loc
, int ddest
=-1) {
83 assert(ddest
< slots
.length
);
84 return cast(ubyte)ddest
;
86 foreach (immutable idx
; firstFreeSlot
..slots
.length
) {
88 if (idx
> maxUsedSlot
) maxUsedSlot
= cast(uint)idx
;
90 return cast(ubyte)idx
;
93 compileError(loc
, "out of free slots");
98 ubyte allocSlots (Loc loc
, int count
) {
99 assert(count
> 0 && count
< slots
.length
);
100 foreach (immutable idx
; firstFreeSlot
..slots
.length
-count
) {
102 foreach (immutable c
; idx
..idx
+count
) if (slots
[c
]) { ok
= false; break; }
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");
114 ubyte reserveCallSlots (Loc loc
, uint resnum
) {
115 foreach_reverse (immutable idx
, bool v
; slots
) {
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");
126 void freeSlot (ubyte num
) {
127 if (num
>= firstFreeSlot
) {
134 void freeSlots (ubyte num
, int count
) {
135 while (count
-- > 0) {
137 if (++num
== 0) break;
142 // ////////////////////////////////////////////////////////////////////////// //
143 int argvar (string 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;
167 void compileError(A
...) (Loc loc
, A args
) {
168 if (curfn
.pp
!is null) {
169 curfn
.pp
.error(loc
, args
);
171 import std
.stdio
: stderr
;
172 stderr
.writeln("ERROR at ", loc
, ": ", args
);
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
) {
187 auto sid
= cast(uint)VM
.scriptPCs
.length
;
188 if (sid
> 32767) compileError(curfn
.loc
, "too many scripts");
189 assert(VM
.scriptASTs
.length
== sid
);
192 VM
.scriptASTs
~= null;
193 VM
.scriptNum2Name
[sid
] = name
;
194 VM
.scripts
[name
] = 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
;
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
;
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
;
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);
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);
243 void fixJumpChain (uint chain
, uint addr
) {
244 assert(chain
<= 0xffffff);
245 assert(addr
<= 0xffffff);
247 auto nc
= op3Byte(VM
.code
[chain
]);
248 VM
.code
[chain
] = (VM
.code
[chain
]&0xff)|
(addr
<<8);
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 () {
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
284 return VisitRes
.SkipChildren
;
286 return VisitRes
.Continue
;
290 void processStatement (Node nn
) {
291 if (nn
is null) return;
292 return selectNode
!void(nn
,
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");
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
); },
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
);
321 auto before
= inited
.dup
;
322 processStatement(n
.et
);
323 auto tset
= inited
.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;
332 (NodeStatementBreakCont 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
);
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
);
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
);
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
);
366 // "expr" can't contain assignments, so it's safe here
368 auto before
= inited
.dup
;
369 foreach (ref ci
; n
.cases
) {
370 processExpr(ci
.e
); // can't contain assignments
372 if (ci
.st
!is null) {
374 processStatement(ci
.st
);
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; }
388 foreach (string name
; locals
.keys
) {
390 { import std
.stdio
; writeln("removing unused local '", name
, "'"); }
391 //unusedLocs ~= Info(vdecls[name], 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
;
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
));
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
) {
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
);
424 if (vpidx
== uint.max
) {
425 vpidx
= cast(uint)VM
.vpool
.length
;
426 if (vpidx
>= 0xffffff) compileError(loc
, "too many constants");
429 if (vpidx
< ushort.max
) {
430 emit2Bytes(Op
.plit
, dest
, cast(ushort)vpidx
);
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
;
446 case "self": return VM
.Slot
.Self
;
447 case "other": return VM
.Slot
.Other
;
451 foreach (immutable idx
, string an
; aaliases
) if (an
== name
) return cast(int)VM
.Slot
.Argument0
+idx
;
453 if (auto v
= name
in locals
) return *v
;
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
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
,
478 if (!noLocalsCheck
) {
479 auto ksl
= isKnownSlot(n
);
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
);
485 emit(Op
.copy
, dest
, cast(ubyte)ksl
, 1);
488 // this may be object, or sprite, or background...
489 if (auto oid
= objectByName(n
.name
)) {
491 auto dest
= allocSlot(n
.loc
, ddest
);
493 emit2Bytes(Op
.oval
, dest
, cast(short)oid
);
495 //emit2Bytes(Op.ilit, dest, cast(short)oid); //FIXME: introduce new opcode for this?
496 emitPLit(n
.loc
, dest
, oid
);
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
));
508 emit(Op
.fval
, dest
, VM
.Slot
.Self
, fid
);
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") {
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);
525 emit(Op
.fval
, dest
, oids
, fid
);
527 emit(Op
.fval
, dest
, (oid
.name
== "self" ? VM
.Slot
.Self
: VM
.Slot
.Other
), fid
);
533 // this is some complex expression
534 auto oids
= compileExpr(n
.e
);
536 auto fid
= allocSlot(n
.loc
);
537 emit2Bytes(Op
.xlit
, fid
, cast(short)allocateFieldId(n
.name
));
538 emit(Op
.fval
, dest
, oids
, fid
);
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);
555 auto oid
= VM
.code
[pc
-1].opOp0
;
556 auto fid
= VM
.code
[pc
-1].opOp1
;
557 setpc(pc
-1); // remove this opcode
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
);
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));
569 () { assert(0, "unimplemented node: "~typeid(nn
).name
); },
574 // assVal: "new value" slot
576 void compileVarStore (Node nn
, ubyte assVal
, bool noLocalsCheck
=false) {
579 if (!noLocalsCheck
) {
580 auto ksl
= isKnownSlot(n
);
582 // this is local variable
583 if (assVal
!= ksl
) emit(Op
.copy
, cast(ubyte)ksl
, assVal
, 1);
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
));
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
600 (NodeDot n
) { assert(0, "internal compiler error: NodeDot should not be here"); },
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
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
) {
613 auto oid
= VM
.code
[pc
-1].opOp0
;
614 auto fid
= VM
.code
[pc
-1].opOp1
;
615 setpc(pc
-1); // remove this opcode
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
);
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
); },
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
);
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
);
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);
667 scope(exit
) nn
.pce
= pc
;
668 return selectNode
!ubyte(nn
,
670 auto dest
= allocSlot(n
.loc
, ddest
);
671 emitPLit(n
.loc
, dest
, n
.val
);
675 auto dest
= allocSlot(n
.loc
, ddest
);
676 auto sid
= allocStrConst(n
.val
, n
.loc
);
677 emitPLit(n
.loc
, dest
, buildStrId(sid
));
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
),
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
);
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");
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
);
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
) {
754 while (curelem
!is null) {
755 if (auto dot
= cast(NodeDot
)curelem
) {
756 withStack
~= new NodeId(dot
.loc
, dot
.name
);
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
));
766 } else if (auto id
= cast(NodeId
)idx
.e
) {
770 compileError(curelem
.loc
, "something strange is happened here (0)");
772 } else if (auto id
= cast(NodeId
)curelem
) {
776 compileError(curelem
.loc
, "something strange is happened here (1)");
779 assert(withStack
.length
);
781 foreach (immutable idx, Node n; withStack; reverse) {
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
791 foreach (Node n
; withStack
[1..$]; reverse
) {
792 its
~= allocSlot(n
.loc
);
793 auto xid
= compileVarAccess(n
, asCond
:false, noLocalsCheck
:true);
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);
799 // trivial body of the inner `with`
800 compileVarStore(withStack
[0], assVal
, noLocalsCheck
:true);
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
]);
812 void compileStatement (Node nn
) {
815 scope(exit
) nn
.pce
= pc
;
816 return selectNode
!void(nn
,
819 foreach (Node st
; n
.stats
) compileStatement(st
);
821 (NodeStatementEmpty n
) {},
822 (NodeStatementAss n
) {
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
);
829 // this is known slot, just put value into it directly
830 auto src
= compileExpr(n
.er
, ksl
);
833 auto src
= compileExpr(n
.er
);
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") {
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);
846 emit(Op
.fstore
, src
, oids
, fid
);
848 emit(Op
.fstore
, src
, (oid
.name
== "self" ? VM
.Slot
.Self
: VM
.Slot
.Other
), fid
);
856 // dot access need lowering
857 if (hasDotAccess(n
.el
)) {
858 lowerLeftAss(n
.el
, src
);
860 compileVarStore(n
.el
, src
);
865 (NodeStatementExpr n
) { freeSlot(compileExpr(n
.e
)); },
868 emitPLit(n
.loc
, 0, 0);
871 auto dest
= compileExpr(n
.e
);
877 auto obc
= breakChain
;
878 auto occ
= contChain
;
879 auto cca
= contChainIsAddr
;
880 scope(exit
) { breakChain
= obc
; contChain
= occ
; contChainIsAddr
= cca
; }
882 auto iid
= allocSlot(n
.loc
);
883 auto obs
= compileExpr(n
.e
);
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
889 contChainIsAddr
= false;
892 compileStatement(n
.ebody
);
894 fixJumpChain(contChain
, pc
);
897 // end of loop, break point
898 fixJumpChain(breakChain
, pc
);
903 auto cs
= compileExpr(n
.ec
, asCond
:true);
904 freeSlot(cs
); // yep, free it here
907 // simple optimization
908 jfc
= emitJumpChain(0, Op
.jump
);
909 compileStatement(n
.et
);
911 auto exc
= emitJumpChain(0, Op
.jump
);
912 fixJumpChain(jfc
, pc
);
914 compileStatement(n
.ef
);
916 fixJumpChain(jfc
, pc
);
918 (NodeStatementBreak n
) {
919 breakChain
= emitJumpChain(breakChain
);
921 (NodeStatementContinue n
) {
922 if (contChainIsAddr
) {
923 emitJumpTo(contChain
);
925 contChain
= emitJumpChain(contChain
);
929 compileStatement(n
.einit
);
930 // generate code like this:
931 // jump to "continue"
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
946 compileStatement(n
.enext
);
948 compileStatement(n
.ebody
);
950 fixJumpChain(contChain
, pc
);
952 auto dest
= compileExpr(n
.econd
, asCond
:true);
953 freeSlot(dest
); // yep, right here
954 emit(Op
.xfalse
, dest
); // skip jump on false
957 fixJumpChain(breakChain
, pc
);
961 auto obc
= breakChain
;
962 auto occ
= contChain
;
963 auto cca
= contChainIsAddr
;
964 scope(exit
) { breakChain
= obc
; contChain
= occ
; contChainIsAddr
= cca
; }
967 // "continue" is here
969 contChainIsAddr
= true;
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
976 compileStatement(n
.ebody
);
978 emitJumpTo(contChain
);
980 fixJumpChain(breakChain
, pc
);
984 auto obc
= breakChain
;
985 auto occ
= contChain
;
986 auto cca
= contChainIsAddr
;
987 scope(exit
) { breakChain
= obc
; contChain
= occ
; contChainIsAddr
= cca
; }
991 // new continue chain
993 contChainIsAddr
= false;
995 compileStatement(n
.ebody
);
996 // "continue" is here
997 fixJumpChain(contChain
, pc
);
999 auto dest
= compileExpr(n
.econd
, asCond
:true);
1000 freeSlot(dest
); // yep, right here
1001 emit(Op
.xfalse
, dest
); // skip jump on false
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);
1014 auto obc
= breakChain
;
1015 auto occ
= contChain
;
1016 auto cca
= contChainIsAddr
;
1017 scope(exit
) { breakChain
= obc
; contChain
= occ
; contChainIsAddr
= cca
; }
1020 // "continue" is here
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
);
1028 breakChain
= emitJumpChain(breakChain
); // get out of here
1029 // decrement counter in-place
1030 emit(Op
.sub, cnt
, cnt
, one
);
1032 compileStatement(n
.ebody
);
1034 emitJumpTo(contChain
);
1036 fixJumpChain(breakChain
, pc
);
1042 // switch expression
1043 auto expr
= compileExpr(n
.e
);
1044 if (n
.cases
.length
) {
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
;
1053 // now generate code for case nodes, skipping "default" by the way
1054 foreach (immutable idx
, ref ci
; n
.cases
) {
1055 uint nodeSkipChain
= 0;
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
1063 emit(Op
.eq
, cond
, cond
, expr
);
1064 emit(Op
.xtrue
, cond
);
1066 lastCaseSkipJumpAddr
= emitJumpChain(0);
1068 // this is default node, jump over it
1069 nodeSkipChain
= emitJumpChain(0);
1071 defaultBodyAddr
= pc
;
1073 // fix fallthru jump
1074 fixJumpChain(lastFalltrhuJump
, pc
);
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
1084 // do we have default node?
1085 if (defaultBodyAddr
) {
1086 // jump there from the last failed condition
1087 fixJumpChain(lastCaseSkipJumpAddr
, defaultBodyAddr
);
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
);
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
);
1110 scope(exit
) { curfn
= null; 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
];
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;
1131 locals
[name
] = 42; // temporary value
1135 return VisitRes
.Continue
;
1138 findUninitialized();
1140 /* here we will do very simple analysis for code like
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;
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
) {
1163 auto lv
= cast(NodeId
)ass
.el
;
1164 auto rv
= cast(NodeId
)ass
.er
;
1165 if (lv
!is null && rv
!is null) {
1167 { import std
.stdio
: stderr
; stderr
.writeln("found assignment: '", lv
.name
, "' = '", rv
.name
, "'"); }
1168 if (argvar(rv
.name
) >= 0 && argvar(lv
.name
) < 0) {
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
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;
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
;
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);
1230 compileStatement(curfn
.ebody
);
1231 emitPLit(curfn
.loc
, 0, 0);
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
;