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
;
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
{
59 alias PrimDg
= Real
delegate (uint pc
, Real
* bp
, ubyte argc
);
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
;
73 shared static this () {
75 VM
.scriptPCs
.length
= 1;
76 scriptASTs
.length
= 1;
81 void setGmk (Gmk agmk
) {
82 assert(agmk
!is null);
87 void opIndexAssign(DG
) (DG dg
, string name
) if (isCallable
!DG
) {
88 assert(name
.length
> 0);
90 if (auto sptr
= name
in VM
.scripts
) {
93 sid
= cast(uint)VM
.scriptPCs
.length
;
94 if (sid
> 32767) assert(0, "too many scripts");
95 assert(scriptASTs
.length
== sid
);
99 scriptNum2Name
[sid
] = name
;
100 VM
.scripts
[name
] = sid
;
102 auto pnum
= cast(uint)VM
.prims
.length
;
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);
113 if (stack
.length
< 65536) stack
.length
= 65536;
114 curframe
= &frames
[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)[])) {
122 } else static if (is(typeof(a
) : Real
)) {
123 stack
[VM
.Slot
.Argument0
+idx
] = cast(Real
)a
;
125 static assert(0, "invalid argument type");
128 //{ import std.stdio; writeln(VM.scriptPCs[sid]); }
129 return doExec(VM
.scriptPCs
[sid
]);
134 // ////////////////////////////////////////////////////////////////////////// //
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) {
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
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"~
174 "if (!o0.isReal || !o1.isReal) runtimeError(cast(uint)(cptr-VM.code.ptr-1), `invalid type`);\n"~
175 "bp[dest] = o0"~op
~"o1;\n"~
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"~
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"~
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"~
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"~
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"~
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"~
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
;
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;
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
));
243 switch (opx
.opCode
) {
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
);
255 case Op
.lnot
: // lognot
256 auto dest
= opx
.opDest
;
257 auto o0
= bp
[opx
.opOp0
];
260 auto s0
= getDynStr(o0
.getStrId
);
261 bp
[dest
] = (s0
.length ?
0 : 1);
263 bp
[dest
] = (lrint(o0
) ?
0 : 1);
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");
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
)));
280 auto dest
= opx
.opDest
;
281 auto o0
= bp
[opx
.opOp0
];
282 auto o1
= bp
[opx
.opOp1
];
283 assert(!o0
.isUndef
&& !o1
.isUndef
);
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
);
289 if (s0
.length
== 0) {
291 } else if (s1
.length
== 0) {
294 bp
[dest
] = buildStrId(newDynStr(s0
~s1
));
298 if (!o1
.isReal
) runtimeError(cast(uint)(cptr
-VM
.code
.ptr
-1), "invalid type");
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
];
333 case Op
.ilit
: // dest becomes ilit val (val: short) -- load small integer literal
335 auto dest
= opx
.opDest
;
336 bp
[dest
] = opx
.opILit
;
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
;
343 case Op
.jump
: // addr: 3 bytes
344 cptr
= VM
.code
.ptr
+opx
.op3Byte
;
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
;
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
;
353 case Op
.call: // dest is result; op0: call frame (see below); op1: number of args
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) {
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
);
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
, "'");
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
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
;
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;
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
]); }
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
);
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
); }
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
;
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");
444 runtimeError(cast(uint)(cptr
-VM
.code
.ptr
-1), "trying to read field of invalid instance");
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");
454 runtimeError(cast(uint)(cptr
-VM
.code
.ptr
-1), "trying to read field of invalid instance");
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");
464 runtimeError(cast(uint)(cptr
-VM
.code
.ptr
-1), "trying to read field of invalid instance");
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
);
473 runtimeError(cast(uint)(cptr
-VM
.code
.ptr
-1), "trying to set field of invalid instance");
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]));
481 runtimeError(cast(uint)(cptr
-VM
.code
.ptr
-1), "trying to set field of invalid instance");
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]));
489 runtimeError(cast(uint)(cptr
-VM
.code
.ptr
-1), "trying to set field of invalid instance");
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
;
501 if (auto nxi
= Instance
.iteratorNext(iid
)) {
503 bp
[VM
.Slot
.Other
] = bp
[VM
.Slot
.Self
];
504 bp
[VM
.Slot
.Self
] = nxi
;
508 // skip loop (execute jump)
509 //cptr = VM.code.ptr+(*cptr).op3Byte;
511 case Op
.niter
: // op0: is iterreg; next instruction is always jump, which continues the loop
512 auto iid
= *cast(uint*)(bp
+opx
.opDest
);
514 if (auto nxi
= Instance
.iteratorNext(iid
)) {
515 bp
[VM
.Slot
.Self
] = nxi
;
516 break; // this will execute jump
521 case Op
.kiter
: // kill iterator, should be called to prevent memory leaks
522 auto iid
= *cast(uint*)(bp
+opx
.opDest
);
524 // restore `self` and `other`
525 bp
[VM
.Slot
.Self
] = bp
[VM
.Slot
.Other
];
526 bp
[VM
.Slot
.Other
] = Instance
.iteratorOldSelf(iid
);
530 case Op
.lirint
: // dest = lrint(op0): do lrint() (or another fast float->int conversion)
531 bp
[opx
.opDest
] = lrint(bp
[opx
.opOp0
]);
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:
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
;
551 return delegate (uint pc
, Real
* bp
, ubyte argc
) {
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
);
561 return Value(dg(bp
, cast(typeof(arguments
[2]))argc
));
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
];
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
);
590 return Value(dg(arguments
));