compiler now compiles array load/store and compound store (lowered to `with`)
[gaemu.git] / gaem / runner / vm.d
blob5879064c3eee0c295484c08672889d585db882a5
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.vm is aliced;
20 import std.stdio : File;
21 import std.traits;
23 import gaem.ungmk;
24 import gaem.parser;
26 import gaem.runner.strpool;
27 import gaem.runner.value;
28 import gaem.runner.opcodes;
31 // ////////////////////////////////////////////////////////////////////////// //
32 public static struct VM {
33 @disable this ();
34 @disable this (this);
36 public:
37 enum Slot {
38 Self,
39 Other,
40 Argument0,
41 Argument1,
42 Argument2,
43 Argument3,
44 Argument4,
45 Argument5,
46 Argument6,
47 Argument7,
48 Argument8,
49 Argument9,
50 Argument10,
51 Argument11,
52 Argument12,
53 Argument13,
54 Argument14,
55 Argument15,
58 private:
59 alias PrimDg = Real delegate (uint pc, Real* bp, ubyte argc);
61 package(gaem.runner):
62 __gshared uint[] code; // [0] is reserved
63 __gshared uint[string] scripts; // name -> number
64 __gshared string[uint] scriptNum2Name;
65 __gshared int[] scriptPCs; // by number; 0 is reserved; <0: primitive number
66 __gshared NodeFunc[] scriptASTs; // by number
67 __gshared PrimDg[] prims; // by number
68 __gshared Real[] vpool; // pool of values
69 __gshared Real[] globals;
70 __gshared Gmk gmk;
73 shared static this () {
74 code.length = 1;
75 VM.scriptPCs.length = 1;
76 scriptASTs.length = 1;
77 VM.prims.length = 1;
80 static public:
81 void setGmk (Gmk agmk) {
82 assert(agmk !is null);
83 assert(gmk is null);
84 gmk = agmk;
87 void opIndexAssign(DG) (DG dg, string name) if (isCallable!DG) {
88 assert(name.length > 0);
89 uint sid;
90 if (auto sptr = name in VM.scripts) {
91 sid = *sptr;
92 } else {
93 sid = cast(uint)VM.scriptPCs.length;
94 if (sid > 32767) assert(0, "too many scripts");
95 assert(scriptASTs.length == sid);
96 // reserve slots
97 VM.scriptPCs ~= 0;
98 scriptASTs ~= null;
99 scriptNum2Name[sid] = name;
100 VM.scripts[name] = sid;
102 auto pnum = cast(uint)VM.prims.length;
103 assert(pnum);
104 VM.scriptPCs[sid] = -cast(int)pnum;
105 VM.prims ~= register(dg);
108 Real exec(A...) (string name, A args) {
109 static assert(A.length < 16, "too many arguments");
110 auto sid = VM.scripts[name];
111 assert(curframe is null);
112 // create frame
113 if (stack.length < 65536) stack.length = 65536;
114 curframe = &frames[0];
115 curframe.bp = 0;
116 curframe.script = sid;
117 stack[0..VM.Slot.max+1] = 0;
118 foreach (immutable idx, immutable a; args) {
119 static if (is(typeof(a) : const(char)[])) {
120 //FIXME
121 assert(0);
122 } else static if (is(typeof(a) : Real)) {
123 stack[VM.Slot.Argument0+idx] = cast(Real)a;
124 } else {
125 static assert(0, "invalid argument type");
128 //{ import std.stdio; writeln(VM.scriptPCs[sid]); }
129 return doExec(VM.scriptPCs[sid]);
134 // ////////////////////////////////////////////////////////////////////////// //
135 private:
136 static struct CallFrame {
137 uint script; // script id
138 uint bp; // base pointer (address of the current frame in stack)
139 uint pc; // current pc; will be set on "call"; it is used by callee
140 ubyte rval; // slot for return value; will be set on "call"; it is used by callee
141 @disable this (this);
145 __gshared CallFrame[32768] frames;
146 __gshared CallFrame* curframe;
147 __gshared Real[] stack;
149 void runtimeError(A...) (uint pc, A args) {
150 import std.stdio : stderr;
151 stderr.writef("ERROR at %08X: ", pc);
152 stderr.writeln(args);
153 // try to build stack trace
154 if (curframe !is null) {
155 curframe.pc = pc;
156 auto cf = curframe;
157 for (;;) {
158 stderr.writefln("%08X: %s", cf.pc, VM.scriptNum2Name[cf.script]);
159 if (cf is frames.ptr) break; // it's not legal to compare pointers from different regions
160 --cf;
163 throw new Exception("fuuuuu");
167 // current frame must be properly initialized
168 Real doExec (uint pc) {
169 enum BinOpMixin(string op, string ack="") =
170 "auto dest = opx.opDest;\n"~
171 "auto o0 = bp[opx.opOp0];\n"~
172 "auto o1 = bp[opx.opOp1];\n"~
173 ack~
174 "if (!o0.isReal || !o1.isReal) runtimeError(cast(uint)(cptr-VM.code.ptr-1), `invalid type`);\n"~
175 "bp[dest] = o0"~op~"o1;\n"~
176 "break;";
177 enum BinIOpMixin(string op, string ack="") =
178 "auto dest = opx.opDest;\n"~
179 "auto o0 = bp[opx.opOp0];\n"~
180 "auto o1 = bp[opx.opOp1];\n"~
181 ack~
182 "if (!o0.isReal || !o1.isReal) runtimeError(cast(uint)(cptr-VM.code.ptr-1), `invalid type`);\n"~
183 "bp[dest] = lrint(o0)"~op~"lrint(o1);\n"~
184 "break;";
186 enum BinCmpMixin(string op) =
187 "auto dest = opx.opDest;\n"~
188 "auto o0 = bp[opx.opOp0];\n"~
189 "auto o1 = bp[opx.opOp1];\n"~
190 "assert(!o0.isUndef && !o1.isUndef);\n"~
191 "if (o0.isString) {\n"~
192 " if (!o1.isString) runtimeError(cast(uint)(cptr-VM.code.ptr-1), `invalid type`);\n"~
193 " string s0 = getDynStr(o0.getStrId);\n"~
194 " string s1 = getDynStr(o1.getStrId);\n"~
195 " bp[dest] = (s0 "~op~" s1 ? 1 : 0);\n"~
196 "} else {\n"~
197 " assert(o0.isReal);\n"~
198 " if (!o1.isReal) runtimeError(cast(uint)(cptr-VM.code.ptr-1), `invalid type`);\n"~
199 " bp[dest] = (o0 "~op~" o1 ? 1 : 0);\n"~
200 "}\n"~
201 "break;";
203 enum BinLogMixin(string op) =
204 "auto dest = opx.opDest;\n"~
205 "auto o0 = bp[opx.opOp0];\n"~
206 "auto o1 = bp[opx.opOp1];\n"~
207 "assert(!o0.isUndef && !o1.isUndef);\n"~
208 "if (o0.isString) {\n"~
209 " if (!o1.isString) runtimeError(cast(uint)(cptr-VM.code.ptr-1), `invalid type`);\n"~
210 " string s0 = getDynStr(o0.getStrId);\n"~
211 " string s1 = getDynStr(o1.getStrId);\n"~
212 " bp[dest] = (s0.length "~op~" s1.length ? 1 : 0);\n"~
213 "} else {\n"~
214 " assert(o0.isReal);\n"~
215 " if (!o1.isReal) runtimeError(cast(uint)(cptr-VM.code.ptr-1), `invalid type`);\n"~
216 " bp[dest] = (lrint(o0) "~op~" lrint(o1) ? 1 : 0);\n"~
217 "}\n"~
218 "break;";
220 static if (is(Real == float)) {
221 import core.stdc.math : lrint = lrintf;
222 } else static if (is(Real == double)) {
223 import core.stdc.math : lrint;
224 } else {
225 static assert(0, "wtf?!");
227 assert(curframe !is null);
228 assert(pc > 0 && pc < VM.code.length);
229 assert(VM.code[pc].opCode == Op.enter);
230 assert(stack.length > 0);
231 auto bp = &stack[curframe.bp];
232 auto origcf = curframe;
233 auto cptr = VM.code.ptr+pc;
234 //if (stack.length < 65536) stack.length = 65536;
235 debug(vm_exec) uint maxslots = VM.Slot.max+1;
236 for (;;) {
237 debug(vm_exec) {
238 import std.stdio : stderr;
239 foreach (immutable idx; 0..maxslots) stderr.writeln(" ", idx, ": ", bp[idx]);
240 dumpInstr(stderr, cast(uint)(cptr-VM.code.ptr));
242 auto opx = *cptr++;
243 switch (opx.opCode) {
244 case Op.nop:
245 break;
247 case Op.copy: // copy regs; dest: dest reg; op0: first reg to copy; op1: number of regs to copy (0: no copy, lol)
248 import core.stdc.string : memmove;
249 auto dest = opx.opDest;
250 auto first = opx.opOp0;
251 auto count = opx.opOp1;
252 if (count) memmove(bp+dest, bp+first, count*Real.sizeof);
253 break;
255 case Op.lnot: // lognot
256 auto dest = opx.opDest;
257 auto o0 = bp[opx.opOp0];
258 assert(!o0.isUndef);
259 if (o0.isString) {
260 auto s0 = getDynStr(o0.getStrId);
261 bp[dest] = (s0.length ? 0 : 1);
262 } else {
263 bp[dest] = (lrint(o0) ? 0 : 1);
265 break;
266 case Op.neg:
267 auto dest = opx.opDest;
268 auto o0 = bp[opx.opOp0];
269 if (!o0.isReal) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "invalid type");
270 bp[dest] = -o0;
271 break;
272 case Op.bneg:
273 auto dest = opx.opDest;
274 auto o0 = bp[opx.opOp0];
275 if (!o0.isReal) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "invalid type");
276 bp[dest] = cast(int)(~(cast(int)lrint(o0)));
277 break;
279 case Op.add:
280 auto dest = opx.opDest;
281 auto o0 = bp[opx.opOp0];
282 auto o1 = bp[opx.opOp1];
283 assert(!o0.isUndef && !o1.isUndef);
284 if (o0.isString) {
285 if (!o1.isString) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "invalid type");
286 string s0 = getDynStr(o0.getStrId);
287 string s1 = getDynStr(o1.getStrId);
288 //FIXME
289 if (s0.length == 0) {
290 bp[dest] = o1;
291 } else if (s1.length == 0) {
292 bp[dest] = o0;
293 } else {
294 bp[dest] = buildStrId(newDynStr(s0~s1));
296 } else {
297 assert(o0.isReal);
298 if (!o1.isReal) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "invalid type");
299 bp[dest] = o0+o1;
301 break;
302 case Op.sub: mixin(BinOpMixin!"-");
303 case Op.mul: mixin(BinOpMixin!"*");
304 case Op.mod: mixin(BinOpMixin!("%", q{ if (o1 == 0) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "division by zero"); }));
305 case Op.div: mixin(BinOpMixin!("/", q{ if (o1 == 0) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "division by zero"); }));
306 case Op.rdiv: mixin(BinOpMixin!("/", q{ if (o1 == 0) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "division by zero"); }));
307 case Op.bor: mixin(BinIOpMixin!"|");
308 case Op.bxor: mixin(BinIOpMixin!"^");
309 case Op.band: mixin(BinIOpMixin!"&");
310 case Op.shl: mixin(BinIOpMixin!"<<");
311 case Op.shr: mixin(BinIOpMixin!">>");
313 case Op.lt: mixin(BinCmpMixin!"<");
314 case Op.le: mixin(BinCmpMixin!"<=");
315 case Op.gt: mixin(BinCmpMixin!">");
316 case Op.ge: mixin(BinCmpMixin!">=");
317 case Op.eq: mixin(BinCmpMixin!"==");
318 case Op.ne: mixin(BinCmpMixin!"!=");
320 case Op.lor: mixin(BinLogMixin!"||");
321 case Op.land: mixin(BinLogMixin!"&&");
322 case Op.lxor: assert(0);
324 case Op.plit: // dest becomes pool slot val (val: 2 bytes) -- load value from pool slot
325 auto dest = opx.opDest;
326 uint idx = cast(ushort)opx.op2Byte;
327 if (idx == ushort.max) {
328 assert((*cptr).opCode == Op.skip);
329 idx = (*cptr++).op3Byte;
331 bp[dest] = VM.vpool.ptr[idx];
332 break;
333 case Op.ilit: // dest becomes ilit val (val: short) -- load small integer literal
334 case Op.slit:
335 auto dest = opx.opDest;
336 bp[dest] = opx.opILit;
337 break;
338 case Op.xlit: // dest becomes integer(!) val (val: short) -- load small integer literal
339 auto dest = opx.opDest;
340 *cast(uint*)(bp+dest) = opx.opILit;
341 break;
343 case Op.jump: // addr: 3 bytes
344 cptr = VM.code.ptr+opx.op3Byte;
345 break;
346 case Op.xtrue: // dest is reg to check; skip next instruction if dest is "gml true" (i.e. fabs(v) >= 0.5`)
347 if (lrint(bp[opx.opDest]) != 0) ++cptr;
348 break;
349 case Op.xfalse: // dest is reg to check; skip next instruction if dest is "gml false" (i.e. fabs(v) >= 0.5`)
350 if (lrint(bp[opx.opDest]) == 0) ++cptr;
351 break;
353 case Op.call: // dest is result; op0: call frame (see below); op1: number of args
354 // call frame is:
355 // new function frame
356 // int scriptid (after op1+3 slots)
357 // note that there should be no used registers after those (as that will be used as new function frame regs)
358 auto sid = *cast(uint*)(bp+opx.opOp0+VM.Slot.Argument0+opx.opOp1);
359 if (sid >= VM.scriptPCs.length) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "invalid script id");
360 pc = VM.scriptPCs.ptr[sid];
361 if (pc < 1 || pc >= VM.code.length) {
362 if (pc&0x8000_0000) {
363 // this is primitive
364 uint pid = -cast(int)pc;
365 if (pid >= VM.prims.length) assert(0, "wtf?!");
366 bp[opx.opDest] = VM.prims.ptr[pid](cast(uint)(cptr-VM.code.ptr-1), bp+opx.opOp0, opx.opOp1);
367 break;
368 } else {
369 string scname;
370 foreach (auto kv; VM.scripts.byKeyValue) if (kv.value == sid) { scname = kv.key; break; }
371 runtimeError(cast(uint)(cptr-VM.code.ptr-1), "trying to execute undefined script '", scname, "'");
374 debug(vm_exec) {
375 import std.stdio : stderr;
376 stderr.writeln("calling '", scriptNum2Name[sid], "'");
377 foreach (immutable aidx; 0..opx.opOp1) stderr.writeln(" ", bp[opx.opOp0+VM.Slot.Argument0+aidx]);
379 // if this is tail call, just do it as tail call then
380 // but don't optimize out top-level call, heh
381 if (curframe !is origcf && (*cptr).opCode == Op.ret) {
382 import core.stdc.string : memcpy;
383 // yay, it is a tail call!
384 // copy arguments (it's safe to use `memcpy()` here); `self` and `other` are automatically ok
385 if (opx.opOp1) memcpy(bp+VM.Slot.Argument0, bp+opx.opOp0+VM.Slot.Argument0, Real.sizeof*opx.opOp1);
386 // simply replace current frame with new one
387 } else {
388 bp[opx.opOp0..opx.opOp0+VM.Slot.Argument0] = bp[0..VM.Slot.Argument0]; // copy `self` and `other`
389 curframe.pc = cast(uint)(cptr-VM.code.ptr);
390 curframe.rval = opx.opDest;
391 ++curframe;
392 curframe.bp = curframe[-1].bp+opx.opOp0;
393 bp = &stack[curframe.bp];
395 curframe.script = sid;
396 cptr = VM.code.ptr+VM.scriptPCs.ptr[sid];
397 //assert((*cptr).opCode == Op.enter);
398 // clear unused arguments, if any
399 // we know that first instruction is always Op.enter, use that fact
400 auto aused = (*cptr).opDest+1;
401 //{ import std.stdio; writeln("aused=", aused, "; op1=", opx.opOp1); }
402 if (aused > opx.opOp1) bp[VM.Slot.Argument0+opx.opOp1..VM.Slot.Argument0+aused] = 0;
403 break;
405 case Op.enter: // dest: number of arguments used; op0: number of stack slots used (including result and args); op1: number of locals
406 if (curframe.bp+opx.opOp0 > stack.length) {
407 stack.length = curframe.bp+opx.opOp0;
408 bp = &stack[curframe.bp];
410 //foreach (immutable idx; VM.Slot.max+1..VM.Slot.max+1+opx.opOp1) bp[idx] = 0; // clear locals
411 if (opx.opOp1) bp[VM.Slot.max+1..VM.Slot.max+1+opx.opOp1] = 0; // clear locals
412 debug(vm_exec) maxslots = opx.opOp0;
413 debug(vm_exec) { import std.stdio : stderr; foreach (immutable idx; VM.Slot.Argument0..VM.Slot.Argument15+1) stderr.writeln(" :", bp[idx]); }
414 break;
416 case Op.ret: // dest is retvalue; it is copied to reg0; other stack items are discarded
417 if (curframe is origcf) return bp[opx.opDest]; // done
418 assert(cast(uint)curframe > cast(uint)origcf);
419 --curframe;
420 auto rv = bp[opx.opDest];
421 // remove stack frame
422 bp = &stack[curframe.bp];
423 cptr = VM.code.ptr+curframe.pc;
424 bp[curframe.rval] = rv;
425 debug(vm_exec) { import std.stdio : stderr; stderr.writeln("RET(", curframe.rval, "): ", rv); }
426 break;
428 //as we are using refloads only in the last stage of assignment, they can create values
430 case Op.lref: // load slot reference to dest
431 *cast(int*)bp[opx.opDest] = opx.opOp0;
432 break;
433 case Op.fref: // load field reference; op0: obj id; op1: int! reg (field id); can create fields
434 assert(0);
435 case Op.fval: // load field value; op0: obj id; op1: int! reg (field id)
436 assert(0);
437 case Op.i1ref: // load indexed reference; op0: varref; op1: index; can create arrays
438 assert(0);
439 case Op.i2ref: // load indexed reference; op0: varref; op1: first index; (op1+1): second index; can create arrays
440 assert(0);
441 case Op.i1val: // load indexed value; op0: varref; op1: index
442 assert(0);
443 case Op.i2val: // load indexed value; op0: varref; op1: first index; (op1+1): second index
444 assert(0);
446 case Op.rstore: // store to op0-varref from op1
447 auto x = *cast(int*)bp[opx.opOp0];
448 assert(x >= 0 && x <= 255);
449 bp[x] = bp[opx.opOp1];
450 break;
452 //case Op.lstore: // store value *from* dest into local slot; op0: slot number
453 // bp[opx.opOp0] = bp[opx.opDest];
454 // break;
456 case Op.fstore: // store value *from* dest into field; op0: obj id; op1: int! reg (field id); can create fields
457 assert(0);
458 case Op.i1store: // store value *from* dest into indexed reference; op0: varref; op1: index; can create arrays
459 assert(0);
460 case Op.i2store: // store value *from* dest into indexed reference; op0: varref; op1: first index; (op1+1): second index; can create arrays
461 assert(0);
464 //case Op.oload: // load object field to dest; op0: int reg (obj id; -666: global object); op1: int reg (field id)
465 //case Op.iload: // load indexed (as iref)
466 //case Op.mload: // load indexed (as mref)
467 default: assert(0);
473 // ////////////////////////////////////////////////////////////////////////// //
474 // create primitive delegate for D delegate/function
475 // D function can include special args like:
476 // Real* -- bp
477 // Real[] -- all args
478 // string, integer, float
479 // no ref args are supported, sorry
480 private VM.PrimDg register(DG) (DG dg) @trusted if (isCallable!DG) {
481 import core.stdc.math : lrint;
482 assert(dg !is null);
483 // build call thunk
484 return delegate (uint pc, Real* bp, ubyte argc) {
485 // prepare arguments
486 Parameters!DG arguments;
487 alias rt = ReturnType!dg;
488 // (Real* bp, ubyte argc)
489 static if (arguments.length == 2 && is(typeof(arguments[1]) == Real*) && is(typeof(arguments[2]) : int)) {
490 static if (is(rt == void)) {
491 cast(void)dg(bp, cast(typeof(arguments[2]))argc);
492 return cast(Real)0;
493 } else {
494 return Value(dg(bp, cast(typeof(arguments[2]))argc));
496 } else {
497 foreach (immutable idx, ref arg; arguments) {
498 // is last argument suitable for `withobj`?
499 static if (is(typeof(arg) == Real[])) {
500 arg = bp[0..VM.Slot.Argument0+argc];
501 } else {
502 static assert(idx < 16, "too many arguments required");
503 static if (is(typeof(arg) == const(char)[]) || is(typeof(arg) == string)) {
504 auto v = bp[VM.Slot.Argument0+idx];
505 if (!v.isString) runtimeError(pc, "invalid argument type");
506 arg = getDynStr(v.getStrId);
507 } else static if (is(typeof(arg) == bool)) {
508 auto v = bp[VM.Slot.Argument0+idx];
509 if (v.isString) arg = (v.getStrId != 0);
510 else if (v.isReal) arg = (lrint(v) != 0);
511 else runtimeError(pc, "invalid argument type");
512 } else static if (is(typeof(arg) : long) || is(typeof(arg) : double)) {
513 auto v = bp[VM.Slot.Argument0+idx];
514 if (!v.isReal) runtimeError(pc, "invalid D argument type");
515 arg = cast(typeof(arg))v;
519 static if (is(rt == void)) {
520 cast(void)dg(arguments);
521 return cast(Real)0;
522 } else {
523 return Value(dg(arguments));