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 }