Spaces:
Running
Running
; | |
/* | |
* Copyright (c) 2014-2025 Vanessa Freudenberg | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to deal | |
* in the Software without restriction, including without limitation the rights | |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
* copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in | |
* all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
* THE SOFTWARE. | |
*/ | |
Object.subclass('Squeak.Compiler', | |
/**************************************************************************** | |
VM and Compiler | |
=============== | |
The VM has an interpreter, it will work fine (and much more memory-efficient) | |
without loading a compiler. The compiler plugs into the VM by providing the | |
Squeak.Compiler global. It can be easily replaced by just loading a different | |
script providing Squeak.Compiler. | |
The VM creates the compiler instance after an image has been loaded and the VM | |
been initialized. Whenever a method is activated that was not compiled yet, the | |
compiler gets a chance to compile it. The compiler may decide to wait for a couple | |
of activations before actually compiling it. This might prevent do-its from ever | |
getting compiled, because they are only activated once. Therefore, the compiler | |
is also called when a long-running non-optimized loop calls checkForInterrupts. | |
Finally, whenever the interpreter is about to execute a bytecode, it calls the | |
compiled method instead (which typically will execute many bytecodes): | |
initialize: | |
compiler = new Squeak.Compiler(vm); | |
executeNewMethod, checkForInterrupts: | |
if (!method.compiled && compiler) | |
compiler.compile(method); | |
interpret: | |
if (method.compiled) method.compiled(vm); | |
Note that a compiler could hook itself into a compiled method by dispatching | |
to vm.compiler in the generated code. This would allow gathering statistics, | |
recompiling with optimized code etc. | |
About This Compiler | |
=================== | |
The compiler in this file is meant to be simple, fast-compiling, and general. | |
It transcribes bytecodes 1-to-1 into equivalent JavaScript code using | |
templates (and thus can even support single-stepping). It uses the | |
interpreter's stack pointer (SP) and program counter (PC), actual context | |
objects just like the interpreter, no register mapping, it does not optimize | |
sends, etc. | |
Jumps are handled by wrapping the whole method in a loop and switch. This also | |
enables continuing in the middle of a compiled method: whenever another context | |
is activated, the method returns to the main loop, and is entered again later | |
with a different PC. Here is an example method, its bytecodes, and a simplified | |
version of the generated JavaScript code: | |
method | |
[value selector] whileFalse. | |
^ 42 | |
0 <00> pushInstVar: 0 | |
1 <D0> send: #selector | |
2 <A8 02> jumpIfTrue: 6 | |
4 <A3 FA> jumpTo: 0 | |
6 <21> pushConst: 42 | |
7 <7C> return: topOfStack | |
context = vm.activeContext | |
while (true) switch (vm.pc) { | |
case 0: | |
stack[++vm.sp] = inst[0]; | |
vm.pc = 2; vm.send(#selector); // activate new method | |
return; // return to main loop | |
// Main loop will execute the activated method. When | |
// that method returns, this method will be called | |
// again with vm.pc == 2 and jump directly to case 2 | |
case 2: | |
if (stack[vm.sp--] === vm.trueObj) { | |
vm.pc = 6; | |
continue; // jump to case 6 | |
} | |
// otherwise fall through to next case | |
case 4: | |
vm.pc = 0; | |
continue; // jump to case 0 | |
case 6: | |
stack[++vm.sp] = 42; | |
vm.pc = 7; vm.doReturn(stack[vm.sp]); | |
return; | |
} | |
Debugging support | |
================= | |
This compiler supports generating single-stepping code and comments, which are | |
rather helpful during debugging. | |
Normally, only bytecodes that can be a jump target are given a label. Also, | |
bytecodes following a send operation need a label, to enable returning to that | |
spot after the context switch. All other bytecodes are executed continuously. | |
When compiling for single-stepping, each bytecode gets a label, and after each | |
bytecode a flag is checked and the method returns if needed. Because this is | |
a performance penalty, methods are first compiled without single-step support, | |
and recompiled for single-stepping on demand. | |
This is optional, another compiler can answer false from enableSingleStepping(). | |
In that case the VM will delete the compiled method and invoke the interpreter | |
to single-step. | |
*****************************************************************************/ | |
'initialization', { | |
initialize: function(vm) { | |
this.vm = vm; | |
this.comments = !!Squeak.Compiler.comments, // generate comments | |
// for debug-printing only | |
this.specialSelectors = ['+', '-', '<', '>', '<=', '>=', '=', '~=', '*', '/', '\\\\', '@', | |
'bitShift:', '//', 'bitAnd:', 'bitOr:', 'at:', 'at:put:', 'size', 'next', 'nextPut:', | |
'atEnd', '==', 'class', 'blockCopy:', 'value', 'value:', 'do:', 'new', 'new:', 'x', 'y']; | |
this.doitCounter = 0; | |
this.blockCounter = 0; | |
}, | |
}, | |
'accessing', { | |
compile: function(method, optClassObj, optSelObj) { | |
if (method.compiled === undefined) { | |
// 1st time | |
method.compiled = false; | |
} else { | |
// 2nd time | |
this.singleStep = false; | |
this.debug = this.comments; | |
var clsName, sel, instVars; | |
if (this.debug && !optClassObj) { | |
// this is expensive, so only do it when debugging | |
var isMethod = method.sqClass === this.vm.specialObjects[Squeak.splOb_ClassCompiledMethod]; | |
this.vm.allMethodsDo(function(classObj, methodObj, selectorObj) { | |
if (isMethod ? methodObj === method : methodObj.pointers.includes(method)) { | |
optClassObj = classObj; | |
optSelObj = selectorObj; | |
return true; | |
} | |
}); | |
} | |
if (optClassObj) { | |
clsName = optClassObj.className(); | |
sel = optSelObj.bytesAsString(); | |
if (this.debug) { | |
// only when debugging | |
var isMethod = method.sqClass === this.vm.specialObjects[Squeak.splOb_ClassCompiledMethod]; | |
if (!isMethod) { | |
clsName = "[] in " + clsName; | |
} | |
instVars = optClassObj.allInstVarNames(); | |
} | |
} | |
method.compiled = this.generate(method, clsName, sel, instVars); | |
} | |
}, | |
enableSingleStepping: function(method, optClass, optSel) { | |
// recompile method for single-stepping | |
if (!method.compiled || !method.compiled.canSingleStep) { | |
this.singleStep = true; // generate breakpoint support | |
this.debug = true; | |
if (!optClass) { | |
this.vm.allMethodsDo(function(classObj, methodObj, selectorObj) { | |
if (methodObj === method) { | |
optClass = classObj; | |
optSel = selectorObj; | |
return true; | |
} | |
}); | |
} | |
var cls = optClass && optClass.className(); | |
var sel = optSel && optSel.bytesAsString(); | |
var instVars = optClass && optClass.allInstVarNames(); | |
method.compiled = this.generate(method, cls, sel, instVars); | |
method.compiled.canSingleStep = true; | |
} | |
// if a compiler does not support single-stepping, return false | |
return true; | |
}, | |
functionNameFor: function(cls, sel) { | |
if (cls === undefined || cls === '?') { | |
var isMethod = this.method.sqClass === this.vm.specialObjects[Squeak.splOb_ClassCompiledMethod]; | |
return isMethod ? "DOIT_" + ++this.doitCounter : "BLOCK_" + ++this.blockCounter; | |
} | |
cls = cls.replace(/ /g, "_").replace("[]", "Block"); | |
if (!/[^a-zA-Z0-9:_]/.test(sel)) | |
return cls + "_" + sel.replace(/:/g, "ː"); // unicode colon is valid in JS identifiers | |
var op = sel.replace(/./g, function(char) { | |
var repl = {'|': "OR", '~': "NOT", '<': "LT", '=': "EQ", '>': "GT", | |
'&': "AND", '@': "AT", '*': "TIMES", '+': "PLUS", '\\': "MOD", | |
'-': "MINUS", ',': "COMMA", '/': "DIV", '?': "IF"}[char]; | |
return repl || 'OPERATOR'; | |
}); | |
return cls + "__" + op + "__"; | |
}, | |
}, | |
'generating', { | |
generate: function(method, optClass, optSel, optInstVarNames) { | |
this.method = method; | |
this.sista = method.methodSignFlag(); | |
this.pc = 0; // next bytecode | |
this.endPC = 0; // pc of furthest jump target | |
this.prevPC = 0; // pc at start of current instruction | |
this.source = []; // snippets will be joined in the end | |
this.sourceLabels = {}; // source pos of generated jump labels | |
this.needsLabel = {}; // jump targets | |
this.sourcePos = {}; // source pos of optional vars / statements | |
this.needsVar = {}; // true if var was used | |
this.needsBreak = false; // insert break check for previous bytecode | |
if (optClass && optSel) | |
this.source.push("// ", optClass, ">>", optSel, "\n"); | |
this.instVarNames = optInstVarNames; | |
this.allVars = ['context', 'stack', 'rcvr', 'inst[', 'temp[', 'lit[']; | |
this.sourcePos['context'] = this.source.length; this.source.push("var context = vm.activeContext;\n"); | |
this.sourcePos['stack'] = this.source.length; this.source.push("var stack = context.pointers;\n"); | |
this.sourcePos['rcvr'] = this.source.length; this.source.push("var rcvr = vm.receiver;\n"); | |
this.sourcePos['inst['] = this.source.length; this.source.push("var inst = rcvr.pointers;\n"); | |
this.sourcePos['temp['] = this.source.length; this.source.push("var temp = vm.homeContext.pointers;\n"); | |
this.sourcePos['lit['] = this.source.length; this.source.push("var lit = vm.method.pointers;\n"); | |
this.sourcePos['loop-start'] = this.source.length; this.source.push("while (true) switch (vm.pc) {\ncase 0:\n"); | |
if (this.sista) this.generateSista(method); | |
else this.generateV3(method); | |
var funcName = this.functionNameFor(optClass, optSel); | |
if (this.singleStep) { | |
if (this.debug) this.source.push("// all valid PCs have a label;\n"); | |
this.source.push("default: throw Error('invalid PC');\n}"); // all PCs handled | |
} else { | |
this.sourcePos['loop-end'] = this.source.length; this.source.push("default: vm.interpretOne(true); return;\n}"); | |
this.deleteUnneededLabels(); | |
} | |
this.deleteUnneededVariables(); | |
var source = "'use strict';\nreturn function " + funcName + "(vm) {\n" + this.source.join("") + "}"; | |
return new Function(source)(); | |
}, | |
generateV3: function(method) { | |
this.done = false; | |
while (!this.done) { | |
var byte = method.bytes[this.pc++], | |
byte2 = 0; | |
switch (byte & 0xF8) { | |
// load inst var | |
case 0x00: case 0x08: | |
this.generatePush("inst[", byte & 0x0F, "]"); | |
break; | |
// load temp var | |
case 0x10: case 0x18: | |
this.generatePush("temp[", 6 + (byte & 0xF), "]"); | |
break; | |
// loadLiteral | |
case 0x20: case 0x28: case 0x30: case 0x38: | |
this.generatePush("lit[", 1 + (byte & 0x1F), "]"); | |
break; | |
// loadLiteralIndirect | |
case 0x40: case 0x48: case 0x50: case 0x58: | |
this.generatePush("lit[", 1 + (byte & 0x1F), "].pointers[1]"); | |
break; | |
// storeAndPop inst var | |
case 0x60: | |
this.generatePopInto("inst[", byte & 0x07, "]"); | |
break; | |
// storeAndPop temp var | |
case 0x68: | |
this.generatePopInto("temp[", 6 + (byte & 0x07), "]"); | |
break; | |
// Quick push | |
case 0x70: | |
switch (byte) { | |
case 0x70: this.generatePush("rcvr"); break; | |
case 0x71: this.generatePush("vm.trueObj"); break; | |
case 0x72: this.generatePush("vm.falseObj"); break; | |
case 0x73: this.generatePush("vm.nilObj"); break; | |
case 0x74: this.generatePush("-1"); break; | |
case 0x75: this.generatePush("0"); break; | |
case 0x76: this.generatePush("1"); break; | |
case 0x77: this.generatePush("2"); break; | |
} | |
break; | |
// Quick return | |
case 0x78: | |
switch (byte) { | |
case 0x78: this.generateReturn("rcvr"); break; | |
case 0x79: this.generateReturn("vm.trueObj"); break; | |
case 0x7A: this.generateReturn("vm.falseObj"); break; | |
case 0x7B: this.generateReturn("vm.nilObj"); break; | |
case 0x7C: this.generateReturn("stack[vm.sp]"); break; | |
case 0x7D: this.generateBlockReturn(); break; | |
default: throw Error("unusedBytecode " + byte); | |
} | |
break; | |
// Extended bytecodes | |
case 0x80: case 0x88: | |
this.generateV3Extended(byte); | |
break; | |
// short jump | |
case 0x90: | |
this.generateJump((byte & 0x07) + 1); | |
break; | |
// short conditional jump | |
case 0x98: | |
this.generateJumpIf(false, (byte & 0x07) + 1); | |
break; | |
// long jump, forward and back | |
case 0xA0: | |
byte2 = method.bytes[this.pc++]; | |
this.generateJump(((byte&7)-4) * 256 + byte2); | |
break; | |
// long conditional jump | |
case 0xA8: | |
byte2 = method.bytes[this.pc++]; | |
this.generateJumpIf(byte < 0xAC, (byte & 3) * 256 + byte2); | |
break; | |
// SmallInteger ops: + - < > <= >= = ~= * / @ lshift: lxor: land: lor: | |
case 0xB0: case 0xB8: | |
this.generateNumericOp(byte); | |
break; | |
// quick primitives: // at:, at:put:, size, next, nextPut:, ... | |
case 0xC0: case 0xC8: | |
this.generateQuickPrim(byte); | |
break; | |
// send literal selector | |
case 0xD0: case 0xD8: | |
this.generateSend("lit[", 1 + (byte & 0x0F), "]", 0, false); | |
break; | |
case 0xE0: case 0xE8: | |
this.generateSend("lit[", 1 + (byte & 0x0F), "]", 1, false); | |
break; | |
case 0xF0: case 0xF8: | |
this.generateSend("lit[", 1 + (byte & 0x0F), "]", 2, false); | |
break; | |
} | |
} | |
}, | |
generateV3Extended: function(bytecode) { | |
var byte2, byte3; | |
switch (bytecode) { | |
// extended push | |
case 0x80: | |
byte2 = this.method.bytes[this.pc++]; | |
switch (byte2 >> 6) { | |
case 0: this.generatePush("inst[", byte2 & 0x3F, "]"); return; | |
case 1: this.generatePush("temp[", 6 + (byte2 & 0x3F), "]"); return; | |
case 2: this.generatePush("lit[", 1 + (byte2 & 0x3F), "]"); return; | |
case 3: this.generatePush("lit[", 1 + (byte2 & 0x3F), "].pointers[1]"); return; | |
} | |
// extended store | |
case 0x81: | |
byte2 = this.method.bytes[this.pc++]; | |
switch (byte2 >> 6) { | |
case 0: this.generateStoreInto("inst[", byte2 & 0x3F, "]"); return; | |
case 1: this.generateStoreInto("temp[", 6 + (byte2 & 0x3F), "]"); return; | |
case 2: throw Error("illegal store into literal"); | |
case 3: this.generateStoreInto("lit[", 1 + (byte2 & 0x3F), "].pointers[1]"); return; | |
} | |
return; | |
// extended pop into | |
case 0x82: | |
byte2 = this.method.bytes[this.pc++]; | |
switch (byte2 >> 6) { | |
case 0: this.generatePopInto("inst[", byte2 & 0x3F, "]"); return; | |
case 1: this.generatePopInto("temp[", 6 + (byte2 & 0x3F), "]"); return; | |
case 2: throw Error("illegal pop into literal"); | |
case 3: this.generatePopInto("lit[", 1 + (byte2 & 0x3F), "].pointers[1]"); return; | |
} | |
// Single extended send | |
case 0x83: | |
byte2 = this.method.bytes[this.pc++]; | |
this.generateSend("lit[", 1 + (byte2 & 0x1F), "]", byte2 >> 5, false); | |
return; | |
// Double extended do-anything | |
case 0x84: | |
byte2 = this.method.bytes[this.pc++]; | |
byte3 = this.method.bytes[this.pc++]; | |
switch (byte2 >> 5) { | |
case 0: this.generateSend("lit[", 1 + byte3, "]", byte2 & 31, false); return; | |
case 1: this.generateSend("lit[", 1 + byte3, "]", byte2 & 31, true); return; | |
case 2: this.generatePush("inst[", byte3, "]"); return; | |
case 3: this.generatePush("lit[", 1 + byte3, "]"); return; | |
case 4: this.generatePush("lit[", 1 + byte3, "].pointers[1]"); return; | |
case 5: this.generateStoreInto("inst[", byte3, "]"); return; | |
case 6: this.generatePopInto("inst[", byte3, "]"); return; | |
case 7: this.generateStoreInto("lit[", 1 + byte3, "].pointers[1]"); return; | |
} | |
// Single extended send to super | |
case 0x85: | |
byte2 = this.method.bytes[this.pc++]; | |
this.generateSend("lit[", 1 + (byte2 & 0x1F), "]", byte2 >> 5, true); | |
return; | |
// Second extended send | |
case 0x86: | |
byte2 = this.method.bytes[this.pc++]; | |
this.generateSend("lit[", 1 + (byte2 & 0x3F), "]", byte2 >> 6, false); | |
return; | |
// pop | |
case 0x87: | |
this.generateInstruction("pop", "vm.sp--"); | |
return; | |
// dup | |
case 0x88: | |
this.needsVar['stack'] = true; | |
this.generateInstruction("dup", "var dup = stack[vm.sp]; stack[++vm.sp] = dup"); | |
return; | |
// thisContext | |
case 0x89: | |
this.needsVar['stack'] = true; | |
this.generateInstruction("push thisContext", "stack[++vm.sp] = vm.exportThisContext()"); | |
return; | |
// closures | |
case 0x8A: | |
byte2 = this.method.bytes[this.pc++]; | |
var popValues = byte2 > 127, | |
count = byte2 & 127; | |
this.generateClosureTemps(count, popValues); | |
return; | |
// call primitive | |
case 0x8B: | |
byte2 = this.method.bytes[this.pc++]; | |
byte3 = this.method.bytes[this.pc++]; | |
this.generateCallPrimitive(byte2 + 256 * byte3, 0x81); | |
return | |
// remote push from temp vector | |
case 0x8C: | |
byte2 = this.method.bytes[this.pc++]; | |
byte3 = this.method.bytes[this.pc++]; | |
this.generatePush("temp[", 6 + byte3, "].pointers[", byte2, "]"); | |
return; | |
// remote store into temp vector | |
case 0x8D: | |
byte2 = this.method.bytes[this.pc++]; | |
byte3 = this.method.bytes[this.pc++]; | |
this.generateStoreInto("temp[", 6 + byte3, "].pointers[", byte2, "]"); | |
return; | |
// remote store and pop into temp vector | |
case 0x8E: | |
byte2 = this.method.bytes[this.pc++]; | |
byte3 = this.method.bytes[this.pc++]; | |
this.generatePopInto("temp[", 6 + byte3, "].pointers[", byte2, "]"); | |
return; | |
// pushClosureCopy | |
case 0x8F: | |
byte2 = this.method.bytes[this.pc++]; | |
byte3 = this.method.bytes[this.pc++]; | |
var byte4 = this.method.bytes[this.pc++]; | |
var numArgs = byte2 & 0xF, | |
numCopied = byte2 >> 4, | |
blockSize = byte3 << 8 | byte4; | |
this.generateClosureCopy(numArgs, numCopied, blockSize); | |
return; | |
} | |
}, | |
generateSista: function() { | |
var bytes = this.method.bytes, | |
b, | |
b2, | |
b3, | |
extA = 0, | |
extB = 0; | |
this.done = false; | |
while (!this.done) { | |
b = bytes[this.pc++]; | |
switch (b) { | |
// 1 Byte Bytecodes | |
// load receiver variable | |
case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: | |
case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F: | |
this.generatePush("inst[", b & 0x0F, "]"); | |
break; | |
// load literal variable | |
case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: | |
case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F: | |
this.generatePush("lit[", 1 + (b & 0x0F), "].pointers[1]"); | |
break; | |
// load literal constant | |
case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: | |
case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: | |
case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: | |
case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: | |
this.generatePush("lit[", 1 + (b & 0x1F), "]"); | |
break; | |
// load temporary variable | |
case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: | |
this.generatePush("temp[", 6 + (b & 0x07), "]"); | |
break; | |
case 0x48: case 0x49: case 0x4A: case 0x4B: | |
this.generatePush("temp[", 6 + (b & 0x03) + 8, "]"); | |
break; | |
case 0x4C: this.generatePush("rcvr"); | |
break; | |
case 0x4D: this.generatePush("vm.trueObj"); | |
break; | |
case 0x4E: this.generatePush("vm.falseObj"); | |
break; | |
case 0x4F: this.generatePush("vm.nilObj"); | |
break; | |
case 0x50: this.generatePush(0); | |
break; | |
case 0x51: this.generatePush(1); | |
break; | |
case 0x52: | |
this.needsVar['stack'] = true; | |
this.generateInstruction("push thisContext", "stack[++vm.sp] = vm.exportThisContext()"); | |
break; | |
case 0x53: | |
this.needsVar['stack'] = true; | |
this.generateInstruction("dup", "var dup = stack[vm.sp]; stack[++vm.sp] = dup"); | |
break; | |
case 0x54: case 0x55: case 0x56: case 0x57: | |
throw Error("unusedBytecode " + b); | |
case 0x58: this.generateReturn("rcvr"); | |
break; | |
case 0x59: this.generateReturn("vm.trueObj"); | |
break; | |
case 0x5A: this.generateReturn("vm.falseObj"); | |
break; | |
case 0x5B: this.generateReturn("vm.nilObj"); | |
break; | |
case 0x5C: this.generateReturn("stack[vm.sp]"); | |
break; | |
case 0x5D: this.generateBlockReturn("vm.nilObj"); | |
break; | |
case 0x5E: this.generateBlockReturn(); | |
break; | |
case 0x5F: break; // nop | |
case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: | |
case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: | |
this.generateNumericOp(b); | |
break; | |
case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: | |
case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: | |
this.generateQuickPrim(b); | |
break; | |
case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: | |
case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E: case 0x8F: | |
this.generateSend("lit[", 1 + (b & 0x0F), "]", 0, false); | |
break; | |
case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: | |
case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F: | |
this.generateSend("lit[", 1 + (b & 0x0F), "]", 1, false); | |
break; | |
case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: | |
case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF: | |
this.generateSend("lit[", 1 + (b & 0x0F), "]", 2, false); | |
break; | |
case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: | |
this.generateJump((b & 0x07) + 1); | |
break; | |
case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: | |
this.generateJumpIf(true, (b & 0x07) + 1); | |
break; | |
case 0xC0: case 0xC1: case 0xC2: case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7: | |
this.generateJumpIf(false, (b & 0x07) + 1); | |
break; | |
case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: | |
this.generatePopInto("inst[", b & 0x07, "]"); | |
break; | |
case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD5: case 0xD6: case 0xD7: | |
this.generatePopInto("temp[", 6 + (b & 0x07), "]"); | |
break; | |
case 0xD8: this.generateInstruction("pop", "vm.sp--"); | |
break; | |
case 0xD9: | |
throw Error("unumplementedBytecode: 0xD9 (unconditional trap)"); | |
case 0xDA: case 0xDB: case 0xDC: case 0xDD: case 0xDE: case 0xDF: | |
throw Error("unusedBytecode " + b); | |
// 2 Byte Bytecodes | |
case 0xE0: | |
b2 = bytes[this.pc++]; | |
extA = extA * 256 + b2; | |
continue; | |
case 0xE1: | |
b2 = bytes[this.pc++]; | |
extB = extB * 256 + (b2 < 128 ? b2 : b2 - 256); | |
continue; | |
case 0xE2: | |
b2 = bytes[this.pc++]; | |
this.generatePush("inst[", b2 + extA * 256, "]"); | |
break; | |
case 0xE3: | |
b2 = bytes[this.pc++]; | |
this.generatePush("lit[", 1 + b2 + extA * 256, "].pointers[1]"); | |
break; | |
case 0xE4: | |
b2 = bytes[this.pc++]; | |
this.generatePush("lit[", 1 + b2 + extA * 256, "]"); | |
break; | |
case 0xE5: | |
b2 = bytes[this.pc++]; | |
this.generatePush("temp[", 6 + b2, "]"); | |
break; | |
case 0xE6: | |
throw Error("unusedBytecode 0xE6"); | |
case 0xE7: | |
b2 = bytes[this.pc++]; | |
var popValues = b2 > 127, | |
count = b2 & 127; | |
this.generateClosureTemps(count, popValues); | |
break; | |
case 0xE8: | |
b2 = bytes[this.pc++]; | |
this.generatePush(b2 + extB * 256); | |
break; | |
case 0xE9: | |
b2 = bytes[this.pc++]; | |
this.generatePush("vm.image.getCharacter(", b2 + extB * 256, ")"); | |
break; | |
case 0xEA: | |
b2 = bytes[this.pc++]; | |
this.generateSend("lit[", 1 + (b2 >> 3) + (extA << 5), "]", (b2 & 7) + (extB << 3), false); | |
break; | |
case 0xEB: | |
b2 = bytes[this.pc++]; | |
var lit = (b2 >> 3) + (extA << 5), | |
numArgs = (b2 & 7) + ((extB & 63) << 3), | |
directed = extB >= 64; | |
this.generateSend("lit[", 1 + lit, "]", numArgs, directed ? "directed" : true); | |
break; | |
case 0xEC: | |
throw Error("unimplemented bytecode: 0xEC (class trap)"); | |
case 0xED: | |
b2 = bytes[this.pc++]; | |
this.generateJump(b2 + extB * 256); | |
break; | |
case 0xEE: | |
b2 = bytes[this.pc++]; | |
this.generateJumpIf(true, b2 + extB * 256); | |
break; | |
case 0xEF: | |
b2 = bytes[this.pc++]; | |
this.generateJumpIf(false, b2 + extB * 256); | |
break; | |
case 0xF0: | |
b2 = bytes[this.pc++]; | |
this.generatePopInto("inst[", b2 + extA * 256, "]"); | |
break; | |
case 0xF1: | |
b2 = bytes[this.pc++]; | |
this.generatePopInto("lit[", 1 + b2 + extA * 256, "].pointers[1]"); | |
break; | |
case 0xF2: | |
b2 = bytes[this.pc++]; | |
this.generatePopInto("temp[", 6 + b2, "]"); | |
break; | |
case 0xF3: | |
b2 = bytes[this.pc++]; | |
this.generateStoreInto("inst[", b2 + extA * 256, "]"); | |
break; | |
case 0xF4: | |
b2 = bytes[this.pc++]; | |
this.generateStoreInto("lit[", 1 + b2 + extA * 256, "].pointers[1]"); | |
break; | |
case 0xF5: | |
b2 = bytes[this.pc++]; | |
this.generateStoreInto("temp[", 6 + b2, "]"); | |
break; | |
case 0xF6: case 0xF7: | |
throw Error("unusedBytecode " + b); | |
// 3 Byte Bytecodes | |
case 0xF8: | |
b2 = bytes[this.pc++]; | |
b3 = bytes[this.pc++]; | |
this.generateCallPrimitive(b2 + b3 * 256, 0xF5); | |
break; | |
case 0xF9: | |
b2 = bytes[this.pc++]; | |
b3 = bytes[this.pc++]; | |
this.generatePushFullClosure(b2 + extA * 255, b3); | |
break; | |
case 0xFA: | |
b2 = bytes[this.pc++]; | |
b3 = bytes[this.pc++]; | |
var numArgs = (b2 & 0x07) + (extA & 0x0F) * 8, | |
numCopied = (b2 >> 3 & 0x7) + (extA >> 4) * 8, | |
blockSize = b3 + (extB << 8); | |
this.generateClosureCopy(numArgs, numCopied, blockSize); | |
break; | |
case 0xFB: | |
b2 = bytes[this.pc++]; | |
b3 = bytes[this.pc++]; | |
this.generatePush("temp[", 6 + b3, "].pointers[", b2, "]"); | |
break; | |
case 0xFC: | |
b2 = bytes[this.pc++]; | |
b3 = bytes[this.pc++]; | |
this.generateStoreInto("temp[", 6 + b3, "].pointers[", b2, "]"); | |
break; | |
case 0xFD: | |
b2 = bytes[this.pc++]; | |
b3 = bytes[this.pc++]; | |
this.generatePopInto("temp[", 6 + b3, "].pointers[", b2, "]"); | |
break; | |
case 0xFE: case 0xFF: | |
throw Error("unusedBytecode " + b); | |
default: | |
throw Error("illegal bytecode: " + b); | |
} | |
extA = 0; | |
extB = 0; | |
} | |
}, | |
generatePush: function(target, arg1, suffix1, arg2, suffix2) { | |
if (this.debug) this.generateDebugCode("push", target, arg1, suffix1, arg2, suffix2); | |
this.generateLabel(); | |
this.needsVar[target] = true; | |
this.needsVar['stack'] = true; | |
this.source.push("stack[++vm.sp] = ", target); | |
if (arg1 !== undefined) { | |
this.source.push(arg1, suffix1); | |
if (arg2 !== undefined) { | |
this.source.push(arg2, suffix2); | |
} | |
} | |
this.source.push(";\n"); | |
}, | |
generateStoreInto: function(target, arg1, suffix1, arg2, suffix2) { | |
if (this.debug) this.generateDebugCode("store into", target, arg1, suffix1, arg2, suffix2); | |
this.generateLabel(); | |
this.needsVar[target] = true; | |
this.needsVar['stack'] = true; | |
this.source.push(target); | |
if (arg1 !== undefined) { | |
this.source.push(arg1, suffix1); | |
if (arg2 !== undefined) { | |
this.source.push(arg2, suffix2); | |
} | |
} | |
this.source.push(" = stack[vm.sp];\n"); | |
this.generateDirty(target, arg1, suffix1); | |
}, | |
generatePopInto: function(target, arg1, suffix1, arg2, suffix2) { | |
if (this.debug) this.generateDebugCode("pop into", target, arg1, suffix1, arg2, suffix2); | |
this.generateLabel(); | |
this.needsVar[target] = true; | |
this.needsVar['stack'] = true; | |
this.source.push(target); | |
if (arg1 !== undefined) { | |
this.source.push(arg1, suffix1); | |
if (arg2 !== undefined) { | |
this.source.push(arg2, suffix2); | |
} | |
} | |
this.source.push(" = stack[vm.sp--];\n"); | |
this.generateDirty(target, arg1, suffix1); | |
}, | |
generateReturn: function(what) { | |
if (this.debug) this.generateDebugCode("return", what); | |
this.generateLabel(); | |
this.needsVar[what] = true; | |
this.source.push( | |
"vm.pc = ", this.pc, "; vm.doReturn(", what, "); return;\n"); | |
this.needsBreak = false; // returning anyway | |
this.done = this.pc > this.endPC; | |
}, | |
generateBlockReturn: function(retVal) { | |
if (this.debug) this.generateDebugCode("block return"); | |
this.generateLabel(); | |
if (!retVal) { | |
this.needsVar['stack'] = true; | |
retVal = "stack[vm.sp--]"; | |
} | |
// actually stack === context.pointers but that would look weird | |
this.needsVar['context'] = true; | |
this.source.push( | |
"vm.pc = ", this.pc, "; vm.doReturn(", retVal, ", context.pointers[0]); return;\n"); | |
this.needsBreak = false; // returning anyway | |
this.done = this.pc > this.endPC; | |
}, | |
generateJump: function(distance) { | |
var destination = this.pc + distance; | |
if (this.debug) this.generateDebugCode("jump to " + destination); | |
this.generateLabel(); | |
this.needsVar['context'] = true; | |
this.source.push("vm.pc = ", destination, "; "); | |
if (distance < 0) this.source.push( | |
"\nif (vm.interruptCheckCounter-- <= 0) {\n", | |
" vm.checkForInterrupts();\n", | |
" if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return;\n", | |
"}\n"); | |
if (this.singleStep) this.source.push("\nif (vm.breakOutOfInterpreter) return;\n"); | |
this.source.push("continue;\n"); | |
this.needsBreak = false; // already checked | |
this.needsLabel[destination] = true; | |
if (destination > this.endPC) this.endPC = destination; | |
}, | |
generateJumpIf: function(condition, distance) { | |
var destination = this.pc + distance; | |
if (this.debug) this.generateDebugCode("jump if " + condition + " to " + destination); | |
this.generateLabel(); | |
this.needsVar['stack'] = true; | |
this.source.push( | |
"var cond = stack[vm.sp--]; if (cond === vm.", condition, "Obj) {vm.pc = ", destination, "; "); | |
if (this.singleStep) this.source.push("if (vm.breakOutOfInterpreter) return; else "); | |
this.source.push("continue}\n", | |
"else if (cond !== vm.", !condition, "Obj) {vm.sp++; vm.pc = ", this.pc, "; vm.send(vm.specialObjects[25], 0, false); return}\n"); | |
this.needsLabel[this.pc] = true; // for coming back after #mustBeBoolean send | |
this.needsLabel[destination] = true; // obviously | |
if (destination > this.endPC) this.endPC = destination; | |
}, | |
generateQuickPrim: function(byte) { | |
if (this.debug) this.generateDebugCode("quick send #" + this.specialSelectors[(byte & 0x0F) + 16]); | |
this.generateLabel(); | |
switch (byte & 0x0F) { | |
case 0x0: // at: | |
this.needsVar['stack'] = true; | |
this.source.push( | |
"var a, b; if ((a=stack[vm.sp-1]).sqClass === vm.specialObjects[7] && a.pointers && typeof (b=stack[vm.sp]) === 'number' && b>0 && b<=a.pointers.length) {\n", | |
" stack[--vm.sp] = a.pointers[b-1];", | |
"} else { var c = vm.primHandler.objectAt(true,true,false); if (vm.primHandler.success) stack[--vm.sp] = c; else {\n", | |
" vm.pc = ", this.pc, "; vm.sendSpecial(16); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return; }}\n"); | |
this.needsLabel[this.pc] = true; | |
return; | |
case 0x1: // at:put: | |
this.needsVar['stack'] = true; | |
this.source.push( | |
"var a, b; if ((a=stack[vm.sp-2]).sqClass === vm.specialObjects[7] && a.pointers && typeof (b=stack[vm.sp-1]) === 'number' && b>0 && b<=a.pointers.length) {\n", | |
" var c = stack[vm.sp]; stack[vm.sp-=2] = a.pointers[b-1] = c; a.dirty = true;", | |
"} else { vm.primHandler.objectAtPut(true,true,false); if (vm.primHandler.success) stack[vm.sp-=2] = c; else {\n", | |
" vm.pc = ", this.pc, "; vm.sendSpecial(17); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return; }}\n"); | |
this.needsLabel[this.pc] = true; | |
return; | |
case 0x2: // size | |
this.needsVar['stack'] = true; | |
this.source.push( | |
"if (stack[vm.sp].sqClass === vm.specialObjects[7]) stack[vm.sp] = stack[vm.sp].pointersSize();\n", // Array | |
"else if (stack[vm.sp].sqClass === vm.specialObjects[6]) stack[vm.sp] = stack[vm.sp].bytesSize();\n", // ByteString | |
"else { vm.pc = ", this.pc, "; vm.sendSpecial(18); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return; }\n"); | |
this.needsLabel[this.pc] = true; | |
return; | |
//case 0x3: return false; // next | |
//case 0x4: return false; // nextPut: | |
//case 0x5: return false; // atEnd | |
case 0x6: // == | |
this.needsVar['stack'] = true; | |
this.source.push("var cond = stack[vm.sp-1] === stack[vm.sp];\nstack[--vm.sp] = cond ? vm.trueObj : vm.falseObj;\n"); | |
return; | |
case 0x7: // class | |
this.needsVar['stack'] = true; | |
this.source.push("stack[vm.sp] = typeof stack[vm.sp] === 'number' ? vm.specialObjects[5] : stack[vm.sp].sqClass;\n"); | |
return; | |
case 0x8: // blockCopy: | |
this.needsVar['rcvr'] = true; | |
this.source.push( | |
"vm.pc = ", this.pc, "; if (!vm.primHandler.quickSendOther(rcvr, ", (byte & 0x0F), ")) ", | |
"{vm.sendSpecial(", ((byte & 0x0F) + 16), "); return}\n"); | |
this.needsLabel[this.pc] = true; // for send | |
this.needsLabel[this.pc + 2] = true; // for start of block | |
return; | |
case 0x9: // value | |
case 0xA: // value: | |
case 0xB: // do: | |
this.needsVar['rcvr'] = true; | |
this.source.push( | |
"vm.pc = ", this.pc, "; if (!vm.primHandler.quickSendOther(rcvr, ", (byte & 0x0F), ")) vm.sendSpecial(", ((byte & 0x0F) + 16), "); return;\n"); | |
this.needsLabel[this.pc] = true; | |
return; | |
//case 0xC: return false; // new | |
//case 0xD: return false; // new: | |
//case 0xE: return false; // x | |
//case 0xF: return false; // y | |
} | |
// generic version for the bytecodes not yet handled above | |
this.needsVar['rcvr'] = true; | |
this.needsVar['context'] = true; | |
this.source.push( | |
"vm.pc = ", this.pc, "; if (!vm.primHandler.quickSendOther(rcvr, ", (byte & 0x0F), "))", | |
" vm.sendSpecial(", ((byte & 0x0F) + 16), ");\n", | |
"if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return;\n"); | |
this.needsBreak = false; // already checked | |
// if falling back to a full send we need a label for coming back | |
this.needsLabel[this.pc] = true; | |
}, | |
generateNumericOp: function(byte) { | |
if (this.debug) this.generateDebugCode("quick send #" + this.specialSelectors[byte & 0x0F]); | |
this.generateLabel(); | |
// if the op cannot be executed here, do a full send and return to main loop | |
// we need a label for coming back | |
this.needsLabel[this.pc] = true; | |
switch (byte & 0x0F) { | |
case 0x0: // PLUS + | |
this.needsVar['stack'] = true; | |
this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n", | |
"if (typeof a === 'number' && typeof b === 'number') {\n", | |
" stack[--vm.sp] = vm.primHandler.signed32BitIntegerFor(a + b);\n", | |
"} else { vm.pc = ", this.pc, "; vm.sendSpecial(0); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n"); | |
return; | |
case 0x1: // MINUS - | |
this.needsVar['stack'] = true; | |
this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n", | |
"if (typeof a === 'number' && typeof b === 'number') {\n", | |
" stack[--vm.sp] = vm.primHandler.signed32BitIntegerFor(a - b);\n", | |
"} else { vm.pc = ", this.pc, "; vm.sendSpecial(1); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n"); | |
return; | |
case 0x2: // LESS < | |
this.needsVar['stack'] = true; | |
this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n", | |
"if (typeof a === 'number' && typeof b === 'number') {\n", | |
" stack[--vm.sp] = a < b ? vm.trueObj : vm.falseObj;\n", | |
"} else { vm.pc = ", this.pc, "; vm.sendSpecial(2); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n"); | |
return; | |
case 0x3: // GRTR > | |
this.needsVar['stack'] = true; | |
this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n", | |
"if (typeof a === 'number' && typeof b === 'number') {\n", | |
" stack[--vm.sp] = a > b ? vm.trueObj : vm.falseObj;\n", | |
"} else { vm.pc = ", this.pc, "; vm.sendSpecial(3); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n"); | |
return; | |
case 0x4: // LEQ <= | |
this.needsVar['stack'] = true; | |
this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n", | |
"if (typeof a === 'number' && typeof b === 'number') {\n", | |
" stack[--vm.sp] = a <= b ? vm.trueObj : vm.falseObj;\n", | |
"} else { vm.pc = ", this.pc, "; vm.sendSpecial(4); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n"); | |
return; | |
case 0x5: // GEQ >= | |
this.needsVar['stack'] = true; | |
this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n", | |
"if (typeof a === 'number' && typeof b === 'number') {\n", | |
" stack[--vm.sp] = a >= b ? vm.trueObj : vm.falseObj;\n", | |
"} else { vm.pc = ", this.pc, "; vm.sendSpecial(5); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n"); | |
return; | |
case 0x6: // EQU = | |
this.needsVar['stack'] = true; | |
this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n", | |
"if (typeof a === 'number' && typeof b === 'number') {\n", | |
" stack[--vm.sp] = a === b ? vm.trueObj : vm.falseObj;\n", | |
"} else if (a === b && a.float === a.float) {\n", // NaN check | |
" stack[--vm.sp] = vm.trueObj;\n", | |
"} else { vm.pc = ", this.pc, "; vm.sendSpecial(6); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n"); | |
return; | |
case 0x7: // NEQ ~= | |
this.needsVar['stack'] = true; | |
this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n", | |
"if (typeof a === 'number' && typeof b === 'number') {\n", | |
" stack[--vm.sp] = a !== b ? vm.trueObj : vm.falseObj;\n", | |
"} else if (a === b && a.float === a.float) {\n", // NaN check | |
" stack[--vm.sp] = vm.falseObj;\n", | |
"} else { vm.pc = ", this.pc, "; vm.sendSpecial(7); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n"); | |
return; | |
case 0x8: // TIMES * | |
this.source.push("vm.success = true; vm.resultIsFloat = false; if(!vm.pop2AndPushNumResult(vm.stackIntOrFloat(1) * vm.stackIntOrFloat(0))) { vm.pc = ", this.pc, "; vm.sendSpecial(8); return}\n"); | |
return; | |
case 0x9: // DIV / | |
this.source.push("vm.success = true; if(!vm.pop2AndPushIntResult(vm.quickDivide(vm.stackInteger(1),vm.stackInteger(0)))) { vm.pc = ", this.pc, "; vm.sendSpecial(9); return}\n"); | |
return; | |
case 0xA: // MOD \ | |
this.source.push("vm.success = true; if(!vm.pop2AndPushIntResult(vm.mod(vm.stackInteger(1),vm.stackInteger(0)))) { vm.pc = ", this.pc, "; vm.sendSpecial(10); return}\n"); | |
return; | |
case 0xB: // MakePt int@int | |
this.source.push("vm.success = true; if(!vm.primHandler.primitiveMakePoint(1, true)) { vm.pc = ", this.pc, "; vm.sendSpecial(11); return}\n"); | |
return; | |
case 0xC: // bitShift: | |
this.source.push("vm.success = true; if(!vm.pop2AndPushIntResult(vm.safeShift(vm.stackInteger(1),vm.stackInteger(0)))) { vm.pc = ", this.pc, "; vm.sendSpecial(12); return}\n"); | |
return; | |
case 0xD: // Divide // | |
this.source.push("vm.success = true; if(!vm.pop2AndPushIntResult(vm.div(vm.stackInteger(1),vm.stackInteger(0)))) { vm.pc = ", this.pc, "; vm.sendSpecial(13); return}\n"); | |
return; | |
case 0xE: // bitAnd: | |
this.source.push("vm.success = true; if(!vm.pop2AndPushIntResult(vm.stackInteger(1) & vm.stackInteger(0))) { vm.pc = ", this.pc, "; vm.sendSpecial(14); return}\n"); | |
return; | |
case 0xF: // bitOr: | |
this.source.push("vm.success = true; if(!vm.pop2AndPushIntResult(vm.stackInteger(1) | vm.stackInteger(0))) { vm.pc = ", this.pc, "; vm.sendSpecial(15); return}\n"); | |
return; | |
} | |
}, | |
generateSend: function(prefix, num, suffix, numArgs, superSend) { | |
if (this.debug) this.generateDebugCode( | |
(superSend === "directed" ? "directed super send " : superSend ? "super send " : "send ") | |
+ (prefix === "lit[" ? this.method.pointers[num].bytesAsString() : "...")); | |
this.generateLabel(); | |
this.needsVar[prefix] = true; | |
this.needsVar['context'] = true; | |
// set pc, activate new method, and return to main loop | |
// unless the method was a successfull primitive call (no context change) | |
this.source.push("vm.pc = ", this.pc); | |
if (superSend === "directed") { | |
this.source.push("; vm.sendSuperDirected(", prefix, num, suffix, ", ", numArgs, "); "); | |
} else { | |
this.source.push("; vm.send(", prefix, num, suffix, ", ", numArgs, ", ", superSend, "); "); | |
} | |
this.source.push("if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return;\n"); | |
this.needsBreak = false; // already checked | |
// need a label for coming back after send | |
this.needsLabel[this.pc] = true; | |
}, | |
generateClosureTemps: function(count, popValues) { | |
if (this.debug) this.generateDebugCode("closure temps"); | |
this.generateLabel(); | |
this.needsVar['stack'] = true; | |
this.source.push("var array = vm.instantiateClass(vm.specialObjects[7], ", count, ");\n"); | |
if (popValues) { | |
for (var i = 0; i < count; i++) | |
this.source.push("array.pointers[", i, "] = stack[vm.sp - ", count - i - 1, "];\n"); | |
this.source.push("stack[vm.sp -= ", count - 1, "] = array;\n"); | |
} else { | |
this.source.push("stack[++vm.sp] = array;\n"); | |
} | |
}, | |
generateClosureCopy: function(numArgs, numCopied, blockSize) { | |
var from = this.pc, | |
to = from + blockSize; | |
if (this.debug) this.generateDebugCode("push closure(" + from + "-" + (to-1) + "): " + numCopied + " copied, " + numArgs + " args"); | |
this.generateLabel(); | |
this.needsVar['stack'] = true; | |
this.source.push( | |
"var closure = vm.instantiateClass(vm.specialObjects[36], ", numCopied, ");\n", | |
"closure.pointers[0] = context; vm.reclaimableContextCount = 0;\n", | |
"closure.pointers[1] = ", from + this.method.pointers.length * 4 + 1, ";\n", // encodeSqueakPC | |
"closure.pointers[2] = ", numArgs, ";\n"); | |
if (numCopied > 0) { | |
for (var i = 0; i < numCopied; i++) | |
this.source.push("closure.pointers[", i + 3, "] = stack[vm.sp - ", numCopied - i - 1,"];\n"); | |
this.source.push("stack[vm.sp -= ", numCopied - 1,"] = closure;\n"); | |
} else { | |
this.source.push("stack[++vm.sp] = closure;\n"); | |
} | |
this.source.push("vm.pc = ", to, ";\n"); | |
if (this.singleStep) this.source.push("if (vm.breakOutOfInterpreter) return;\n"); | |
this.source.push("continue;\n"); | |
this.needsBreak = false; // already checked | |
this.needsLabel[from] = true; // initial pc when activated | |
this.needsLabel[to] = true; // for jump over closure | |
if (to > this.endPC) this.endPC = to; | |
}, | |
generatePushFullClosure: function(index, b3) { | |
if (this.debug) this.generateDebugCode("push full closure " + (index + 1)); | |
this.generateLabel(); | |
this.needsVar['lit['] = true; | |
this.needsVar['rcvr'] = true; | |
this.needsVar['stack'] = true; | |
var numCopied = b3 & 63; | |
var outer; | |
if ((b3 >> 6 & 1) === 1) { | |
outer = "vm.nilObj"; | |
} else { | |
outer = "context"; | |
} | |
if ((b3 >> 7 & 1) === 1) { | |
throw Error("on-stack receiver not yet supported"); | |
} | |
this.source.push("var closure = vm.newFullClosure(", outer, ", ", numCopied, ", lit[", 1 + index, "]);\n"); | |
this.source.push("closure.pointers[", Squeak.ClosureFull_receiver, "] = rcvr;\n"); | |
if (outer === "context") this.source.push("vm.reclaimableContextCount = 0;\n"); | |
if (numCopied > 0) { | |
for (var i = 0; i < numCopied; i++) | |
this.source.push("closure.pointers[", i + Squeak.ClosureFull_firstCopiedValue, "] = stack[vm.sp - ", numCopied - i - 1,"];\n"); | |
this.source.push("stack[vm.sp -= ", numCopied - 1,"] = closure;\n"); | |
} else { | |
this.source.push("stack[++vm.sp] = closure;\n"); | |
} | |
}, | |
generateCallPrimitive: function(index, extendedStoreBytecode) { | |
if (this.debug) this.generateDebugCode("call primitive " + index); | |
this.generateLabel(); | |
if (this.method.bytes[this.pc] === extendedStoreBytecode) { | |
this.needsVar['stack'] = true; | |
this.source.push("if (vm.primFailCode) {stack[vm.sp] = vm.getErrorObjectFromPrimFailCode(); vm.primFailCode = 0;}\n"); | |
} | |
}, | |
generateDirty: function(target, arg, suffix) { | |
switch(target) { | |
case "inst[": this.source.push("rcvr.dirty = true;\n"); break; | |
case "lit[": this.source.push(target, arg, "].dirty = true;\n"); break; | |
case "temp[": if (suffix !== "]") this.source.push(target, arg, "].dirty = true;\n"); break; | |
default: | |
throw Error("unexpected target " + target); | |
} | |
}, | |
generateLabel: function() { | |
// remember label position for deleteUnneededLabels() | |
if (this.prevPC) { | |
this.sourceLabels[this.prevPC] = this.source.length; | |
this.source.push("case ", this.prevPC, ":\n"); // must match deleteUnneededLabels | |
} | |
this.prevPC = this.pc; | |
}, | |
generateDebugCode: function(command, what, arg1, suffix1, arg2, suffix2) { | |
// single-step for previous instructiuon | |
if (this.needsBreak) { | |
this.source.push("if (vm.breakOutOfInterpreter) {vm.pc = ", this.prevPC, "; return}\n"); | |
this.needsLabel[this.prevPC] = true; | |
} | |
// comment for this instruction | |
var bytecodes = []; | |
for (var i = this.prevPC; i < this.pc; i++) | |
bytecodes.push((this.method.bytes[i] + 0x100).toString(16).slice(-2).toUpperCase()); | |
this.source.push("// ", this.prevPC, " <", bytecodes.join(" "), "> ", command); | |
// append argument to comment | |
if (what !== undefined) { | |
this.source.push(" "); | |
switch (what) { | |
case 'vm.nilObj': this.source.push('nil'); break; | |
case 'vm.trueObj': this.source.push('true'); break; | |
case 'vm.falseObj': this.source.push('false'); break; | |
case 'rcvr': this.source.push('self'); break; | |
case 'stack[vm.sp]': this.source.push('top of stack'); break; | |
case 'inst[': | |
if (!this.instVarNames) this.source.push('inst var ', arg1); | |
else this.source.push(this.instVarNames[arg1]); | |
break; | |
case 'temp[': | |
this.source.push('tmp', arg1 - 6); | |
if (suffix1 !== ']') this.source.push('[', arg2, ']'); | |
break; | |
case 'lit[': | |
var lit = this.method.pointers[arg1]; | |
if (suffix1 === ']') this.source.push(lit); | |
else this.source.push(lit.pointers[0].bytesAsString()); | |
break; | |
default: | |
this.source.push(what); | |
} | |
} | |
this.source.push("\n"); | |
// enable single-step for next instruction | |
this.needsBreak = this.singleStep; | |
}, | |
generateInstruction: function(comment, instr) { | |
if (this.debug) this.generateDebugCode(comment); | |
this.generateLabel(); | |
this.source.push(instr, ";\n"); | |
}, | |
deleteUnneededLabels: function() { | |
// switch statement is more efficient with fewer labels | |
var hasAnyLabel = false; | |
for (var i in this.sourceLabels) | |
if (this.needsLabel[i]) | |
hasAnyLabel = true; | |
else for (var j = 0; j < 3; j++) | |
this.source[this.sourceLabels[i] + j] = ""; | |
if (!hasAnyLabel) { | |
this.source[this.sourcePos['loop-start']] = ""; | |
this.source[this.sourcePos['loop-end']] = ""; | |
} | |
}, | |
deleteUnneededVariables: function() { | |
if (this.needsVar['stack']) this.needsVar['context'] = true; | |
if (this.needsVar['inst[']) this.needsVar['rcvr'] = true; | |
for (var i = 0; i < this.allVars.length; i++) { | |
var v = this.allVars[i]; | |
if (!this.needsVar[v]) | |
this.source[this.sourcePos[v]] = ""; | |
} | |
}, | |
}); | |