1 /**
2 This module implements the VirtualMachine that executes compiled bytecode.
3 
4 ────────────────────────────────────────────────────────────────────────────────
5 
6 Copyright (C) 2021 pillager86.rf.gd
7 
8 This program is free software: you can redistribute it and/or modify it under 
9 the terms of the GNU General Public License as published by the Free Software 
10 Foundation, either version 3 of the License, or (at your option) any later 
11 version.
12 
13 This program is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15 PARTICULAR PURPOSE.  See the GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License along with 
18 this program.  If not, see <https://www.gnu.org/licenses/>.
19 */
20 module mildew.vm.virtualmachine;
21 
22 import core.sync.semaphore;
23 import core.thread.fiber;
24 import std.concurrency;
25 import std.container: SList;
26 import std.conv: to;
27 import std.parallelism;
28 import std.stdio;
29 import std..string;
30 import std.typecons;
31 
32 import mildew.environment;
33 import mildew.exceptions;
34 import mildew.stdlib.buffers;
35 import mildew.stdlib.generator;
36 import mildew.stdlib.map;
37 import mildew.stdlib.regexp;
38 import mildew.types;
39 import mildew.util.encode;
40 import mildew.util.regex: extract;
41 import mildew.util.stack;
42 import mildew.vm.consttable;
43 import mildew.vm.debuginfo;
44 import mildew.vm.program;
45 import mildew.vm.fiber;
46 
47 /// 8-bit opcodes
48 enum OpCode : ubyte 
49 {
50     NOP, // nop() -> ip += 1
51     CONST, // const(uint) : load a const by index from the const table
52     CONST_0, // const0() : load long(0) on to stack
53     CONST_1, // const1() : push 1 to the stack
54     CONST_N1, // constN1() : push -1 to the stack
55     PUSH, // push(int) : push a stack value, can start at -1
56     POP, // pop() : remove exactly one value from stack
57     POPN, // pop(uint) : remove n values from stack
58     SET, // set(uint) : set index of stack to value at top without popping stack
59     STACK, // stack(uint) : push n number of undefines to stack
60     STACK_1, // stack1() : push one undefined to stack
61     ARRAY, // array(uint) : pops n items to create array and pushes to top
62     OBJECT, // array(uint) : create an object from key-value pairs starting with stack[-n] so n must be even
63     CLASS, // class(ubyte,ubyte,ubyte,ubyte) : first arg is how many normal method pairs, second arg is how many getters,
64             // third arg is how many setter pairs, 4th arg is static method pairs, and at stack[-1] base class.
65             // the finalized class constructor is pushed after all those values are popped. String group before method group
66             // stack[-2] is constructor
67     REGEX, // creates a regex from a string at stack[-1] such as /foo/g
68     ITER, // pushes a function that returns {value:..., done:bool} performed on pop()
69     DEL, // delete member stack[-1]:string from object stack[2]. pops 2
70     NEW, // similar to call(uint) except only func, arg1, arg2, etc.
71     THIS, // pushes local "this" or undefined if not found
72     OPENSCOPE, // openscope() : open an environment scope
73     CLOSESCOPE, // closescope() : close an environment scope
74     DECLVAR, // declvar(uint) : declare pop() to a global described by a const string
75     DECLLET, // decllet(uint) : declare pop() to a local described by a const string
76     DECLCONST, // declconst(uint) : declare pop() as stack[-2] to a local const described by a const
77     GETVAR, // getvar(uint) : push variable described by const table string on to stack
78     SETVAR, // setvar(uint) : store top() in variable described by const table index string leave value on top
79     OBJGET, // objget() : retrieves stack[-2][stack[-1]], popping 2 and pushing 1
80     OBJSET, // objset() : sets stack[-3][stack[-2]] to stack[-1], pops 3 and pushes the value that was set
81     CALL, // call(uint) : stack should be this, func, arg1, arg2, arg3 and arg would be 3
82     JMPFALSE, // jmpfalse(int) : relative jump
83     JMP,  // jmp(int) -> : relative jump
84     SWITCH, // switch(uint) -> arg=abs jmp, stack[-2] jmp table stack[-1] value to test
85     GOTO, // goto(uint, ubyte) : absolute ip. second param is number of scopes to subtract
86     THROW, // throw() : throws pop() as a script runtime exception
87     RETHROW, // rethrow() : rethrow the exception flag, should only be generated with try-finally
88     TRY, // try(uint) : parameter is ip to goto for catch (or sometimes finally if finally only)
89     ENDTRY, // pop unconsumed try-entry from try-entry list
90     LOADEXC, // loads the current exception on to stack, either message or thrown value
91     
92     // special ops
93     CONCAT, // concat(uint) : concat N elements on stack and push resulting string
94 
95     // binary and unary and terniary ops
96     BITNOT, // bitwise not
97     NOT, // not top()
98     NEGATE, // negate top()
99     TYPEOF, // typeof operator
100     INSTANCEOF, // instanceof operator
101     POW, // exponent operation
102     MUL, // multiplication
103     DIV, // division
104     MOD, // modulo
105     ADD, // add() : adds stack[-2,-1], pops 2, push 1
106     SUB, // minus
107     BITLSH, // bit shift left top
108     BITRSH, // bit shift right top
109     BITURSH, // bit shift right unsigned
110     LT, // less than
111     LE, // less than or equal
112     GT, // greater than
113     GE, // greater than or equal
114     EQUALS, // equals
115     NEQUALS, // !equals
116     STREQUALS, // strict equals (===)
117     NSTREQUALS, // not strict equals (!==)
118     BITAND, // bitwise and
119     BITOR, // bitwise or
120     BITXOR, // bitwise xor
121 
122     RETURN, // return from a function, should leave exactly one value on stack
123     HALT, // completely stop the vm
124 }
125 
126 alias OpCodeFunction = size_t function(VirtualMachine, const ubyte[] chunk, size_t ip);
127 
128 private ScriptAny getLocalThis(Environment env)
129 {
130     bool _; // @suppress(dscanner.suspicious.unmodified)
131     auto thisPtr = env.lookupVariableOrConst("this", _);
132     if(thisPtr == null)
133         return ScriptAny.UNDEFINED;
134     else
135         return *thisPtr;
136 }
137 
138 /**
139  * Attempts to recover from or throw a ScriptRuntimeException. Returns an instruction pointer to the nearest
140  * catch if on the current stack frame, and failing that, throws the exception.
141  * Params:
142  *  message = The message of the exception
143  *  vm = The VirtualMachine associated with the exception
144  *  chunk = The bytecode currently being executed
145  *  ip = The current instruction pointer
146  *  thrownValue = (Optional) If a ScriptAny is being thrown, this is the value
147  *  rethrow = (Optional) If the exception is being rethrown, the value should be vm._exc
148  * Returns:
149  *  The instruction pointer pointing to the nearest catch block. If none exists, the exception is thrown instead
150  *  of returning.
151  */
152 private size_t throwRuntimeError(in string message, VirtualMachine vm, const ubyte[] chunk, 
153                             size_t ip, ScriptAny thrownValue = ScriptAny.UNDEFINED, 
154                             ScriptRuntimeException rethrow = null)
155 {
156     if(rethrow)
157         vm._exc = rethrow;
158     else
159         vm._exc = new ScriptRuntimeException(message);
160     
161     if(thrownValue != ScriptAny.UNDEFINED)
162         vm._exc.thrownValue = thrownValue;
163     // unwind stack starting with current
164     if(chunk && vm._latestDebugMap && chunk in vm._latestDebugMap)
165     {
166         // TODO fix error reporting
167         immutable lineNum = vm._latestDebugMap[chunk].getLineNumber(ip);
168         vm._exc.scriptTraceback ~= tuple(lineNum, vm._latestDebugMap[chunk].getSourceLine(lineNum));
169     }
170     // consume latest try-data entry if available
171     if(vm._tryData.length > 0)
172     {
173         immutable tryData = vm._tryData[$-1];
174         vm._tryData = vm._tryData[0..$-1];
175         immutable depthToReduce = vm._environment.depth - tryData.depth;
176         for(int i = 0; i < depthToReduce; ++i)
177             vm._environment = vm._environment.parent;
178         vm._stack.size = tryData.stackSize;
179         return tryData.catchGoto;
180     }
181     // Generators will set parent VM exception flag. This is checked in opCall
182     throw vm._exc;
183 }
184 
185 /// Similar to the above function except directly throws because there are no try-data blocks in native code
186 private void throwNativeRuntimeError(string reason, VirtualMachine vm, ScriptAny thrownValue = ScriptAny.UNDEFINED)
187 {
188     vm._exc = new ScriptRuntimeException(reason);
189     if(thrownValue != ScriptAny.UNDEFINED)
190         vm._exc.thrownValue = thrownValue;
191     throw vm._exc;
192 }
193 
194 private string opCodeToString(const OpCode op)
195 {
196     return op.to!string().toLower();
197 }
198 
199 pragma(inline, true)
200 private size_t opNop(VirtualMachine vm, const ubyte[] chunk, size_t ip)
201 {
202     return ip + 1;
203 }
204 
205 pragma(inline, true)
206 private size_t opConst(VirtualMachine vm, const ubyte[] chunk, size_t ip)
207 {
208     immutable constID = decode!uint(chunk[ip + 1..$]);
209     auto value = vm._latestConstTable.get(constID);
210     if(value.type == ScriptAny.Type.FUNCTION)
211         value = value.toValue!ScriptFunction().copyCompiled(vm._environment);
212     vm._stack.push(value);
213     return ip + 1 + uint.sizeof;
214 }
215 
216 pragma(inline, true)
217 private size_t opConst0(VirtualMachine vm, const ubyte[] chunk, size_t ip)
218 {
219     vm._stack.push(ScriptAny(0));
220     return ip + 1;
221 }
222 
223 pragma(inline, true)
224 private size_t opConst1(VirtualMachine vm, const ubyte[] chunk, size_t ip)
225 {
226     vm._stack.push(ScriptAny(1));
227     return ip + 1;
228 }
229 
230 pragma(inline, true)
231 private size_t opPush(VirtualMachine vm, const ubyte[] chunk, size_t ip)
232 {
233     immutable index = decode!int(chunk[ip + 1..$]);
234     if(index < 0)
235         vm._stack.push(vm._stack.array[$ + index]);
236     else
237         vm._stack.push(vm._stack.array[index]);
238     return ip + 1 + int.sizeof;
239 }
240 
241 pragma(inline, true)
242 private size_t opPop(VirtualMachine vm, const ubyte[] chunk, size_t ip)
243 {
244     vm._lastValuePopped = vm._stack.pop();
245     return ip + 1;
246 }
247 
248 pragma(inline, true)
249 private size_t opPopN(VirtualMachine vm, const ubyte[] chunk, size_t ip)
250 {
251     immutable amount = decode!uint(chunk[ip + 1..$]);
252     vm._stack.pop(amount);
253     return ip + 1 + uint.sizeof;
254 }
255 
256 pragma(inline, true)
257 private size_t opSet(VirtualMachine vm, const ubyte[] chunk, size_t ip)
258 {
259     immutable index = decode!uint(chunk[ip + 1..$]);
260     vm._stack.array[index] = vm._stack.array[$-1];
261     return ip + 1 + uint.sizeof;
262 }
263 
264 pragma(inline, true)
265 private size_t opStack(VirtualMachine vm, const ubyte[] chunk, size_t ip)
266 {
267     immutable n = decode!uint(chunk[ip + 1..$]);
268     ScriptAny[] undefineds = new ScriptAny[n];
269     vm._stack.push(undefineds);
270     return ip + 1 + uint.sizeof;
271 }
272 
273 pragma(inline, true)
274 private size_t opStack1(VirtualMachine vm, const ubyte[] chunk, size_t ip)
275 {
276     vm._stack.push(ScriptAny.UNDEFINED);
277     return ip + 1;
278 }
279 
280 pragma(inline, true)
281 private size_t opArray(VirtualMachine vm, const ubyte[] chunk, size_t ip)
282 {
283     immutable n = decode!uint(chunk[ip + 1..$]);
284     auto arr = vm._stack.pop(n);
285     vm._stack.push(ScriptAny(arr));
286     return ip + 1 + uint.sizeof;
287 }
288 
289 pragma(inline, true)
290 private size_t opObject(VirtualMachine vm, const ubyte[] chunk, size_t ip)
291 {
292     immutable n = decode!uint(chunk[ip + 1..$]) * 2;
293     auto pairList = vm._stack.pop(n);
294     auto obj = new ScriptObject("object", null, null);
295     for(uint i = 0; i < n; i += 2)
296         obj[pairList[i].toString()] = pairList[i+1];
297     vm._stack.push(ScriptAny(obj));
298     return ip + 1 + uint.sizeof;
299 }
300 
301 pragma(inline, true)
302 private size_t opClass(VirtualMachine vm, const ubyte[] chunk, size_t ip)
303 {
304     immutable numMethods = decode!ubyte(chunk[ip + 1..$]);
305     immutable numGetters = decode!ubyte(chunk[ip + 2..$]);
306     immutable numSetters = decode!ubyte(chunk[ip + 3..$]);
307     immutable numStatics = decode!ubyte(chunk[ip + 4..$]);
308     auto baseClass = vm._stack.pop();
309     auto ctor = vm._stack.pop(); // @suppress(dscanner.suspicious.unmodified)
310     auto statics = vm._stack.pop(numStatics);
311     auto staticNames = vm._stack.pop(numStatics);
312     auto setters = vm._stack.pop(numSetters);
313     auto setterNames = vm._stack.pop(numSetters);
314     auto getters = vm._stack.pop(numGetters);
315     auto getterNames = vm._stack.pop(numGetters);
316     auto methods = vm._stack.pop(numMethods);
317     auto methodNames = vm._stack.pop(numMethods);
318 
319     auto constructor = ctor.toValue!ScriptFunction;
320     if(constructor is null)
321         throw new VMException("Malformed class instruction: invalid constructor", ip, OpCode.CLASS);
322 
323     for(auto i = 0; i < numMethods; ++i)
324     {
325         auto method = methods[i].toValue!ScriptFunction; // @suppress(dscanner.suspicious.unmodified)
326         if(method is null)
327             throw new VMException("Malformed class instruction: invalid method", ip, OpCode.CLASS);
328         constructor["prototype"][methodNames[i].toString] = method;
329     }
330 
331     for(auto i = 0; i < numGetters; ++i)
332     {
333         auto getter = getters[i].toValue!ScriptFunction;
334         if(getter is null)
335             throw new VMException("Malformed class instruction: invalid get property", ip, OpCode.CLASS);
336         constructor["prototype"].addGetterProperty(getterNames[i].toString(), getter);
337     }
338 
339     for(auto i = 0; i < numSetters; ++i)
340     {
341         auto setter = setters[i].toValue!ScriptFunction;
342         if(setter is null)
343             throw new VMException("Malformed class instruction: invalid set property", ip, OpCode.CLASS);
344         constructor["prototype"].addSetterProperty(setterNames[i].toString(), setter);
345     }
346 
347     for(auto i = 0; i < numStatics; ++i)
348     {
349         constructor[staticNames[i].toString()] = statics[i];
350     }
351 
352     if(baseClass)
353     {
354         auto baseClassCtor = baseClass.toValue!ScriptFunction;
355         if(baseClassCtor is null)
356             return throwRuntimeError("Invalid base class " ~ baseClass.toString(), vm, chunk, ip);
357         auto ctorPrototype = constructor["prototype"].toValue!ScriptObject;
358         ctorPrototype.prototype = baseClassCtor["prototype"].toValue!ScriptObject;
359         constructor.prototype = baseClassCtor;
360     }
361 
362     // push the resulting modified constructor
363     vm._stack.push(ScriptAny(constructor));
364 
365     return ip + 1 + 4 * ubyte.sizeof;
366 }
367 
368 pragma(inline, true)
369 private size_t opRegex(VirtualMachine vm, const ubyte[] chunk, size_t ip)
370 {
371     auto regexString = vm._stack.pop().toString();
372     auto parts = extract(regexString);
373     auto regexResult = ScriptAny(new ScriptObject("RegExp", getRegExpProto, new ScriptRegExp(parts[0], parts[1])));
374     vm._stack.push(regexResult);
375     return ip + 1;
376 }
377 
378 private template BufferGenerator(A)
379 {
380 
381 }
382 
383 pragma(inline, true)
384 private size_t opIter(VirtualMachine vm, const ubyte[] chunk, size_t ip)
385 {
386     auto objToIterate = vm._stack.pop();
387     // can be a string, array, or object
388     // FUTURE: a Generator returned by a Generator function
389     if(!(objToIterate.isObject))
390         return throwRuntimeError("Cannot iterate over non-object " ~ objToIterate.toString, vm, chunk, ip);
391     if(objToIterate.type == ScriptAny.Type.STRING)
392     {
393         immutable elements = objToIterate.toValue!string;
394         auto generator = new Generator!(Tuple!(size_t,dstring))({
395             size_t indexCounter = 0;
396             foreach(dchar ele ; elements)
397             {
398                 ++indexCounter;
399                 yield(tuple(indexCounter-1,ele.to!dstring));
400             }
401         });
402         vm._stack.push(ScriptAny(new ScriptFunction("next", 
403             delegate ScriptAny(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError){
404                 auto retVal = new ScriptObject("iteration", null, null);
405                 if(generator.empty)
406                 {
407                     retVal.assignField("done", ScriptAny(true));
408                 }
409                 else 
410                 {
411                     auto result = generator.front();
412                     retVal.assignField("key", ScriptAny(result[0]));
413                     retVal.assignField("value", ScriptAny(result[1]));
414                     generator.popFront();
415                 }
416                 return ScriptAny(retVal);
417             }, 
418             false)));
419     }
420     else if(objToIterate.type == ScriptAny.Type.ARRAY)
421     {
422         auto elements = objToIterate.toValue!(ScriptAny[]); // @suppress(dscanner.suspicious.unmodified)
423         auto generator = new Generator!(Tuple!(size_t, ScriptAny))({
424             size_t indexCounter = 0;
425             foreach(item ; elements)
426             {
427                 ++indexCounter;
428                 yield(tuple(indexCounter-1, item));
429             }
430         });
431         vm._stack.push(ScriptAny(new ScriptFunction("next",
432             delegate ScriptAny(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError) {
433                 auto retVal = new ScriptObject("iteration", null, null);
434                 if(generator.empty)
435                 {
436                     retVal.assignField("done", ScriptAny(true));
437                 }
438                 else 
439                 {
440                     auto result = generator.front();
441                     retVal.assignField("key", ScriptAny(result[0]));
442                     retVal.assignField("value", ScriptAny(result[1]));
443                     generator.popFront();
444                 }
445                 return ScriptAny(retVal);
446             })));
447     }
448     else if(objToIterate.isNativeObjectType!AbstractArrayBuffer)
449     {
450         auto aab = objToIterate.toNativeObject!AbstractArrayBuffer; // @suppress(dscanner.suspicious.unmodified)
451         if(!aab.isView)
452             return throwRuntimeError("Cannot iterate over ArrayBuffer, must convert to view", vm, chunk, ip);
453         string PRODUCE_GENERATOR(A)()
454         {
455             import std.format: format;
456             return format(q{
457             {
458                 auto a = cast(%1$s)aab;
459                 auto generator = new Generator!(Tuple!(size_t, ScriptAny))({
460                     size_t indexCounter = 0;
461                     foreach(element ; a.data)
462                     {
463                         yield(tuple(indexCounter, ScriptAny(element)));
464                         ++indexCounter;
465                     }
466                 });
467                 vm._stack.push(ScriptAny(new ScriptFunction("next", 
468                 delegate ScriptAny(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError){
469                     auto retVal = new ScriptObject("iteration", null);
470                     if(generator.empty)
471                     {
472                         retVal.assignField("done", ScriptAny(true));
473                     }
474                     else
475                     {
476                         auto result = generator.front();
477                         retVal.assignField("key", ScriptAny(result[0]));
478                         retVal.assignField("value", ScriptAny(result[1]));
479                         generator.popFront();
480                     }
481                     return ScriptAny(retVal);
482                 })));
483             }
484             }, A.stringof);
485         }
486         final switch(aab.type)
487         {
488         case AbstractArrayBuffer.Type.ARRAY_BUFFER:
489             break; // already handled
490         case AbstractArrayBuffer.Type.INT8_ARRAY:
491             mixin(PRODUCE_GENERATOR!Int8Array);
492             break;
493         case AbstractArrayBuffer.Type.UINT8_ARRAY:
494             mixin(PRODUCE_GENERATOR!Uint8Array);
495             break;
496         case AbstractArrayBuffer.Type.INT16_ARRAY:
497             mixin(PRODUCE_GENERATOR!Int16Array);
498             break;
499         case AbstractArrayBuffer.Type.UINT16_ARRAY:
500             mixin(PRODUCE_GENERATOR!Uint16Array);
501             break;
502         case AbstractArrayBuffer.Type.INT32_ARRAY:
503             mixin(PRODUCE_GENERATOR!Int32Array);
504             break;
505         case AbstractArrayBuffer.Type.UINT32_ARRAY:
506             mixin(PRODUCE_GENERATOR!Uint32Array);
507             break;
508         case AbstractArrayBuffer.Type.FLOAT32_ARRAY:
509             mixin(PRODUCE_GENERATOR!Float32Array);
510             break;
511         case AbstractArrayBuffer.Type.FLOAT64_ARRAY:
512             mixin(PRODUCE_GENERATOR!Float64Array);
513             break;
514         case AbstractArrayBuffer.Type.BIGINT64_ARRAY:
515             mixin(PRODUCE_GENERATOR!BigInt64Array);
516             break;
517         case AbstractArrayBuffer.Type.BIGUINT64_ARRAY:
518             mixin(PRODUCE_GENERATOR!BigUint64Array);
519             break;
520         }
521 
522         
523     }
524     else if(objToIterate.isObject)
525     {
526         if(objToIterate.isNativeObjectType!ScriptGenerator)
527         {
528             auto func = new ScriptFunction("next", &native_Generator_next, false);
529             func.bind(objToIterate);
530             vm._stack.push(ScriptAny(func));
531         }
532         else if(objToIterate.isNativeObjectType!ScriptMap)
533         {
534             auto map = objToIterate.toNativeObject!ScriptMap;
535             auto generator = new Generator!(Tuple!(ScriptAny, ScriptAny))({
536                 foreach(key, value ; map.entries)
537                     yield(tuple(key, value));
538             });
539             vm._stack.push(ScriptAny(new ScriptFunction("next",
540                 delegate ScriptAny(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError) {
541                     auto retVal = new ScriptObject("iteration", null, null);
542                     if(generator.empty)
543                     {
544                         retVal.assignField("done", ScriptAny(true));
545                     }
546                     else
547                     {
548                         auto result = generator.front();
549                         retVal.assignField("key", result[0]);
550                         retVal.assignField("value", result[1]);
551                         generator.popFront();
552                     }
553                     return ScriptAny(retVal);
554                 }
555             )));
556         }
557         else
558         {
559             auto obj = objToIterate.toValue!ScriptObject;
560             auto generator = new Generator!(Tuple!(string, ScriptAny))({
561                 foreach(k, v ; obj.dictionary)
562                     yield(tuple(k,v));
563             });
564             vm._stack.push(ScriptAny(new ScriptFunction("next", 
565                 delegate ScriptAny(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError){
566                     auto retVal = new ScriptObject("iteration", null, null);
567                     if(generator.empty)
568                     {
569                         retVal.assignField("done", ScriptAny(true));
570                     }
571                     else
572                     {
573                         auto result = generator.front();
574                         retVal.assignField("key", ScriptAny(result[0]));
575                         retVal.assignField("value", result[1]);
576                         generator.popFront();
577                     }
578                     return ScriptAny(retVal);
579             })));
580         }
581     }
582     return ip + 1;
583 }
584 
585 pragma(inline, true)
586 private size_t opDel(VirtualMachine vm, const ubyte[] chunk, size_t ip)
587 {
588     auto memberToDelete = vm._stack.pop().toString();
589     auto objToDelete = vm._stack.pop();
590     auto obj = objToDelete.toValue!ScriptObject;
591     if(obj is null)
592         return throwRuntimeError("Cannot delete member of non-object " ~ objToDelete.toString,
593             vm, chunk, ip);
594     obj.dictionary.remove(memberToDelete);
595     return ip + 1;
596 }
597 
598 pragma(inline, true)
599 private size_t opNew(VirtualMachine vm, const ubyte[] chunk, size_t ip)
600 {
601     immutable n = decode!uint(chunk[ip + 1..$]) + 1;
602     auto callInfo = vm._stack.pop(n);
603     auto funcAny = callInfo[0];
604     auto args = callInfo[1..$];
605 
606     if(funcAny.type != ScriptAny.Type.FUNCTION)
607         return throwRuntimeError("Unable to instantiate new object from non-function " ~ funcAny.toString(), 
608             vm, chunk, ip);
609     auto func = funcAny.toValue!ScriptFunction; // @suppress(dscanner.suspicious.unmodified)
610 
611     ScriptAny thisObj = new ScriptObject(func.functionName, func["prototype"].toValue!ScriptObject, null);
612 
613     try 
614     {
615         ScriptAny[string] values;
616         values["__new"] = ScriptAny(true);
617         auto newObject = vm.runFunction(func, thisObj, args, values);
618         vm._stack.push(newObject);
619     }
620     catch(ScriptRuntimeException ex)
621     {
622         return throwRuntimeError(null, vm, chunk, ip, ex.thrownValue, ex);
623     }
624 
625     return ip + 1 + uint.sizeof;
626 }
627 
628 pragma(inline, true)
629 private size_t opThis(VirtualMachine vm, const ubyte[] chunk, size_t ip)
630 {
631     bool _; // @suppress(dscanner.suspicious.unmodified)
632     auto thisPtr = vm._environment.lookupVariableOrConst("this", _);
633     if(thisPtr == null)
634         vm._stack.push(ScriptAny.UNDEFINED);
635     else
636         vm._stack.push(*thisPtr);
637     return ip + 1;
638 }
639 
640 pragma(inline, true)
641 private size_t opOpenScope(VirtualMachine vm, const ubyte[] chunk, size_t ip)
642 {
643     vm._environment = new Environment(vm._environment);
644     // debug writefln("Opening environment from parent %s", vm._environment.parent);
645     // debug writefln("VM{ environment depth=%s", vm._environment.depth);
646     return ip + 1;
647 }
648 
649 pragma(inline, true)
650 private size_t opCloseScope(VirtualMachine vm, const ubyte[] chunk, size_t ip)
651 {
652     vm._environment = vm._environment.parent;
653     // debug writefln("Closing environment: %s", vm._environment);
654     // debug writefln("VM} environment depth=%s", vm._environment.depth);
655     return ip + 1;
656 }
657 
658 pragma(inline, true)
659 private size_t opDeclVar(VirtualMachine vm, const ubyte[] chunk, size_t ip)
660 {
661     auto constID = decode!uint(chunk[ip + 1..$]);
662     auto varName = vm._latestConstTable.get(constID).toString();
663     auto value = vm._stack.pop();
664     immutable ok = vm._globals.declareVariableOrConst(varName, value, false);
665     if(!ok)
666         return throwRuntimeError("Cannot redeclare global " ~ varName, vm, chunk, ip);
667     return ip + 1 + uint.sizeof;
668 }
669 
670 pragma(inline, true)
671 private size_t opDeclLet(VirtualMachine vm, const ubyte[] chunk, size_t ip)
672 {
673     auto constID = decode!uint(chunk[ip + 1..$]);
674     auto varName = vm._latestConstTable.get(constID).toString();
675     auto value = vm._stack.pop();
676     immutable ok = vm._environment.declareVariableOrConst(varName, value, false);
677     if(!ok)
678         return throwRuntimeError("Cannot redeclare local " ~ varName, vm, chunk, ip);
679     return ip + 1 + uint.sizeof;
680 }
681 
682 pragma(inline, true)
683 private size_t opDeclConst(VirtualMachine vm, const ubyte[] chunk, size_t ip)
684 {
685     auto constID = decode!uint(chunk[ip + 1..$]);
686     auto varName = vm._latestConstTable.get(constID).toString();
687     auto value = vm._stack.pop();
688     immutable ok = vm._environment.declareVariableOrConst(varName, value, true);
689     if(!ok)
690         return throwRuntimeError("Cannot redeclare const " ~ varName, vm, chunk, ip);
691     return ip + 1 + uint.sizeof;
692 }
693 
694 pragma(inline, true)
695 private size_t opGetVar(VirtualMachine vm, const ubyte[] chunk, size_t ip)
696 {
697     auto constID = decode!uint(chunk[ip + 1..$]);   
698     auto varName = vm._latestConstTable.get(constID).toString();
699     bool isConst; // @suppress(dscanner.suspicious.unmodified)
700     auto valuePtr = vm._environment.lookupVariableOrConst(varName, isConst);
701     if(valuePtr == null)
702         return throwRuntimeError("Variable lookup failed: " ~ varName, vm, chunk, ip);
703     vm._stack.push(*valuePtr);
704     return ip + 1 + uint.sizeof;
705 }
706 
707 pragma(inline, true)
708 private size_t opSetVar(VirtualMachine vm, const ubyte[] chunk, size_t ip)
709 {
710     auto constID = decode!uint(chunk[ip + 1..$]);
711     auto varName = vm._latestConstTable.get(constID).toString();
712     bool isConst; // @suppress(dscanner.suspicious.unmodified)
713     auto varPtr = vm._environment.lookupVariableOrConst(varName, isConst);
714     if(varPtr == null)
715     {
716         // maybe someday this will be the only way to declare globals
717         return throwRuntimeError("Cannot assign to undefined variable: " ~ varName, vm, chunk, ip);
718     }
719     auto value = vm._stack.peek(); // @suppress(dscanner.suspicious.unmodified)
720     if(value == ScriptAny.UNDEFINED)
721         vm._environment.unsetVariable(varName);
722     else
723         *varPtr = value;
724     return ip + 1 + uint.sizeof;
725 }
726 
727 pragma(inline, true)
728 private size_t opObjGet(VirtualMachine vm, const ubyte[] chunk, size_t ip)
729 {
730     import std.utf: UTFException;
731 
732     auto objToAccess = vm._stack.array[$-2];
733     auto field = vm._stack.array[$-1]; // @suppress(dscanner.suspicious.unmodified)
734     vm._stack.pop(2);
735     // TODO handle getters
736     // if field is integer it is array access
737     if(field.isNumber 
738       && (objToAccess.type == ScriptAny.Type.ARRAY || objToAccess.type == ScriptAny.Type.STRING
739         || objToAccess.isNativeObjectType!AbstractArrayBuffer))
740     {
741         auto index = field.toValue!long;
742         if(objToAccess.type == ScriptAny.Type.ARRAY)
743         {
744             auto arr = objToAccess.toValue!(ScriptAny[]);
745             if(index < 0)
746                 index = arr.length + index;
747             if(index < 0 || index >= arr.length)
748                 return throwRuntimeError("Out of bounds array access", vm, chunk, ip);
749             vm._stack.push(arr[index]);
750         }
751         else if(objToAccess.type == ScriptAny.Type.STRING)
752         {
753             auto str = objToAccess.toValue!(ScriptString)().toString();
754             if(index < 0)
755                 index = str.length + index;
756             if(index < 0 || index >= str.length)
757                 return throwRuntimeError("Out of bounds string access", vm, chunk, ip);
758             try 
759             {
760                 vm._stack.push(ScriptAny([str[index]]));
761             }
762             catch(UTFException)
763             {
764                 vm._stack.push(ScriptAny.UNDEFINED);
765             }
766         }
767         else
768         {
769             auto aab = objToAccess.toNativeObject!AbstractArrayBuffer;
770             immutable realIndex = aab.getIndex(index);
771             if(!aab.isView)
772                 return throwRuntimeError("ArrayBuffer cannot be indexed directly, convert to view",
773                     vm, chunk, ip);
774             if(realIndex == -1)
775                 return throwRuntimeError("Buffer out of bounds array index", vm, chunk, ip);
776             final switch(aab.type)
777             {
778             case AbstractArrayBuffer.Type.ARRAY_BUFFER:
779                 break; // already handled
780             case AbstractArrayBuffer.Type.INT8_ARRAY:
781                 vm._stack.push(ScriptAny((cast(Int8Array)aab).data[realIndex]));
782                 break;
783             case AbstractArrayBuffer.Type.UINT8_ARRAY:
784                 vm._stack.push(ScriptAny((cast(Uint8Array)aab).data[realIndex]));
785                 break;
786             case AbstractArrayBuffer.Type.INT16_ARRAY:
787                 vm._stack.push(ScriptAny((cast(Int16Array)aab).data[realIndex]));
788                 break;
789             case AbstractArrayBuffer.Type.UINT16_ARRAY:
790                 vm._stack.push(ScriptAny((cast(Uint16Array)aab).data[realIndex]));
791                 break;
792             case AbstractArrayBuffer.Type.INT32_ARRAY:
793                 vm._stack.push(ScriptAny((cast(Int32Array)aab).data[realIndex]));
794                 break;
795             case AbstractArrayBuffer.Type.UINT32_ARRAY:
796                 vm._stack.push(ScriptAny((cast(Uint32Array)aab).data[realIndex]));
797                 break;
798             case AbstractArrayBuffer.Type.FLOAT32_ARRAY:
799                 vm._stack.push(ScriptAny((cast(Float32Array)aab).data[realIndex]));
800                 break;
801             case AbstractArrayBuffer.Type.FLOAT64_ARRAY:
802                 vm._stack.push(ScriptAny((cast(Float64Array)aab).data[realIndex]));
803                 break;
804             case AbstractArrayBuffer.Type.BIGINT64_ARRAY:
805                 vm._stack.push(ScriptAny((cast(BigInt64Array)aab).data[realIndex]));
806                 break;
807             case AbstractArrayBuffer.Type.BIGUINT64_ARRAY:
808                 vm._stack.push(ScriptAny((cast(BigUint64Array)aab).data[realIndex]));
809                 break;
810             }
811         }
812     }
813     else // else object field or property access
814     {
815         auto index = field.toString();
816         if(objToAccess.isObject)
817         {
818             auto obj = objToAccess.toValue!ScriptObject; // @suppress(dscanner.suspicious.unmodified)
819             auto getter = obj.findGetter(index);
820             if(getter)
821             {
822                 // this might be a super property call
823                 auto thisObj = getLocalThis(vm._environment);
824                 ScriptAny retVal;
825                 try 
826                 {
827                     if(ScriptFunction.isInstanceOf(thisObj.toValue!ScriptObject, 
828                     objToAccess["constructor"].toValue!ScriptFunction))
829                     {
830                         retVal = vm.runFunction(getter, thisObj, []);
831                     }
832                     else
833                     {
834                         retVal = vm.runFunction(getter, objToAccess, []);
835                     }
836                     vm._stack.push(retVal);
837                 }
838                 catch(ScriptRuntimeException ex)
839                 {
840                     return throwRuntimeError(null, vm, chunk, ip, ex.thrownValue, ex);
841                 }
842             }
843             else
844                 vm._stack.push(objToAccess[index]);
845         }
846         else
847             return throwRuntimeError("Unable to access member " ~ index ~ " of non-object " ~ objToAccess.toString(),
848                     vm, chunk, ip);
849     }
850     return ip + 1;
851 }
852 
853 pragma(inline, true)
854 private size_t opObjSet(VirtualMachine vm, const ubyte[] chunk, size_t ip)
855 {
856     auto objToAccess = vm._stack.array[$-3];
857     auto fieldToAssign = vm._stack.array[$-2]; // @suppress(dscanner.suspicious.unmodified)
858     auto value = vm._stack.array[$-1];
859     vm._stack.pop(3);
860     if(fieldToAssign.isNumber 
861       && (objToAccess.type == ScriptAny.Type.ARRAY || objToAccess.type == ScriptAny.Type.STRING
862         || objToAccess.isNativeObjectType!AbstractArrayBuffer))
863     {
864         auto index = fieldToAssign.toValue!long;
865         if(objToAccess.type == ScriptAny.Type.ARRAY)
866         {
867             auto arr = objToAccess.toValue!(ScriptAny[]);
868             if(index < 0)
869                 index = arr.length + index;
870             if(index < 0 || index >= arr.length)
871                 return throwRuntimeError("Out of bounds array assignment", vm, chunk, ip);
872             arr[index] = value;
873             vm._stack.push(value);
874         }
875         else if(objToAccess.type == ScriptAny.Type.STRING)
876         {
877             return throwRuntimeError("Cannot assign index to strings", vm, chunk, ip);
878         }
879         else 
880         {
881             auto aab = objToAccess.toNativeObject!AbstractArrayBuffer;
882             index = aab.getIndex(index);
883             if(!aab.isView)
884                 return throwRuntimeError("ArrayBuffer must be converted to view", vm, chunk, ip);
885                     else
886             if(index == -1)
887                 return throwRuntimeError("Buffer out of bounds array index", vm, chunk, ip);
888             final switch(aab.type)
889             {
890             case AbstractArrayBuffer.Type.ARRAY_BUFFER:
891                 break; // already handled
892             case AbstractArrayBuffer.Type.INT8_ARRAY:
893                 vm._stack.push(ScriptAny((cast(Int8Array)aab).data[index] = value.toValue!byte));
894                 break;
895             case AbstractArrayBuffer.Type.UINT8_ARRAY:
896                 vm._stack.push(ScriptAny((cast(Uint8Array)aab).data[index] = value.toValue!ubyte));
897                 break;
898             case AbstractArrayBuffer.Type.INT16_ARRAY:
899                 vm._stack.push(ScriptAny((cast(Int16Array)aab).data[index] = value.toValue!short));
900                 break;
901             case AbstractArrayBuffer.Type.UINT16_ARRAY:
902                 vm._stack.push(ScriptAny((cast(Uint16Array)aab).data[index] = value.toValue!ushort));
903                 break;
904             case AbstractArrayBuffer.Type.INT32_ARRAY:
905                 vm._stack.push(ScriptAny((cast(Int32Array)aab).data[index] = value.toValue!int));
906                 break;
907             case AbstractArrayBuffer.Type.UINT32_ARRAY:
908                 vm._stack.push(ScriptAny((cast(Uint32Array)aab).data[index] = value.toValue!uint));
909                 break;
910             case AbstractArrayBuffer.Type.FLOAT32_ARRAY:
911                 vm._stack.push(ScriptAny((cast(Float32Array)aab).data[index] = value.toValue!float));
912                 break;
913             case AbstractArrayBuffer.Type.FLOAT64_ARRAY:
914                 vm._stack.push(ScriptAny((cast(Float64Array)aab).data[index] = value.toValue!double));
915                 break;
916             case AbstractArrayBuffer.Type.BIGINT64_ARRAY:
917                 vm._stack.push(ScriptAny((cast(BigInt64Array)aab).data[index] = value.toValue!long));
918                 break;
919             case AbstractArrayBuffer.Type.BIGUINT64_ARRAY:
920                 vm._stack.push(ScriptAny((cast(BigUint64Array)aab).data[index] = value.toValue!ulong));
921                 break;
922             }
923         }
924     }
925     else
926     {
927         auto index = fieldToAssign.toValue!string;
928         if(!objToAccess.isObject)
929             return throwRuntimeError("Unable to assign member " ~ index ~ " of non-object " 
930                 ~ objToAccess.toString(), vm, chunk, ip);
931         auto obj = objToAccess.toValue!ScriptObject; // @suppress(dscanner.suspicious.unmodified)
932         auto setter = obj.findSetter(index);
933         if(setter)
934         {
935             auto thisObj = getLocalThis(vm._environment);
936             immutable isSuperProp = ScriptFunction.isInstanceOf(thisObj.toValue!ScriptObject, 
937                     objToAccess["constructor"].toValue!ScriptFunction);
938             try 
939             {
940                 if(isSuperProp)
941                     vm.runFunction(setter, thisObj, [value]);
942                 else
943                     vm.runFunction(setter, objToAccess, [value]);
944                 // if getter push that or else undefined
945                 auto getter = obj.findGetter(index);
946                 if(getter)
947                 {
948                     if(isSuperProp)
949                         vm._stack.push(vm.runFunction(getter, thisObj, []));
950                     else
951                         vm._stack.push(vm.runFunction(getter, objToAccess, []));
952                 }
953                 else
954                 {
955                     vm._stack.push(ScriptAny.UNDEFINED);
956                 }
957             }
958             catch(ScriptRuntimeException ex)
959             {
960                 return throwRuntimeError(null, vm, chunk, ip, ex.thrownValue, ex);
961             }
962         }
963         else
964         {
965             if(obj.hasGetter(index))
966                 return throwRuntimeError("Object " ~ obj.toString() ~ " has getter for property "
967                     ~ index ~ " but no setter.", vm, chunk, ip);
968             objToAccess[index] = value;
969             vm._stack.push(value);
970         }
971     }
972     return ip + 1;
973 }
974 
975 pragma(inline, true)
976 private size_t opCall(VirtualMachine vm, const ubyte[] chunk, size_t ip)
977 {
978     immutable n = decode!uint(chunk[ip + 1..$]) + 2;
979     if(vm._stack.size < n)
980     {
981         vm.printStack();
982         throw new VMException("opCall failure, stack < " ~ n.to!string, ip, OpCode.CALL);
983     }
984     auto callInfo = vm._stack.pop(n);
985     auto thisObj = callInfo[0]; // @suppress(dscanner.suspicious.unmodified)
986     auto funcAny = callInfo[1];
987     auto args = callInfo[2..$];
988     if(funcAny.type != ScriptAny.Type.FUNCTION)
989         return throwRuntimeError("Unable to call non-function " ~ funcAny.toString(), vm, chunk, ip);
990     auto func = funcAny.toValue!ScriptFunction; // @suppress(dscanner.suspicious.unmodified)
991     if(func.isGenerator)
992     {
993         ScriptObject newGen = new ScriptObject("Generator", getGeneratorPrototype,
994             new ScriptGenerator(vm._environment, func, args, thisObj));
995         vm._stack.push(ScriptAny(newGen));
996         return ip + 1 + uint.sizeof;
997     }
998 
999     try 
1000     {
1001         vm._stack.push(vm.runFunction(func, thisObj, args, null));
1002         if(vm._exc) // flag could be set by Generator
1003             return throwRuntimeError(null, vm, chunk, ip, vm._exc.thrownValue, vm._exc);
1004     }
1005     catch(ScriptRuntimeException ex)
1006     {
1007         return throwRuntimeError(null, vm, chunk, ip, ex.thrownValue, ex);
1008     }
1009 
1010     return ip + 1 + uint.sizeof;
1011 }
1012 
1013 pragma(inline, true)
1014 private size_t opJmpFalse(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1015 {
1016     immutable jmpAmount = decode!int(chunk[ip + 1..$]);
1017     immutable shouldJump = vm._stack.pop();
1018     if(!shouldJump)
1019         return ip + jmpAmount;
1020     else
1021         return ip + 1 + int.sizeof;
1022 }
1023 
1024 pragma(inline, true)
1025 private size_t opJmp(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1026 {
1027     immutable jmpAmount = decode!int(chunk[ip + 1..$]);
1028     return ip + jmpAmount;
1029 }
1030 
1031 pragma(inline, true)
1032 private size_t opSwitch(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1033 {
1034     immutable relAbsJmp = decode!uint(chunk[ip + 1..$]);
1035     auto valueToTest = vm._stack.pop();
1036     auto jumpTableArray = vm._stack.pop();
1037     // build the jump table out of the entries
1038     if(jumpTableArray.type != ScriptAny.Type.ARRAY)
1039         throw new VMException("Invalid jump table", ip, OpCode.SWITCH);
1040     int[ScriptAny] jmpTable;
1041     foreach(entry ; jumpTableArray.toValue!(ScriptAny[]))
1042     {
1043         if(entry.type != ScriptAny.Type.ARRAY)
1044             throw new VMException("Invalid jump table entry", ip, OpCode.SWITCH);
1045         auto entryArray = entry.toValue!(ScriptAny[]);
1046         if(entryArray.length < 2)
1047             throw new VMException("Invalid jump table entry size", ip, OpCode.SWITCH);
1048         jmpTable[entryArray[0]] = entryArray[1].toValue!int;
1049     }
1050     if(valueToTest in jmpTable)
1051         return jmpTable[valueToTest];
1052     else
1053         return relAbsJmp;
1054 }
1055 
1056 pragma(inline, true)
1057 private size_t opGoto(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1058 {
1059     immutable address = decode!uint(chunk[ip + 1..$]);
1060     immutable depth = decode!ubyte(chunk[ip+1+uint.sizeof..$]);
1061     for(ubyte i = 0; i < depth; ++i)
1062     {
1063         vm._environment = vm._environment.parent;
1064     }
1065     return address;
1066 }
1067 
1068 pragma(inline, true)
1069 private size_t opThrow(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1070 {
1071     auto valToThrow = vm._stack.pop();
1072     return throwRuntimeError("Uncaught script exception", vm, chunk, ip, valToThrow);
1073 }
1074 
1075 pragma(inline, true)
1076 private size_t opRethrow(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1077 {
1078     if(vm._exc)
1079         return throwRuntimeError(vm._exc.msg, vm, chunk, ip, vm._exc.thrownValue, vm._exc);
1080     return ip + 1;
1081 }
1082 
1083 pragma(inline, true)
1084 private size_t opTry(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1085 {
1086     immutable catchGoto = decode!uint(chunk[ip + 1..$]);
1087     immutable depth = cast(int)vm._environment.depth();
1088     vm._tryData ~= VirtualMachine.TryData(depth, vm._stack.size, catchGoto);
1089     return ip + 1 + uint.sizeof;
1090 }
1091 
1092 pragma(inline, true)
1093 private size_t opEndTry(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1094 {
1095     vm._tryData = vm._tryData[0..$-1];
1096     return ip + 1;
1097 }
1098 
1099 pragma(inline, true)
1100 private size_t opLoadExc(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1101 {
1102     if(vm._exc is null)
1103         throw new VMException("An exception was never thrown", ip, OpCode.LOADEXC);
1104     if(vm._exc.thrownValue != ScriptAny.UNDEFINED)
1105         vm._stack.push(vm._exc.thrownValue);
1106     else
1107         vm._stack.push(ScriptAny(vm._exc.msg));
1108     vm._exc = null; // once loaded by a catch block it should be cleared
1109     return ip + 1;
1110 }
1111 
1112 pragma(inline, true)
1113 private size_t opConcat(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1114 {
1115     immutable n = decode!uint(chunk[ip + 1..$]);
1116     string result = "";
1117     auto values = vm._stack.pop(n);
1118     foreach(value ; values)
1119         result ~= value.toString();
1120     vm._stack.push(ScriptAny(result));
1121     return ip + 1 + uint.sizeof;
1122 }
1123 
1124 pragma(inline, true)
1125 private size_t opBitNot(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1126 {
1127     vm._stack.array[$-1] = ~vm._stack.array[$-1];
1128     return ip + 1;
1129 }
1130 
1131 pragma(inline, true)
1132 private size_t opNot(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1133 {
1134     vm._stack.array[$-1] = ScriptAny(!vm._stack.array[$-1]);
1135     return ip + 1;
1136 }
1137 
1138 pragma(inline, true)
1139 private size_t opNegate(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1140 {
1141     vm._stack.array[$-1] = -vm._stack.array[$-1];
1142     return ip + 1;
1143 }
1144 
1145 pragma(inline, true)
1146 private size_t opTypeof(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1147 {
1148     vm._stack.array[$-1] = ScriptAny(vm._stack.array[$-1].typeToString());
1149     return ip + 1;
1150 }
1151 
1152 pragma(inline, true)
1153 private size_t opInstanceOf(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1154 {
1155     auto operands = vm._stack.pop(2);
1156     if(!operands[0].isObject)
1157         vm._stack.push(ScriptAny(false));
1158     else if(operands[1].type != ScriptAny.Type.FUNCTION)
1159         vm._stack.push(ScriptAny(false));
1160     else
1161     {
1162         auto lhsObj = operands[0].toValue!ScriptObject; // @suppress(dscanner.suspicious.unmodified)
1163         auto rhsFunc = operands[1].toValue!ScriptFunction; // @suppress(dscanner.suspicious.unmodified)
1164         auto proto = lhsObj.prototype;
1165         while(proto !is null)
1166         {
1167             if(proto["constructor"].toValue!ScriptFunction is rhsFunc)
1168             {
1169                 vm._stack.push(ScriptAny(true));
1170                 return ip + 1;
1171             }
1172             proto = proto.prototype;
1173         }
1174     }
1175     vm._stack.push(ScriptAny(false));
1176     return ip + 1;
1177 }
1178 
1179 private string DEFINE_BIN_OP(string name, string op)()
1180 {
1181     import std.format: format;
1182     return format(q{
1183 pragma(inline, true)
1184 private size_t %1$s(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1185 {
1186     auto operands = vm._stack.pop(2);
1187     vm._stack.push(operands[0] %2$s operands[1]);
1188     return ip + 1;
1189 }
1190     }, name, op);
1191 }
1192 
1193 mixin(DEFINE_BIN_OP!("opPow", "^^"));
1194 mixin(DEFINE_BIN_OP!("opMul", "*"));
1195 mixin(DEFINE_BIN_OP!("opDiv", "/"));
1196 mixin(DEFINE_BIN_OP!("opMod", "%"));
1197 mixin(DEFINE_BIN_OP!("opAdd", "+"));
1198 mixin(DEFINE_BIN_OP!("opSub", "-"));
1199 mixin(DEFINE_BIN_OP!("opBitRSh", ">>"));
1200 mixin(DEFINE_BIN_OP!("opBitURSh", ">>>"));
1201 mixin(DEFINE_BIN_OP!("opBitLSh", "<<"));
1202 
1203 private string DEFINE_BIN_BOOL_OP(string name, string op)()
1204 {
1205     import std.format: format;
1206     return format(q{
1207 pragma(inline, true)
1208 private size_t %1$s(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1209 {
1210     auto operands = vm._stack.pop(2);
1211     vm._stack.push(ScriptAny(operands[0] %2$s operands[1]));
1212     return ip + 1;
1213 }
1214     }, name, op);
1215 }
1216 
1217 mixin(DEFINE_BIN_BOOL_OP!("opLT", "<"));
1218 mixin(DEFINE_BIN_BOOL_OP!("opLE", "<="));
1219 mixin(DEFINE_BIN_BOOL_OP!("opGT", ">"));
1220 mixin(DEFINE_BIN_BOOL_OP!("opGE", ">="));
1221 mixin(DEFINE_BIN_BOOL_OP!("opEQ", "=="));
1222 mixin(DEFINE_BIN_BOOL_OP!("opNEQ", "!="));
1223 
1224 pragma(inline, true)
1225 private size_t opStrictEquals(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1226 {
1227     auto operands = vm._stack.pop(2);
1228     vm._stack.push(ScriptAny(operands[0].strictEquals(operands[1])));
1229     return ip + 1;
1230 }
1231 
1232 pragma(inline, true)
1233 private size_t opNotStrictEquals(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1234 {
1235     auto operands = vm._stack.pop(2);
1236     vm._stack.push(ScriptAny(!operands[0].strictEquals(operands[1])));
1237     return ip + 1;
1238 }
1239 
1240 
1241 mixin(DEFINE_BIN_OP!("opBitAnd", "&"));
1242 mixin(DEFINE_BIN_OP!("opBitOr", "|"));
1243 mixin(DEFINE_BIN_OP!("opBitXor", "^"));
1244 
1245 pragma(inline, true)
1246 private size_t opReturn(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1247 {
1248     throw new VMException("opReturn should never be called directly", ip, OpCode.RETURN);
1249 }
1250 
1251 pragma(inline, true)
1252 private size_t opHalt(VirtualMachine vm, const ubyte[] chunk, size_t ip)
1253 {
1254     vm._stopped = true;
1255     return ip + 1;
1256 }
1257 
1258 /**
1259  * This class implements a virtual machine that runs Chunks of bytecode. This class is not thread
1260  * safe and a copy() of the VM should be instantiated to run scripts in multiple threads. The
1261  * Environments are shallow copied and not guaranteed to be thread safe either.
1262  */
1263 class VirtualMachine
1264 {
1265     /// ctor
1266     this(Environment globalEnv, bool printDisasm = false, bool printSteps = false)
1267     {
1268         _environment = globalEnv;
1269         _globals = globalEnv;
1270         _printDisassembly = printDisasm;
1271         _printSteps = printSteps;
1272         _ops[] = &opNop;
1273         _ops[OpCode.CONST] = &opConst;
1274         _ops[OpCode.CONST_0] = &opConst0;
1275         _ops[OpCode.CONST_1] = &opConst1;
1276         _ops[OpCode.PUSH] = &opPush;
1277         _ops[OpCode.POP] = &opPop;
1278         _ops[OpCode.POPN] = &opPopN;
1279         _ops[OpCode.SET] = &opSet;
1280         _ops[OpCode.STACK] = &opStack;
1281         _ops[OpCode.STACK_1] = &opStack1;
1282         _ops[OpCode.ARRAY] = &opArray;
1283         _ops[OpCode.OBJECT] = &opObject;
1284         _ops[OpCode.CLASS] = &opClass;
1285         _ops[OpCode.REGEX] = &opRegex;
1286         _ops[OpCode.ITER] = &opIter;
1287         _ops[OpCode.DEL] = &opDel;
1288         _ops[OpCode.NEW] = &opNew;
1289         _ops[OpCode.THIS] = &opThis;
1290         _ops[OpCode.OPENSCOPE] = &opOpenScope;
1291         _ops[OpCode.CLOSESCOPE] = &opCloseScope;
1292         _ops[OpCode.DECLVAR] = &opDeclVar;
1293         _ops[OpCode.DECLLET] = &opDeclLet;
1294         _ops[OpCode.DECLCONST] = &opDeclConst;
1295         _ops[OpCode.GETVAR] = &opGetVar;
1296         _ops[OpCode.SETVAR] = &opSetVar;
1297         _ops[OpCode.OBJGET] = &opObjGet;
1298         _ops[OpCode.OBJSET] = &opObjSet;
1299         _ops[OpCode.CALL] = &opCall;
1300         _ops[OpCode.JMPFALSE] = &opJmpFalse;
1301         _ops[OpCode.JMP] = &opJmp;
1302         _ops[OpCode.SWITCH] = &opSwitch;
1303         _ops[OpCode.GOTO] = &opGoto;
1304         _ops[OpCode.THROW] = &opThrow;
1305         _ops[OpCode.RETHROW] = &opRethrow;
1306         _ops[OpCode.TRY] = &opTry;
1307         _ops[OpCode.ENDTRY] = &opEndTry;
1308         _ops[OpCode.LOADEXC] = &opLoadExc;
1309         _ops[OpCode.CONCAT] = &opConcat;
1310         _ops[OpCode.BITNOT] = &opBitNot;
1311         _ops[OpCode.NOT] = &opNot;
1312         _ops[OpCode.NEGATE] = &opNegate;
1313         _ops[OpCode.TYPEOF] = &opTypeof;
1314         _ops[OpCode.INSTANCEOF] = &opInstanceOf;
1315         _ops[OpCode.POW] = &opPow;
1316         _ops[OpCode.MUL] = &opMul;
1317         _ops[OpCode.DIV] = &opDiv;
1318         _ops[OpCode.MOD] = &opMod;
1319         _ops[OpCode.ADD] = &opAdd;
1320         _ops[OpCode.SUB] = &opSub;
1321         _ops[OpCode.BITRSH] = &opBitRSh;
1322         _ops[OpCode.BITURSH] = &opBitURSh;
1323         _ops[OpCode.BITLSH] = &opBitLSh;
1324         _ops[OpCode.LT] = &opLT;
1325         _ops[OpCode.LE] = &opLE;
1326         _ops[OpCode.GT] = &opGT;
1327         _ops[OpCode.GE] = &opGE;
1328         _ops[OpCode.EQUALS] = &opEQ;
1329         _ops[OpCode.NEQUALS] = &opNEQ;
1330         _ops[OpCode.STREQUALS] = &opStrictEquals;
1331         _ops[OpCode.NSTREQUALS] = &opNotStrictEquals;
1332         _ops[OpCode.BITAND] = &opBitAnd;
1333         _ops[OpCode.BITOR] = &opBitOr;
1334         _ops[OpCode.BITXOR] = &opBitXor;
1335         _ops[OpCode.RETURN] = &opReturn;
1336         _ops[OpCode.HALT] = &opHalt;
1337         _stack.reserve(64); // TODO tweak this number as necessary
1338     }
1339 
1340     /// Sets the exception flag of the VM. This is checked after each opCall for Generators
1341     package(mildew) void setException(ScriptRuntimeException ex)
1342     {
1343         _exc = ex;
1344     }
1345 
1346     /// print a program instruction by instruction, using the const table to indicate values
1347     void printProgram(Program program, bool printConstTable=false)
1348     {
1349         if(printConstTable)
1350         {
1351             writeln("===== CONST TABLE =====");
1352             foreach(index, value ; program.constTable)
1353             {
1354                 writef("#%s: ", index);
1355                 if(value.type == ScriptAny.Type.FUNCTION)
1356                 {
1357                     auto fn = value.toValue!ScriptFunction;
1358                     writeln("<function> " ~ fn.functionName);
1359                     auto funcProgram = new Program(program.constTable, fn);
1360                     printProgram(funcProgram, false);
1361                 }
1362                 else
1363                 {
1364                     write("<" ~ value.typeToString() ~ "> ");
1365                     if(value.toString().length < 100)
1366                         writeln(value.toString());
1367                     else
1368                         writeln();
1369                 }
1370             }
1371         }
1372         if(printConstTable)
1373             writeln("===== DISASSEMBLY =====");
1374         size_t ip = 0;
1375         while(ip < program.mainFunction.compiled.length)
1376         {
1377             auto op = cast(OpCode)program.mainFunction.compiled[ip];
1378             printInstruction(ip, program.mainFunction.compiled);
1379             switch(op)
1380             {
1381             case OpCode.NOP:
1382                 ++ip;
1383                 break;
1384             case OpCode.CONST:
1385                 ip += 1 + uint.sizeof;
1386                 break;
1387             case OpCode.CONST_0:
1388             case OpCode.CONST_1:
1389                 ++ip;
1390                 break;
1391             case OpCode.PUSH:
1392                 ip += 1 + int.sizeof;
1393                 break;
1394             case OpCode.POP:
1395                 ++ip;
1396                 break;
1397             case OpCode.POPN:
1398                 ip += 1 + uint.sizeof;
1399                 break;
1400             case OpCode.SET: 
1401                 ip += 1 + uint.sizeof;
1402                 break;
1403             case OpCode.STACK:
1404                 ip += 1 + uint.sizeof;
1405                 break;
1406             case OpCode.STACK_1:
1407                 ++ip;
1408                 break;
1409             case OpCode.ARRAY:
1410                 ip += 1 + uint.sizeof;
1411                 break;
1412             case OpCode.OBJECT:
1413                 ip += 1 + uint.sizeof;
1414                 break;
1415             case OpCode.CLASS:
1416                 ip += 1 + 4 * ubyte.sizeof;
1417                 break;
1418             case OpCode.ITER:
1419             case OpCode.DEL:
1420                 ++ip;
1421                 break;
1422             case OpCode.NEW:
1423                 ip += 1 + uint.sizeof;
1424                 break;
1425             case OpCode.THIS:
1426                 ++ip;
1427                 break;
1428             case OpCode.OPENSCOPE:
1429                 ++ip;
1430                 break;
1431             case OpCode.CLOSESCOPE:
1432                 ++ip;
1433                 break;
1434             case OpCode.DECLVAR:
1435             case OpCode.DECLLET:
1436             case OpCode.DECLCONST:
1437                 ip += 1 + uint.sizeof;
1438                 break;
1439             case OpCode.GETVAR:
1440             case OpCode.SETVAR:
1441                 ip += 1 + uint.sizeof;
1442                 break;
1443             case OpCode.OBJGET:
1444             case OpCode.OBJSET:
1445                 ++ip;
1446                 break;
1447             case OpCode.CALL:
1448                 ip += 1 + uint.sizeof;
1449                 break;
1450             case OpCode.JMPFALSE:
1451             case OpCode.JMP:
1452                 ip += 1 + int.sizeof;
1453                 break;
1454             case OpCode.SWITCH:
1455                 ip += 1 + uint.sizeof;
1456                 break;
1457             case OpCode.GOTO:
1458                 ip += 1 + uint.sizeof + ubyte.sizeof;
1459                 break;
1460             case OpCode.THROW:
1461             case OpCode.RETHROW:
1462                 ++ip;
1463                 break;
1464             case OpCode.TRY:
1465                 ip += 1 + uint.sizeof;
1466                 break;
1467             case OpCode.ENDTRY:
1468             case OpCode.LOADEXC:
1469                 ++ip;
1470                 break;
1471             case OpCode.CONCAT:
1472                 ip += 1 + uint.sizeof;
1473                 break;
1474             case OpCode.BITNOT:
1475             case OpCode.NOT:
1476             case OpCode.NEGATE:
1477             case OpCode.TYPEOF:
1478             case OpCode.INSTANCEOF:
1479             case OpCode.POW:
1480             case OpCode.MUL:
1481             case OpCode.DIV:
1482             case OpCode.MOD:
1483             case OpCode.ADD:
1484             case OpCode.SUB:
1485             case OpCode.LT:
1486             case OpCode.LE:
1487             case OpCode.GT:
1488             case OpCode.GE:
1489             case OpCode.EQUALS:
1490             case OpCode.NEQUALS:
1491             case OpCode.STREQUALS:
1492             case OpCode.NSTREQUALS:
1493             case OpCode.BITAND:
1494             case OpCode.BITOR:
1495             case OpCode.BITXOR:
1496                 ++ip;
1497                 break;
1498             case OpCode.RETURN:
1499             case OpCode.HALT:
1500                 ++ip;
1501                 break;
1502             default:
1503                 ++ip;
1504             }
1505         }
1506         writeln("=======================");
1507     }
1508 
1509     /// prints an individual instruction without moving the ip
1510     void printInstruction(in size_t ip, const ubyte[] chunk)
1511     {
1512         auto op = cast(OpCode)chunk[ip];
1513         switch(op)
1514         {
1515         case OpCode.NOP:
1516             writefln("%05d: %s", ip, op.opCodeToString);
1517             break;
1518         case OpCode.CONST: {
1519             immutable constID = decode!uint(chunk[ip + 1..$]);
1520             printInstructionWithConstID(ip, op, constID, _latestConstTable);
1521             break;
1522         }
1523         case OpCode.CONST_0:
1524         case OpCode.CONST_1:
1525             writefln("%05d: %s", ip, op.opCodeToString);
1526             break;
1527         case OpCode.PUSH: {
1528             immutable index = decode!int(chunk[ip + 1..$]);
1529             writefln("%05d: %s index=%s", ip, op.opCodeToString, index);
1530             break;
1531         }
1532         case OpCode.POP:
1533             writefln("%05d: %s", ip, op.opCodeToString);
1534             break;
1535         case OpCode.POPN: {
1536             immutable amount = decode!uint(chunk[ip + 1..$]);
1537             writefln("%05d: %s amount=%s", ip, op.opCodeToString, amount);
1538             break;
1539         }
1540         case OpCode.SET: {
1541             immutable index = decode!uint(chunk[ip + 1..$]);
1542             writefln("%05d: %s index=%s", ip, op.opCodeToString, index);
1543             break;
1544         }
1545         case OpCode.STACK: {
1546             immutable n = decode!uint(chunk[ip + 1..$]);
1547             writefln("%05d: %s n=%s", ip, op.opCodeToString, n);
1548             break;
1549         }
1550         case OpCode.STACK_1:
1551             writefln("%05d: %s", ip, op.opCodeToString);
1552             break;
1553         case OpCode.ARRAY: {
1554             immutable n = decode!uint(chunk[ip + 1..$]);
1555             writefln("%05d: %s n=%s", ip, op.opCodeToString, n);
1556             break;
1557         }
1558         case OpCode.OBJECT: {
1559             immutable n = decode!uint(chunk[ip + 1..$]);
1560             writefln("%05d: %s n=%s", ip, op.opCodeToString, n);
1561             break;
1562         }
1563         case OpCode.CLASS: {
1564             immutable numMethods = decode!ubyte(chunk[ip + 1..$]);
1565             immutable numGetters = decode!ubyte(chunk[ip + 2..$]);
1566             immutable numSetters = decode!ubyte(chunk[ip + 3..$]);
1567             immutable numStatics = decode!ubyte(chunk[ip + 4..$]);
1568             writefln("%05d: %s %s,%s,%s,%s", ip, op.opCodeToString, numMethods, numGetters, numSetters, numStatics);
1569             break;
1570         }
1571         case OpCode.ITER:
1572         case OpCode.DEL:
1573             writefln("%05d: %s", ip, op.opCodeToString);
1574             break;
1575         case OpCode.NEW: {
1576             immutable args = decode!uint(chunk[ip + 1..$]);
1577             writefln("%05d: %s args=%s", ip, op.opCodeToString, args);
1578             break;
1579         }
1580         case OpCode.THIS:
1581             writefln("%05d: %s", ip, op.opCodeToString);
1582             break;
1583         case OpCode.OPENSCOPE:
1584         case OpCode.CLOSESCOPE:
1585             writefln("%05d: %s", ip, op.opCodeToString);
1586             break;
1587         case OpCode.DECLVAR: 
1588         case OpCode.DECLLET:
1589         case OpCode.DECLCONST: {
1590             immutable constID = decode!uint(chunk[ip + 1..$]);
1591             printInstructionWithConstID(ip, op, constID, _latestConstTable);
1592             break;
1593         }
1594         case OpCode.GETVAR:
1595         case OpCode.SETVAR: {
1596             immutable constID = decode!uint(chunk[ip + 1..$]);
1597             printInstructionWithConstID(ip, op, constID, _latestConstTable);
1598             break;
1599         }
1600         case OpCode.OBJSET:
1601         case OpCode.OBJGET:
1602             writefln("%05d: %s", ip, op.opCodeToString);
1603             break;
1604         case OpCode.CALL: {
1605             immutable args = decode!uint(chunk[ip + 1..$]);
1606             writefln("%05d: %s args=%s", ip, op.opCodeToString, args);
1607             break;
1608         }
1609         case OpCode.JMPFALSE: 
1610         case OpCode.JMP: {
1611             immutable jump = decode!int(chunk[ip + 1..$]);
1612             writefln("%05d: %s jump=%s", ip, op.opCodeToString, jump);
1613             break;
1614         }
1615         case OpCode.SWITCH: {
1616             immutable def = decode!uint(chunk[ip + 1..$]);
1617             writefln("%05d: %s default=%s", ip, op.opCodeToString, def);
1618             break;
1619         }
1620         case OpCode.GOTO: {
1621             immutable instruction = decode!uint(chunk[ip + 1..$]);
1622             immutable depth = decode!ubyte(chunk[ip + 1 + uint.sizeof..$]);
1623             writefln("%05d: %s instruction=%s, depth=%s", ip, op.opCodeToString, instruction, depth);
1624             break;
1625         }
1626         case OpCode.THROW:
1627         case OpCode.RETHROW:
1628             writefln("%05d: %s", ip, op.opCodeToString);
1629             break;
1630         case OpCode.TRY: {
1631             immutable catchGoto = decode!uint(chunk[ip + 1..$]);
1632             writefln("%05d: %s catch=%s", ip, op.opCodeToString, catchGoto);
1633             break;
1634         }
1635         case OpCode.ENDTRY:
1636         case OpCode.LOADEXC:
1637             writefln("%05d: %s", ip, op.opCodeToString);
1638             break;
1639         case OpCode.CONCAT: {
1640             immutable n = decode!uint(chunk[ip + 1..$]);
1641             writefln("%05d: %s n=%s", ip, op.opCodeToString, n);
1642             break;
1643         }
1644         case OpCode.BITNOT:
1645         case OpCode.NOT:
1646         case OpCode.NEGATE:
1647         case OpCode.TYPEOF:
1648         case OpCode.INSTANCEOF:
1649         case OpCode.POW:
1650         case OpCode.MUL:
1651         case OpCode.DIV:
1652         case OpCode.MOD:
1653         case OpCode.ADD:
1654         case OpCode.SUB:
1655         case OpCode.LT:
1656         case OpCode.LE:
1657         case OpCode.GT:
1658         case OpCode.GE:
1659         case OpCode.EQUALS:
1660         case OpCode.NEQUALS:
1661         case OpCode.STREQUALS:
1662         case OpCode.NSTREQUALS:
1663         case OpCode.BITAND:
1664         case OpCode.BITOR:
1665         case OpCode.BITXOR:
1666             writefln("%05d: %s", ip, op.opCodeToString);
1667             break;
1668         case OpCode.RETURN:
1669         case OpCode.HALT:
1670             writefln("%05d: %s", ip, op.opCodeToString);
1671             break;
1672         default:
1673             writefln("%05d: ??? (%s)", ip, cast(ubyte)op);
1674         }  
1675     }
1676 
1677     /// print the current contents of the stack
1678     void printStack()
1679     {
1680         write("Stack: [");
1681         for(size_t i = 0; i < _stack.size; ++i)
1682         {
1683             if(_stack.array[i].type == ScriptAny.Type.STRING)
1684             {
1685                 auto str = _stack.array[i].toString();
1686                 if(str.length < 100)
1687                     write("\"" ~ str ~ "\"");
1688                 else
1689                     write("[string too long to display]");
1690             }
1691             else if(_stack.array[i].type == ScriptAny.Type.ARRAY)
1692             {
1693                 immutable arrLen = _stack.array[i].toValue!(ScriptAny[]).length;
1694                 if(arrLen < 100)
1695                     write(_stack.array[i].toString());
1696                 else
1697                     write("[array too long to display]");
1698             }
1699             else
1700             {
1701                 write(_stack.array[i].toString());
1702             }
1703             if(i < _stack.size - 1)
1704                 write(", ");
1705         }
1706         writeln("]");
1707     }
1708 
1709     /// Runs a compiled program
1710     ScriptAny runProgram(Program program, ScriptAny[] args)
1711     {
1712         _exc = null;
1713         _stopped = false;
1714         auto prevConstTable = _latestConstTable; // @suppress(dscanner.suspicious.unmodified)
1715         _latestConstTable = program.constTable;
1716         auto prevDebugMap = _latestDebugMap; // @suppress(dscanner.suspicious.unmodified)
1717         _latestDebugMap = program.debugMap;
1718         auto oldEnv = _environment; // @suppress(dscanner.suspicious.unmodified)
1719         _environment = new Environment(_environment, program.mainFunction.functionName);
1720         debug if(_printDisassembly)
1721             printProgram(program, true);
1722         try 
1723         {
1724             return runFunction(program.mainFunction, ScriptAny.UNDEFINED, args, null);
1725         }
1726         finally 
1727         {
1728             // leave them non-null for async loop
1729             if(prevConstTable)
1730                 _latestConstTable = prevConstTable;
1731             if(prevDebugMap)
1732                 _latestDebugMap = prevDebugMap;
1733             _environment = oldEnv;
1734         }
1735     }
1736 
1737     /**
1738      * This method provides a common interface for calling any ScriptFunction under the same
1739      * compilation unit as the initial program. It should not be used to run "foreign" functions
1740      * with a different const table. Use runProgram for that. This method may throw ScriptRuntimeException. Such
1741      * an exception is caught at opNew and opCall boundaries and propagated or caught by the script.
1742      * Params:
1743      *  func = The function to be run.
1744      *  thisObj = The "this" object to be used.
1745      *  args = The arguments to be passed to the function. If this is a SCRIPT_FUNCTION the arguments
1746      *         will be set up by this method.
1747      *  contextValues = Special meaning variables that are added to the SCRIPT_FUNCTION environment. For example,
1748      *      \_\_new:true means that a modified thisObj will be returned. \_\_yield\_\_:yieldFunc is used for Generator
1749      *      functions.
1750      * Returns:
1751      *   The return value of the function if an exception is not thrown in most cases (see contextValues).
1752      */
1753     ScriptAny runFunction(ScriptFunction func, ScriptAny thisObj, ScriptAny[] args, 
1754                           ScriptAny[string] contextValues=null)
1755     {
1756         ScriptAny retVal;
1757         NativeFunctionError nfe = NativeFunctionError.NO_ERROR;
1758         if(func.boundThis != ScriptAny.UNDEFINED)
1759             thisObj = func.boundThis;
1760         final switch(func.type)
1761         {
1762         case ScriptFunction.Type.SCRIPT_FUNCTION: {
1763             auto oldEnv = _environment; // @suppress(dscanner.suspicious.unmodified)
1764             _environment = func.closure is null ? 
1765                 new Environment(_environment, func.functionName) :
1766                 new Environment(func.closure, func.functionName);
1767             auto oldTrys = _tryData; // @suppress(dscanner.suspicious.unmodified)
1768             _tryData = [];
1769             immutable oldStack = _stack.size();
1770             _environment.forceSetVarOrConst("this", thisObj, false);
1771             _environment.forceSetVarOrConst("arguments", ScriptAny(args), false);
1772             for(size_t i = 0; i < func.argNames.length; ++i)
1773             {
1774                 if(i < args.length)
1775                     _environment.forceSetVarOrConst(func.argNames[i], args[i], false);
1776                 else
1777                     _environment.forceSetVarOrConst(func.argNames[i], ScriptAny.UNDEFINED, false);
1778             }
1779             foreach(name, value ; contextValues)
1780                 _environment.forceSetVarOrConst(name, value, true);
1781             try 
1782             {
1783                 retVal = runBytecode(func.compiled);
1784                 if("__new" in contextValues)
1785                     return thisObj;
1786                 else
1787                     return retVal;
1788             }
1789             finally 
1790             {
1791                 _environment = oldEnv;
1792                 _tryData = oldTrys;
1793                 _stack.size = oldStack;
1794             }
1795         }
1796         case ScriptFunction.Type.NATIVE_FUNCTION:
1797             retVal = func.nativeFunction()(_environment, &thisObj, args, nfe);
1798             break;
1799         case ScriptFunction.Type.NATIVE_DELEGATE:
1800             retVal = func.nativeDelegate()(_environment, &thisObj, args, nfe);
1801             break;
1802         }
1803 
1804         final switch(nfe)
1805         {
1806         case NativeFunctionError.NO_ERROR:
1807             break;
1808         case NativeFunctionError.WRONG_NUMBER_OF_ARGS:
1809             throwNativeRuntimeError("Wrong number of arguments to " ~ func.functionName, this);
1810             break;
1811         case NativeFunctionError.WRONG_TYPE_OF_ARG:
1812             throwNativeRuntimeError("Wrong type of argument to " ~ func.functionName, this);
1813             break;
1814         case NativeFunctionError.RETURN_VALUE_IS_EXCEPTION:
1815             throwNativeRuntimeError("Native exception", this, retVal);
1816         }
1817         if("__new" in contextValues)
1818             return thisObj;
1819         else
1820             return retVal;
1821     }
1822 
1823     private ScriptAny runBytecode(const ubyte[] chunk)
1824     {
1825         size_t ip = 0;
1826         ubyte op;
1827         while(ip < chunk.length && !_stopped)
1828         {
1829             op = chunk[ip];
1830             if(op == OpCode.RETURN)
1831                 return _stack.pop();
1832             if(_printSteps)
1833                 printInstruction(ip, chunk);
1834             ip = _ops[op](this, chunk, ip);
1835             if(_printSteps)
1836                 printStack();
1837         }
1838         debug writeln("Warning: missing return op");
1839         return ScriptAny.UNDEFINED;
1840     }
1841 
1842     /**
1843      * For coroutines (and in the future threads.)
1844      */
1845     VirtualMachine copy(bool copyStack = false)
1846     {
1847         auto vm = new VirtualMachine(_globals, _printDisassembly, _printSteps);
1848         vm._environment = _environment;
1849         vm._latestConstTable = _latestConstTable;
1850         if(copyStack)
1851             vm._stack = _stack;
1852         return vm;
1853     }
1854 
1855     /// Get the last value from opPop
1856     ScriptAny lastValuePopped() 
1857     {
1858         auto retVal = _lastValuePopped; 
1859         _lastValuePopped = ScriptAny.UNDEFINED; // to avoid memory leaks
1860         return retVal; 
1861     }
1862 
1863     /// Queue a fiber last.
1864     ScriptObject addFiber(string name, ScriptFunction func, ScriptAny thisToUse, ScriptAny[] args)
1865     {
1866         auto fiber = new ScriptFiber(name, this, func, thisToUse, args);
1867         // _fibersQueued.insert(fiber);
1868         _fibersQueued.insertAfter(_fibersQueued[], fiber);
1869         return new ScriptObject(name, null, fiber);
1870     }
1871 
1872     /// Add fiber to front.
1873     ScriptObject addFiberFirst(string name, ScriptFunction func, ScriptAny thisToUse, ScriptAny[] args)
1874     {
1875         auto fiber = new ScriptFiber(name, this, func, thisToUse, args);
1876         _fibersQueued.insertFront(fiber);
1877         return new ScriptObject(name, null, fiber);
1878     }
1879 
1880     /**
1881      * Removes a ScriptFiber from the queue
1882      * Params:
1883      *  fiber = The ScriptFiber to remove. This is an object returned by async
1884      * Returns:
1885      *  Whether or not the fiber was successfully removed.
1886      */
1887     bool removeFiber(ScriptFiber fiber)
1888     {
1889         return _fibersQueued.linearRemoveElement(fiber);
1890     }
1891 
1892     // TODO await functionality for running a specific fiber to completion and waiting on it
1893 
1894     /**
1895      * Runs the queued fibers repeatedly until they are done. This is called by Interpreter.runVMFibers
1896      */
1897     void runFibersToCompletion()
1898     {
1899         _gSync = new Semaphore;
1900         
1901         while(!_fibersQueued.empty)
1902         {
1903             auto fibersRunning = _fibersQueued;
1904             _fibersQueued = SList!ScriptFiber();
1905             foreach(fiber ; fibersRunning)
1906             {
1907                 fiber.call();
1908                 if(_exc)
1909                 {
1910                     foreach(fib ; fibersRunning)
1911                     {
1912                         if(fiber != fib)
1913                             _fibersQueued.insert(fib);
1914                     }
1915                     break;
1916                 }        
1917                 if(fiber.state != Fiber.State.TERM)
1918                     _fibersQueued.insert(fiber);
1919             }
1920 
1921             if(_gWaitingOnThreads > 0)
1922                 _gSync.wait();
1923             if(_exc)
1924                 break;
1925         }
1926         if(_exc)
1927             throw _exc;
1928     }
1929 
1930     // TODO: runFibersOnce for game event loops
1931 
1932     /// Whether or not there is an exception flag set
1933     bool hasException() const { return _exc !is null; }
1934 
1935 private:
1936 
1937     struct TryData
1938     {
1939         int depth;
1940         size_t stackSize;
1941         uint catchGoto;
1942     }
1943 
1944     void printInstructionWithConstID(size_t ip, OpCode op, uint constID, ConstTable ct)
1945     {
1946         if(ct.get(constID).toString().length < 100)
1947             writefln("%05d: %s #%s // <%s> %s", 
1948                 ip, op.opCodeToString, constID, ct.get(constID).typeToString(),
1949                 ct.get(constID));
1950         else
1951             writefln("%05d: %s #%s // <%s>", 
1952                 ip, op.opCodeToString, constID, ct.get(constID).typeToString());
1953     }
1954 
1955     DebugMap _latestDebugMap;
1956     ConstTable _latestConstTable;
1957     ScriptRuntimeException _exc; // exception flag
1958     Environment _environment;
1959     Environment _globals;
1960     OpCodeFunction[ubyte.max + 1] _ops;
1961     Stack!ScriptAny _stack;
1962     TryData[] _tryData; // latest
1963     ScriptAny _lastValuePopped;
1964     // async stuff
1965     SList!ScriptFiber _fibersQueued = SList!ScriptFiber();
1966     size_t _gWaitingOnThreads = 0;
1967     __gshared Semaphore _gSync;
1968 
1969     // bytecode debugging
1970     bool _printDisassembly;
1971     bool _printSteps;
1972 
1973     /// stops the machine
1974     bool _stopped;
1975 }
1976 
1977 class VMException : Exception
1978 {
1979     this(string msg, size_t iptr, OpCode op, string file = __FILE__, size_t line = __LINE__)
1980     {
1981         super(msg, file, line);
1982         ip = iptr;
1983         opcode = op;
1984     }
1985 
1986     override string toString() const
1987     {
1988         import std.format: format;
1989         return msg ~ " at instruction " ~ format("%x", ip) ~ " (" ~ opcode.opCodeToString ~ ")";
1990     }
1991 
1992     size_t ip;
1993     OpCode opcode;
1994 }
1995 
1996 unittest
1997 {
1998     auto vm = new VirtualMachine(new Environment(null, "<global>"));
1999     vm = vm.copy();
2000     ubyte[] chunk;
2001     auto constTable = new ConstTable();
2002 
2003     ubyte[] getConst(T)(T value)
2004     {
2005         return encode(constTable.addValueUint(ScriptAny(value)));
2006     }
2007 
2008     ScriptAny native_tpropGet(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
2009     {
2010         writeln("in native_tpropGet");
2011         return ScriptAny(1000);
2012     }
2013 
2014     ScriptAny native_tpropSet(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
2015     {
2016         writeln("Setting value to " ~ args[0].toString);
2017         return ScriptAny.UNDEFINED;
2018     }
2019 
2020     /*
2021 
2022     */
2023     auto testObj = new ScriptObject("test", null, null);
2024     testObj.addGetterProperty("tprop", new ScriptFunction("tpropGet", &native_tpropGet));
2025     testObj.addSetterProperty("tprop", new ScriptFunction("tpropSet", &native_tpropSet));
2026 
2027     chunk ~= OpCode.CONST ~ getConst(testObj);
2028     chunk ~= OpCode.CONST ~ getConst("tprop");
2029     chunk ~= OpCode.OBJGET;
2030     chunk ~= OpCode.CONST ~ getConst(testObj);
2031     chunk ~= OpCode.CONST ~ getConst("tprop");
2032     chunk ~= OpCode.CONST_1;
2033     chunk ~= OpCode.OBJSET;
2034 
2035     auto program = new Program(constTable, new ScriptFunction("<test>", [], chunk));
2036 
2037     vm.runProgram(program, []);
2038 }