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