switched to GPLv3 ONLY, because i don't trust FSF anymore
[gaemu.git] / gaem / runner / vm.d
blobfef53156db543119eeb8942792961b70b3c48ab7
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, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module gaem.runner.vm is aliced;
19 import std.stdio : File;
20 import std.traits;
22 import gaem.ungmk;
23 import gaem.parser;
25 import gaem.runner.strpool;
26 import gaem.runner.value;
27 import gaem.runner.opcodes;
28 import gaem.runner.objects;
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 case Op.oval: // load object value to dest; 2byte: object index
429 //TODO: backgrounds, sounds, etc
430 if (auto inst = Instance.firstInstanceOf(opx.op2Byte)) {
431 bp[opx.opDest] = inst.id;
432 } else {
433 bp[opx.opDest] = 0;
435 break;
437 case Op.fval: // load field value; op0: obj id; op1: int! reg (field id)
438 auto fid = *cast(uint*)(bp+opx.opOp1);
439 if (auto inst = Instance.firstInstanceOf(lrint(bp[opx.opOp0]))) {
440 auto v = inst.get(fid);
441 if (v.isUndef) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "trying to read undefined field");
442 bp[opx.opDest] = v;
443 } else {
444 runtimeError(cast(uint)(cptr-VM.code.ptr-1), "trying to read field of invalid instance");
446 break;
447 case Op.i1fval: // load indexed value; op0: obj id; op1: xslots (int! field id, first index)
448 auto fid = *cast(uint*)(bp+opx.opOp1);
449 if (auto inst = Instance.firstInstanceOf(lrint(bp[opx.opOp0]))) {
450 auto v = inst.get(fid, lrint(bp[opx.opOp1+1]));
451 if (v.isUndef) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "trying to read undefined field");
452 bp[opx.opDest] = v;
453 } else {
454 runtimeError(cast(uint)(cptr-VM.code.ptr-1), "trying to read field of invalid instance");
456 break;
457 case Op.i2fval: // load indexed value; op0: obj id; op1: xslots (int! field id, first index, second index)
458 auto fid = *cast(uint*)(bp+opx.opOp1);
459 if (auto inst = Instance.firstInstanceOf(lrint(bp[opx.opOp0]))) {
460 auto v = inst.get(fid, lrint(bp[opx.opOp1+1]), lrint(bp[opx.opOp1+2]));
461 if (v.isUndef) runtimeError(cast(uint)(cptr-VM.code.ptr-1), "trying to read undefined field");
462 bp[opx.opDest] = v;
463 } else {
464 runtimeError(cast(uint)(cptr-VM.code.ptr-1), "trying to read field of invalid instance");
466 break;
468 case Op.fstore: // store value *from* dest into field; op0: obj id; op1: int! reg (field id); can create fields
469 auto fid = *cast(uint*)(bp+opx.opOp1);
470 if (auto inst = Instance.firstInstanceOf(lrint(bp[opx.opOp0]))) {
471 inst.set(bp[opx.opDest], fid);
472 } else {
473 runtimeError(cast(uint)(cptr-VM.code.ptr-1), "trying to set field of invalid instance");
475 break;
476 case Op.i1fstore: // store value *from* dest into indexed reference; op0: obj id; op1: xslots (int! field id, first index)
477 auto fid = *cast(uint*)(bp+opx.opOp1);
478 if (auto inst = Instance.firstInstanceOf(lrint(bp[opx.opOp0]))) {
479 inst.set(bp[opx.opDest], fid, lrint(bp[opx.opOp1+1]));
480 } else {
481 runtimeError(cast(uint)(cptr-VM.code.ptr-1), "trying to set field of invalid instance");
483 break;
484 case Op.i2fstore: // store value *from* dest into indexed reference; op0: obj id; op1: xslots (int! field id, first index, second index)
485 auto fid = *cast(uint*)(bp+opx.opOp1);
486 if (auto inst = Instance.firstInstanceOf(lrint(bp[opx.opOp0]))) {
487 inst.set(bp[opx.opDest], fid, lrint(bp[opx.opOp1+1]), lrint(bp[opx.opOp1+2]));
488 } else {
489 runtimeError(cast(uint)(cptr-VM.code.ptr-1), "trying to set field of invalid instance");
491 break;
493 // `with` is done by copying `self` to another reg, execute the code and restore `self`
494 case Op.siter: // start instance iterator; dest: iterid; op0: objid or instid
495 // this is special: it will skip next instruction if iteration has at least one item
496 // next instruction is always jump, which skips the loop
497 // `self` will be copied to `other`, `other` should be saved
498 uint iid = Instance.newIterator(lrint(bp[opx.opOp0]), lrint(bp[VM.Slot.Other]));
499 *cast(uint*)(bp+opx.opDest) = iid;
500 if (iid) {
501 if (auto nxi = Instance.iteratorNext(iid)) {
502 ++cptr; // skip jump
503 bp[VM.Slot.Other] = bp[VM.Slot.Self];
504 bp[VM.Slot.Self] = nxi;
505 break;
508 // skip loop (execute jump)
509 //cptr = VM.code.ptr+(*cptr).op3Byte;
510 break;
511 case Op.niter: // op0: is iterreg; next instruction is always jump, which continues the loop
512 auto iid = *cast(uint*)(bp+opx.opDest);
513 if (iid) {
514 if (auto nxi = Instance.iteratorNext(iid)) {
515 bp[VM.Slot.Self] = nxi;
516 break; // this will execute jump
519 ++cptr; // skip jump
520 break;
521 case Op.kiter: // kill iterator, should be called to prevent memory leaks
522 auto iid = *cast(uint*)(bp+opx.opDest);
523 if (iid) {
524 // restore `self` and `other`
525 bp[VM.Slot.Self] = bp[VM.Slot.Other];
526 bp[VM.Slot.Other] = Instance.iteratorOldSelf(iid);
528 break;
530 case Op.lirint: // dest = lrint(op0): do lrint() (or another fast float->int conversion)
531 bp[opx.opDest] = lrint(bp[opx.opOp0]);
532 break;
534 default: import std.string : format; assert(0, "invalid opcode: %s".format(opx.opCode));
540 // ////////////////////////////////////////////////////////////////////////// //
541 // create primitive delegate for D delegate/function
542 // D function can include special args like:
543 // Real* -- bp
544 // Real[] -- all args
545 // string, integer, float
546 // no ref args are supported, sorry
547 private VM.PrimDg register(DG) (DG dg) @trusted if (isCallable!DG) {
548 import core.stdc.math : lrint;
549 assert(dg !is null);
550 // build call thunk
551 return delegate (uint pc, Real* bp, ubyte argc) {
552 // prepare arguments
553 Parameters!DG arguments;
554 alias rt = ReturnType!dg;
555 // (Real* bp, ubyte argc)
556 static if (arguments.length == 2 && is(typeof(arguments[1]) == Real*) && is(typeof(arguments[2]) : int)) {
557 static if (is(rt == void)) {
558 cast(void)dg(bp, cast(typeof(arguments[2]))argc);
559 return cast(Real)0;
560 } else {
561 return Value(dg(bp, cast(typeof(arguments[2]))argc));
563 } else {
564 foreach (immutable idx, ref arg; arguments) {
565 // is last argument suitable for `withobj`?
566 static if (is(typeof(arg) == Real[])) {
567 arg = bp[0..VM.Slot.Argument0+argc];
568 } else {
569 static assert(idx < 16, "too many arguments required");
570 static if (is(typeof(arg) == const(char)[]) || is(typeof(arg) == string)) {
571 auto v = bp[VM.Slot.Argument0+idx];
572 if (!v.isString) runtimeError(pc, "invalid argument type");
573 arg = getDynStr(v.getStrId);
574 } else static if (is(typeof(arg) == bool)) {
575 auto v = bp[VM.Slot.Argument0+idx];
576 if (v.isString) arg = (v.getStrId != 0);
577 else if (v.isReal) arg = (lrint(v) != 0);
578 else runtimeError(pc, "invalid argument type");
579 } else static if (is(typeof(arg) : long) || is(typeof(arg) : double)) {
580 auto v = bp[VM.Slot.Argument0+idx];
581 if (!v.isReal) runtimeError(pc, "invalid D argument type");
582 arg = cast(typeof(arg))v;
586 static if (is(rt == void)) {
587 cast(void)dg(arguments);
588 return cast(Real)0;
589 } else {
590 return Value(dg(arguments));