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
;
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
{
60 alias PrimDg
= Real
delegate (uint pc
, Real
* bp
, ubyte argc
);
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
;
74 shared static this () {
76 VM
.scriptPCs
.length
= 1;
77 scriptASTs
.length
= 1;
82 void setGmk (Gmk agmk
) {
83 assert(agmk
!is null);
88 void opIndexAssign(DG
) (DG dg
, string name
) if (isCallable
!DG
) {
89 assert(name
.length
> 0);
91 if (auto sptr
= name
in VM
.scripts
) {
94 sid
= cast(uint)VM
.scriptPCs
.length
;
95 if (sid
> 32767) assert(0, "too many scripts");
96 assert(scriptASTs
.length
== sid
);
100 scriptNum2Name
[sid
] = name
;
101 VM
.scripts
[name
] = sid
;
103 auto pnum
= cast(uint)VM
.prims
.length
;
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);
114 if (stack
.length
< 65536) stack
.length
= 65536;
115 curframe
= &frames
[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)[])) {
123 } else static if (is(typeof(a
) : Real
)) {
124 stack
[VM
.Slot
.Argument0
+idx
] = cast(Real
)a
;
126 static assert(0, "invalid argument type");
129 //{ import std.stdio; writeln(VM.scriptPCs[sid]); }
130 return doExec(VM
.scriptPCs
[sid
]);
135 // ////////////////////////////////////////////////////////////////////////// //
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) {
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
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"~
175 "if (!o0.isReal || !o1.isReal) runtimeError(cast(uint)(cptr-VM.code.ptr-1), `invalid type`);\n"~
176 "bp[dest] = o0"~op
~"o1;\n"~
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"~
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"~
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"~
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"~
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"~
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"~
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
;
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;
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
));
244 switch (opx
.opCode
) {
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
);
256 case Op
.lnot
: // lognot
257 auto dest
= opx
.opDest
;
258 auto o0
= bp
[opx
.opOp0
];
261 auto s0
= getDynStr(o0
.getStrId
);
262 bp
[dest
] = (s0
.length ?
0 : 1);
264 bp
[dest
] = (lrint(o0
) ?
0 : 1);
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");
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
)));
281 auto dest
= opx
.opDest
;
282 auto o0
= bp
[opx
.opOp0
];
283 auto o1
= bp
[opx
.opOp1
];
284 assert(!o0
.isUndef
&& !o1
.isUndef
);
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
);
290 if (s0
.length
== 0) {
292 } else if (s1
.length
== 0) {
295 bp
[dest
] = buildStrId(newDynStr(s0
~s1
));
299 if (!o1
.isReal
) runtimeError(cast(uint)(cptr
-VM
.code
.ptr
-1), "invalid type");
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
];
334 case Op
.ilit
: // dest becomes ilit val (val: short) -- load small integer literal
336 auto dest
= opx
.opDest
;
337 bp
[dest
] = opx
.opILit
;
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
;
344 case Op
.jump
: // addr: 3 bytes
345 cptr
= VM
.code
.ptr
+opx
.op3Byte
;
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
;
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
;
354 case Op
.call: // dest is result; op0: call frame (see below); op1: number of args
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) {
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
);
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
, "'");
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
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
;
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;
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
]); }
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
);
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
); }
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
;
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");
445 runtimeError(cast(uint)(cptr
-VM
.code
.ptr
-1), "trying to read field of invalid instance");
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");
455 runtimeError(cast(uint)(cptr
-VM
.code
.ptr
-1), "trying to read field of invalid instance");
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");
465 runtimeError(cast(uint)(cptr
-VM
.code
.ptr
-1), "trying to read field of invalid instance");
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
);
474 runtimeError(cast(uint)(cptr
-VM
.code
.ptr
-1), "trying to set field of invalid instance");
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]));
482 runtimeError(cast(uint)(cptr
-VM
.code
.ptr
-1), "trying to set field of invalid instance");
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]));
490 runtimeError(cast(uint)(cptr
-VM
.code
.ptr
-1), "trying to set field of invalid instance");
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
;
502 if (auto nxi
= Instance
.iteratorNext(iid
)) {
504 bp
[VM
.Slot
.Other
] = bp
[VM
.Slot
.Self
];
505 bp
[VM
.Slot
.Self
] = nxi
;
509 // skip loop (execute jump)
510 //cptr = VM.code.ptr+(*cptr).op3Byte;
512 case Op
.niter
: // op0: is iterreg; next instruction is always jump, which continues the loop
513 auto iid
= *cast(uint*)(bp
+opx
.opDest
);
515 if (auto nxi
= Instance
.iteratorNext(iid
)) {
516 bp
[VM
.Slot
.Self
] = nxi
;
517 break; // this will execute jump
522 case Op
.kiter
: // kill iterator, should be called to prevent memory leaks
523 auto iid
= *cast(uint*)(bp
+opx
.opDest
);
525 // restore `self` and `other`
526 bp
[VM
.Slot
.Self
] = bp
[VM
.Slot
.Other
];
527 bp
[VM
.Slot
.Other
] = Instance
.iteratorOldSelf(iid
);
531 case Op
.lirint
: // dest = lrint(op0): do lrint() (or another fast float->int conversion)
532 bp
[opx
.opDest
] = lrint(bp
[opx
.opOp0
]);
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:
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
;
552 return delegate (uint pc
, Real
* bp
, ubyte argc
) {
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
);
562 return Value(dg(bp
, cast(typeof(arguments
[2]))argc
));
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
];
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
);
591 return Value(dg(arguments
));