added missing opcodes to VM
[gaemu.git] / gaem / runner / vm.d
blob8ce1b4007cbf71c35258af20d9835422d3795b9e
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;
29 import gaem.runner.objects;
32 // ////////////////////////////////////////////////////////////////////////// //
33 public static struct VM {
34 @disable this ();
35 @disable this (this);
37 public:
38 enum Slot {
39 Self,
40 Other,
41 Argument0,
42 Argument1,
43 Argument2,
44 Argument3,
45 Argument4,
46 Argument5,
47 Argument6,
48 Argument7,
49 Argument8,
50 Argument9,
51 Argument10,
52 Argument11,
53 Argument12,
54 Argument13,
55 Argument14,
56 Argument15,
59 private:
60 alias PrimDg = Real delegate (uint pc, Real* bp, ubyte argc);
62 package(gaem.runner):
63 __gshared uint[] code; // [0] is reserved
64 __gshared uint[string] scripts; // name -> number
65 __gshared string[uint] scriptNum2Name;
66 __gshared int[] scriptPCs; // by number; 0 is reserved; <0: primitive number
67 __gshared NodeFunc[] scriptASTs; // by number
68 __gshared PrimDg[] prims; // by number
69 __gshared Real[] vpool; // pool of values
70 __gshared Real[] globals;
71 __gshared Gmk gmk;
74 shared static this () {
75 code.length = 1;
76 VM.scriptPCs.length = 1;
77 scriptASTs.length = 1;
78 VM.prims.length = 1;
81 static public:
82 void setGmk (Gmk agmk) {
83 assert(agmk !is null);
84 assert(gmk is null);
85 gmk = agmk;
88 void opIndexAssign(DG) (DG dg, string name) if (isCallable!DG) {
89 assert(name.length > 0);
90 uint sid;
91 if (auto sptr = name in VM.scripts) {
92 sid = *sptr;
93 } else {
94 sid = cast(uint)VM.scriptPCs.length;
95 if (sid > 32767) assert(0, "too many scripts");
96 assert(scriptASTs.length == sid);
97 // reserve slots
98 VM.scriptPCs ~= 0;
99 scriptASTs ~= null;
100 scriptNum2Name[sid] = name;
101 VM.scripts[name] = sid;
103 auto pnum = cast(uint)VM.prims.length;
104 assert(pnum);
105 VM.scriptPCs[sid] = -cast(int)pnum;
106 VM.prims ~= register(dg);
109 Real exec(A...) (string name, A args) {
110 static assert(A.length < 16, "too many arguments");
111 auto sid = VM.scripts[name];
112 assert(curframe is null);
113 // create frame
114 if (stack.length < 65536) stack.length = 65536;
115 curframe = &frames[0];
116 curframe.bp = 0;
117 curframe.script = sid;
118 stack[0..VM.Slot.max+1] = 0;
119 foreach (immutable idx, immutable a; args) {
120 static if (is(typeof(a) : const(char)[])) {
121 //FIXME
122 assert(0);
123 } else static if (is(typeof(a) : Real)) {
124 stack[VM.Slot.Argument0+idx] = cast(Real)a;
125 } else {
126 static assert(0, "invalid argument type");
129 //{ import std.stdio; writeln(VM.scriptPCs[sid]); }
130 return doExec(VM.scriptPCs[sid]);
135 // ////////////////////////////////////////////////////////////////////////// //
136 private:
137 static struct CallFrame {
138 uint script; // script id
139 uint bp; // base pointer (address of the current frame in stack)
140 uint pc; // current pc; will be set on "call"; it is used by callee
141 ubyte rval; // slot for return value; will be set on "call"; it is used by callee
142 @disable this (this);
146 __gshared CallFrame[32768] frames;
147 __gshared CallFrame* curframe;
148 __gshared Real[] stack;
150 void runtimeError(A...) (uint pc, A args) {
151 import std.stdio : stderr;
152 stderr.writef("ERROR at %08X: ", pc);
153 stderr.writeln(args);
154 // try to build stack trace
155 if (curframe !is null) {
156 curframe.pc = pc;
157 auto cf = curframe;
158 for (;;) {
159 stderr.writefln("%08X: %s", cf.pc, VM.scriptNum2Name[cf.script]);
160 if (cf is frames.ptr) break; // it's not legal to compare pointers from different regions
161 --cf;
164 throw new Exception("fuuuuu");
168 // current frame must be properly initialized
169 Real doExec (uint pc) {
170 enum BinOpMixin(string op, string ack="") =
171 "auto dest = opx.opDest;\n"~
172 "auto o0 = bp[opx.opOp0];\n"~
173 "auto o1 = bp[opx.opOp1];\n"~
174 ack~
175 "if (!o0.isReal || !o1.isReal) runtimeError(cast(uint)(cptr-VM.code.ptr-1), `invalid type`);\n"~
176 "bp[dest] = o0"~op~"o1;\n"~
177 "break;";
178 enum BinIOpMixin(string op, string ack="") =
179 "auto dest = opx.opDest;\n"~
180 "auto o0 = bp[opx.opOp0];\n"~
181 "auto o1 = bp[opx.opOp1];\n"~
182 ack~
183 "if (!o0.isReal || !o1.isReal) runtimeError(cast(uint)(cptr-VM.code.ptr-1), `invalid type`);\n"~
184 "bp[dest] = lrint(o0)"~op~"lrint(o1);\n"~
185 "break;";
187 enum BinCmpMixin(string op) =
188 "auto dest = opx.opDest;\n"~
189 "auto o0 = bp[opx.opOp0];\n"~
190 "auto o1 = bp[opx.opOp1];\n"~
191 "assert(!o0.isUndef && !o1.isUndef);\n"~
192 "if (o0.isString) {\n"~
193 " if (!o1.isString) runtimeError(cast(uint)(cptr-VM.code.ptr-1), `invalid type`);\n"~
194 " string s0 = getDynStr(o0.getStrId);\n"~
195 " string s1 = getDynStr(o1.getStrId);\n"~
196 " bp[dest] = (s0 "~op~" s1 ? 1 : 0);\n"~
197 "} else {\n"~
198 " assert(o0.isReal);\n"~
199 " if (!o1.isReal) runtimeError(cast(uint)(cptr-VM.code.ptr-1), `invalid type`);\n"~
200 " bp[dest] = (o0 "~op~" o1 ? 1 : 0);\n"~
201 "}\n"~
202 "break;";
204 enum BinLogMixin(string op) =
205 "auto dest = opx.opDest;\n"~
206 "auto o0 = bp[opx.opOp0];\n"~
207 "auto o1 = bp[opx.opOp1];\n"~
208 "assert(!o0.isUndef && !o1.isUndef);\n"~
209 "if (o0.isString) {\n"~
210 " if (!o1.isString) runtimeError(cast(uint)(cptr-VM.code.ptr-1), `invalid type`);\n"~
211 " string s0 = getDynStr(o0.getStrId);\n"~
212 " string s1 = getDynStr(o1.getStrId);\n"~
213 " bp[dest] = (s0.length "~op~" s1.length ? 1 : 0);\n"~
214 "} else {\n"~
215 " assert(o0.isReal);\n"~
216 " if (!o1.isReal) runtimeError(cast(uint)(cptr-VM.code.ptr-1), `invalid type`);\n"~
217 " bp[dest] = (lrint(o0) "~op~" lrint(o1) ? 1 : 0);\n"~
218 "}\n"~
219 "break;";
221 static if (is(Real == float)) {
222 import core.stdc.math : lrint = lrintf;
223 } else static if (is(Real == double)) {
224 import core.stdc.math : lrint;
225 } else {
226 static assert(0, "wtf?!");
228 assert(curframe !is null);
229 assert(pc > 0 && pc < VM.code.length);
230 assert(VM.code[pc].opCode == Op.enter);
231 assert(stack.length > 0);
232 auto bp = &stack[curframe.bp];
233 auto origcf = curframe;
234 auto cptr = VM.code.ptr+pc;
235 //if (stack.length < 65536) stack.length = 65536;
236 debug(vm_exec) uint maxslots = VM.Slot.max+1;
237 for (;;) {
238 debug(vm_exec) {
239 import std.stdio : stderr;
240 foreach (immutable idx; 0..maxslots) stderr.writeln(" ", idx, ": ", bp[idx]);
241 dumpInstr(stderr, cast(uint)(cptr-VM.code.ptr));
243 auto opx = *cptr++;
244 switch (opx.opCode) {
245 case Op.nop:
246 break;
248 case Op.copy: // copy regs; dest: dest reg; op0: first reg to copy; op1: number of regs to copy (0: no copy, lol)
249 import core.stdc.string : memmove;
250 auto dest = opx.opDest;
251 auto first = opx.opOp0;
252 auto count = opx.opOp1;
253 if (count) memmove(bp+dest, bp+first, count*Real.sizeof);
254 break;
256 case Op.lnot: // lognot
257 auto dest = opx.opDest;
258 auto o0 = bp[opx.opOp0];
259 assert(!o0.isUndef);
260 if (o0.isString) {
261 auto s0 = getDynStr(o0.getStrId);
262 bp[dest] = (s0.length ? 0 : 1);
263 } else {
264 bp[dest] = (lrint(o0) ? 0 : 1);
266 break;
267 case Op.neg:
268 auto dest = opx.opDest;
269 auto o0 = bp[opx.opOp0];
270 if (!o0.isReal) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "invalid type");
271 bp[dest] = -o0;
272 break;
273 case Op.bneg:
274 auto dest = opx.opDest;
275 auto o0 = bp[opx.opOp0];
276 if (!o0.isReal) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "invalid type");
277 bp[dest] = cast(int)(~(cast(int)lrint(o0)));
278 break;
280 case Op.add:
281 auto dest = opx.opDest;
282 auto o0 = bp[opx.opOp0];
283 auto o1 = bp[opx.opOp1];
284 assert(!o0.isUndef && !o1.isUndef);
285 if (o0.isString) {
286 if (!o1.isString) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "invalid type");
287 string s0 = getDynStr(o0.getStrId);
288 string s1 = getDynStr(o1.getStrId);
289 //FIXME
290 if (s0.length == 0) {
291 bp[dest] = o1;
292 } else if (s1.length == 0) {
293 bp[dest] = o0;
294 } else {
295 bp[dest] = buildStrId(newDynStr(s0~s1));
297 } else {
298 assert(o0.isReal);
299 if (!o1.isReal) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "invalid type");
300 bp[dest] = o0+o1;
302 break;
303 case Op.sub: mixin(BinOpMixin!"-");
304 case Op.mul: mixin(BinOpMixin!"*");
305 case Op.mod: mixin(BinOpMixin!("%", q{ if (o1 == 0) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "division by zero"); }));
306 case Op.div: mixin(BinOpMixin!("/", q{ if (o1 == 0) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "division by zero"); }));
307 case Op.rdiv: mixin(BinOpMixin!("/", q{ if (o1 == 0) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "division by zero"); }));
308 case Op.bor: mixin(BinIOpMixin!"|");
309 case Op.bxor: mixin(BinIOpMixin!"^");
310 case Op.band: mixin(BinIOpMixin!"&");
311 case Op.shl: mixin(BinIOpMixin!"<<");
312 case Op.shr: mixin(BinIOpMixin!">>");
314 case Op.lt: mixin(BinCmpMixin!"<");
315 case Op.le: mixin(BinCmpMixin!"<=");
316 case Op.gt: mixin(BinCmpMixin!">");
317 case Op.ge: mixin(BinCmpMixin!">=");
318 case Op.eq: mixin(BinCmpMixin!"==");
319 case Op.ne: mixin(BinCmpMixin!"!=");
321 case Op.lor: mixin(BinLogMixin!"||");
322 case Op.land: mixin(BinLogMixin!"&&");
323 case Op.lxor: assert(0);
325 case Op.plit: // dest becomes pool slot val (val: 2 bytes) -- load value from pool slot
326 auto dest = opx.opDest;
327 uint idx = cast(ushort)opx.op2Byte;
328 if (idx == ushort.max) {
329 assert((*cptr).opCode == Op.skip);
330 idx = (*cptr++).op3Byte;
332 bp[dest] = VM.vpool.ptr[idx];
333 break;
334 case Op.ilit: // dest becomes ilit val (val: short) -- load small integer literal
335 case Op.slit:
336 auto dest = opx.opDest;
337 bp[dest] = opx.opILit;
338 break;
339 case Op.xlit: // dest becomes integer(!) val (val: short) -- load small integer literal
340 auto dest = opx.opDest;
341 *cast(uint*)(bp+dest) = opx.opILit;
342 break;
344 case Op.jump: // addr: 3 bytes
345 cptr = VM.code.ptr+opx.op3Byte;
346 break;
347 case Op.xtrue: // dest is reg to check; skip next instruction if dest is "gml true" (i.e. fabs(v) >= 0.5`)
348 if (lrint(bp[opx.opDest]) != 0) ++cptr;
349 break;
350 case Op.xfalse: // dest is reg to check; skip next instruction if dest is "gml false" (i.e. fabs(v) >= 0.5`)
351 if (lrint(bp[opx.opDest]) == 0) ++cptr;
352 break;
354 case Op.call: // dest is result; op0: call frame (see below); op1: number of args
355 // call frame is:
356 // new function frame
357 // int scriptid (after op1+3 slots)
358 // note that there should be no used registers after those (as that will be used as new function frame regs)
359 auto sid = *cast(uint*)(bp+opx.opOp0+VM.Slot.Argument0+opx.opOp1);
360 if (sid >= VM.scriptPCs.length) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "invalid script id");
361 pc = VM.scriptPCs.ptr[sid];
362 if (pc < 1 || pc >= VM.code.length) {
363 if (pc&0x8000_0000) {
364 // this is primitive
365 uint pid = -cast(int)pc;
366 if (pid >= VM.prims.length) assert(0, "wtf?!");
367 bp[opx.opDest] = VM.prims.ptr[pid](cast(uint)(cptr-VM.code.ptr-1), bp+opx.opOp0, opx.opOp1);
368 break;
369 } else {
370 string scname;
371 foreach (auto kv; VM.scripts.byKeyValue) if (kv.value == sid) { scname = kv.key; break; }
372 runtimeError(cast(uint)(cptr-VM.code.ptr-1), "trying to execute undefined script '", scname, "'");
375 debug(vm_exec) {
376 import std.stdio : stderr;
377 stderr.writeln("calling '", scriptNum2Name[sid], "'");
378 foreach (immutable aidx; 0..opx.opOp1) stderr.writeln(" ", bp[opx.opOp0+VM.Slot.Argument0+aidx]);
380 // if this is tail call, just do it as tail call then
381 // but don't optimize out top-level call, heh
382 if (curframe !is origcf && (*cptr).opCode == Op.ret) {
383 import core.stdc.string : memcpy;
384 // yay, it is a tail call!
385 // copy arguments (it's safe to use `memcpy()` here); `self` and `other` are automatically ok
386 if (opx.opOp1) memcpy(bp+VM.Slot.Argument0, bp+opx.opOp0+VM.Slot.Argument0, Real.sizeof*opx.opOp1);
387 // simply replace current frame with new one
388 } else {
389 bp[opx.opOp0..opx.opOp0+VM.Slot.Argument0] = bp[0..VM.Slot.Argument0]; // copy `self` and `other`
390 curframe.pc = cast(uint)(cptr-VM.code.ptr);
391 curframe.rval = opx.opDest;
392 ++curframe;
393 curframe.bp = curframe[-1].bp+opx.opOp0;
394 bp = &stack[curframe.bp];
396 curframe.script = sid;
397 cptr = VM.code.ptr+VM.scriptPCs.ptr[sid];
398 //assert((*cptr).opCode == Op.enter);
399 // clear unused arguments, if any
400 // we know that first instruction is always Op.enter, use that fact
401 auto aused = (*cptr).opDest+1;
402 //{ import std.stdio; writeln("aused=", aused, "; op1=", opx.opOp1); }
403 if (aused > opx.opOp1) bp[VM.Slot.Argument0+opx.opOp1..VM.Slot.Argument0+aused] = 0;
404 break;
406 case Op.enter: // dest: number of arguments used; op0: number of stack slots used (including result and args); op1: number of locals
407 if (curframe.bp+opx.opOp0 > stack.length) {
408 stack.length = curframe.bp+opx.opOp0;
409 bp = &stack[curframe.bp];
411 //foreach (immutable idx; VM.Slot.max+1..VM.Slot.max+1+opx.opOp1) bp[idx] = 0; // clear locals
412 if (opx.opOp1) bp[VM.Slot.max+1..VM.Slot.max+1+opx.opOp1] = 0; // clear locals
413 debug(vm_exec) maxslots = opx.opOp0;
414 debug(vm_exec) { import std.stdio : stderr; foreach (immutable idx; VM.Slot.Argument0..VM.Slot.Argument15+1) stderr.writeln(" :", bp[idx]); }
415 break;
417 case Op.ret: // dest is retvalue; it is copied to reg0; other stack items are discarded
418 if (curframe is origcf) return bp[opx.opDest]; // done
419 assert(cast(uint)curframe > cast(uint)origcf);
420 --curframe;
421 auto rv = bp[opx.opDest];
422 // remove stack frame
423 bp = &stack[curframe.bp];
424 cptr = VM.code.ptr+curframe.pc;
425 bp[curframe.rval] = rv;
426 debug(vm_exec) { import std.stdio : stderr; stderr.writeln("RET(", curframe.rval, "): ", rv); }
427 break;
429 case Op.oval: // load object value to dest; 2byte: object index
430 //TODO: backgrounds, sounds, etc
431 if (auto inst = Instance.firstInstanceOf(opx.op2Byte)) {
432 bp[opx.opDest] = inst.id;
433 } else {
434 bp[opx.opDest] = 0;
436 break;
438 case Op.fval: // load field value; op0: obj id; op1: int! reg (field id)
439 auto fid = *cast(uint*)(bp+opx.opOp1);
440 if (auto inst = Instance.firstInstanceOf(lrint(bp[opx.opOp0]))) {
441 auto v = inst.get(fid);
442 if (v.isUndef) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "trying to read undefined field");
443 bp[opx.opDest] = v;
444 } else {
445 runtimeError(cast(uint)(cptr-VM.code.ptr-1), "trying to read field of invalid instance");
447 break;
448 case Op.i1fval: // load indexed value; op0: obj id; op1: xslots (int! field id, first index)
449 auto fid = *cast(uint*)(bp+opx.opOp1);
450 if (auto inst = Instance.firstInstanceOf(lrint(bp[opx.opOp0]))) {
451 auto v = inst.get(fid, lrint(bp[opx.opOp1+1]));
452 if (v.isUndef) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "trying to read undefined field");
453 bp[opx.opDest] = v;
454 } else {
455 runtimeError(cast(uint)(cptr-VM.code.ptr-1), "trying to read field of invalid instance");
457 break;
458 case Op.i2fval: // load indexed value; op0: obj id; op1: xslots (int! field id, first index, second index)
459 auto fid = *cast(uint*)(bp+opx.opOp1);
460 if (auto inst = Instance.firstInstanceOf(lrint(bp[opx.opOp0]))) {
461 auto v = inst.get(fid, lrint(bp[opx.opOp1+1]), lrint(bp[opx.opOp1+2]));
462 if (v.isUndef) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "trying to read undefined field");
463 bp[opx.opDest] = v;
464 } else {
465 runtimeError(cast(uint)(cptr-VM.code.ptr-1), "trying to read field of invalid instance");
467 break;
469 case Op.fstore: // store value *from* dest into field; op0: obj id; op1: int! reg (field id); can create fields
470 auto fid = *cast(uint*)(bp+opx.opOp1);
471 if (auto inst = Instance.firstInstanceOf(lrint(bp[opx.opOp0]))) {
472 inst.set(bp[opx.opDest], fid);
473 } else {
474 runtimeError(cast(uint)(cptr-VM.code.ptr-1), "trying to set field of invalid instance");
476 break;
477 case Op.i1fstore: // store value *from* dest into indexed reference; op0: obj id; op1: xslots (int! field id, first index)
478 auto fid = *cast(uint*)(bp+opx.opOp1);
479 if (auto inst = Instance.firstInstanceOf(lrint(bp[opx.opOp0]))) {
480 inst.set(bp[opx.opDest], fid, lrint(bp[opx.opOp1+1]));
481 } else {
482 runtimeError(cast(uint)(cptr-VM.code.ptr-1), "trying to set field of invalid instance");
484 break;
485 case Op.i2fstore: // store value *from* dest into indexed reference; op0: obj id; op1: xslots (int! field id, first index, second index)
486 auto fid = *cast(uint*)(bp+opx.opOp1);
487 if (auto inst = Instance.firstInstanceOf(lrint(bp[opx.opOp0]))) {
488 inst.set(bp[opx.opDest], fid, lrint(bp[opx.opOp1+1]), lrint(bp[opx.opOp1+2]));
489 } else {
490 runtimeError(cast(uint)(cptr-VM.code.ptr-1), "trying to set field of invalid instance");
492 break;
494 // `with` is done by copying `self` to another reg, execute the code and restore `self`
495 case Op.siter: // start instance iterator; dest: iterid; op0: objid or instid
496 // this is special: it will skip next instruction if iteration has at least one item
497 // next instruction is always jump, which skips the loop
498 // `self` will be copied to `other`, `other` should be saved
499 uint iid = Instance.newIterator(lrint(bp[opx.opOp0]), lrint(bp[VM.Slot.Other]));
500 *cast(uint*)(bp+opx.opDest) = iid;
501 if (iid) {
502 if (auto nxi = Instance.iteratorNext(iid)) {
503 ++cptr; // skip jump
504 bp[VM.Slot.Other] = bp[VM.Slot.Self];
505 bp[VM.Slot.Self] = nxi;
506 break;
509 // skip loop (execute jump)
510 //cptr = VM.code.ptr+(*cptr).op3Byte;
511 break;
512 case Op.niter: // op0: is iterreg; next instruction is always jump, which continues the loop
513 auto iid = *cast(uint*)(bp+opx.opDest);
514 if (iid) {
515 if (auto nxi = Instance.iteratorNext(iid)) {
516 bp[VM.Slot.Self] = nxi;
517 break; // this will execute jump
520 ++cptr; // skip jump
521 break;
522 case Op.kiter: // kill iterator, should be called to prevent memory leaks
523 auto iid = *cast(uint*)(bp+opx.opDest);
524 if (iid) {
525 // restore `self` and `other`
526 bp[VM.Slot.Self] = bp[VM.Slot.Other];
527 bp[VM.Slot.Other] = Instance.iteratorOldSelf(iid);
529 break;
531 case Op.lirint: // dest = lrint(op0): do lrint() (or another fast float->int conversion)
532 bp[opx.opDest] = lrint(bp[opx.opOp0]);
533 break;
535 default: import std.string : format; assert(0, "invalid opcode: %s".format(opx.opCode));
541 // ////////////////////////////////////////////////////////////////////////// //
542 // create primitive delegate for D delegate/function
543 // D function can include special args like:
544 // Real* -- bp
545 // Real[] -- all args
546 // string, integer, float
547 // no ref args are supported, sorry
548 private VM.PrimDg register(DG) (DG dg) @trusted if (isCallable!DG) {
549 import core.stdc.math : lrint;
550 assert(dg !is null);
551 // build call thunk
552 return delegate (uint pc, Real* bp, ubyte argc) {
553 // prepare arguments
554 Parameters!DG arguments;
555 alias rt = ReturnType!dg;
556 // (Real* bp, ubyte argc)
557 static if (arguments.length == 2 && is(typeof(arguments[1]) == Real*) && is(typeof(arguments[2]) : int)) {
558 static if (is(rt == void)) {
559 cast(void)dg(bp, cast(typeof(arguments[2]))argc);
560 return cast(Real)0;
561 } else {
562 return Value(dg(bp, cast(typeof(arguments[2]))argc));
564 } else {
565 foreach (immutable idx, ref arg; arguments) {
566 // is last argument suitable for `withobj`?
567 static if (is(typeof(arg) == Real[])) {
568 arg = bp[0..VM.Slot.Argument0+argc];
569 } else {
570 static assert(idx < 16, "too many arguments required");
571 static if (is(typeof(arg) == const(char)[]) || is(typeof(arg) == string)) {
572 auto v = bp[VM.Slot.Argument0+idx];
573 if (!v.isString) runtimeError(pc, "invalid argument type");
574 arg = getDynStr(v.getStrId);
575 } else static if (is(typeof(arg) == bool)) {
576 auto v = bp[VM.Slot.Argument0+idx];
577 if (v.isString) arg = (v.getStrId != 0);
578 else if (v.isReal) arg = (lrint(v) != 0);
579 else runtimeError(pc, "invalid argument type");
580 } else static if (is(typeof(arg) : long) || is(typeof(arg) : double)) {
581 auto v = bp[VM.Slot.Argument0+idx];
582 if (!v.isReal) runtimeError(pc, "invalid D argument type");
583 arg = cast(typeof(arg))v;
587 static if (is(rt == void)) {
588 cast(void)dg(arguments);
589 return cast(Real)0;
590 } else {
591 return Value(dg(arguments));