Spaces:
Running
Running
; | |
/* | |
* Copyright (c) 2013-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.Interpreter', | |
'initialization', { | |
initialize: function(image, display, options) { | |
console.log('squeak: initializing interpreter ' + Squeak.vmVersion + ' (' + Squeak.vmDate + ')'); | |
this.Squeak = Squeak; // store locally to avoid dynamic lookup in Lively | |
this.image = image; | |
this.image.vm = this; | |
this.options = options || {}; | |
this.primHandler = new Squeak.Primitives(this, display); | |
this.loadImageState(); | |
this.initVMState(); | |
this.loadInitialContext(); | |
this.hackImage(); | |
this.initCompiler(); | |
console.log('squeak: ready'); | |
}, | |
loadImageState: function() { | |
this.specialObjects = this.image.specialObjectsArray.pointers; | |
this.specialSelectors = this.specialObjects[Squeak.splOb_SpecialSelectors].pointers; | |
this.nilObj = this.specialObjects[Squeak.splOb_NilObject]; | |
this.falseObj = this.specialObjects[Squeak.splOb_FalseObject]; | |
this.trueObj = this.specialObjects[Squeak.splOb_TrueObject]; | |
this.hasClosures = this.image.hasClosures; | |
this.getGlobals = this.globalsGetter(); | |
// hack for old image that does not support Unix files | |
if (!this.hasClosures && !this.findMethod("UnixFileDirectory class>>pathNameDelimiter")) | |
this.primHandler.emulateMac = true; | |
// pre-release image has inverted colors | |
if (this.image.version == 6501) | |
this.primHandler.reverseDisplay = true; | |
}, | |
initVMState: function() { | |
this.byteCodeCount = 0; | |
this.sendCount = 0; | |
this.interruptCheckCounter = 0; | |
this.interruptCheckCounterFeedBackReset = 1000; | |
this.interruptChecksEveryNms = 3; | |
this.lowSpaceThreshold = 1000000; | |
this.signalLowSpace = false; | |
this.nextPollTick = 0; | |
this.nextWakeupTick = 0; | |
this.lastTick = 0; | |
this.interruptKeycode = 2094; //"cmd-." | |
this.interruptPending = false; | |
this.pendingFinalizationSignals = 0; | |
this.freeContexts = this.nilObj; | |
this.freeLargeContexts = this.nilObj; | |
this.reclaimableContextCount = 0; | |
this.nRecycledContexts = 0; | |
this.nAllocatedContexts = 0; | |
this.methodCacheSize = 1024; | |
this.methodCacheMask = this.methodCacheSize - 1; | |
this.methodCacheRandomish = 0; | |
this.methodCache = []; | |
for (var i = 0; i < this.methodCacheSize; i++) | |
this.methodCache[i] = {lkupClass: null, selector: null, method: null, primIndex: 0, argCount: 0, mClass: null}; | |
this.breakOutOfInterpreter = false; | |
this.breakOutTick = 0; | |
this.breakOnMethod = null; // method to break on | |
this.breakOnNewMethod = false; | |
this.breakOnMessageNotUnderstood = false; | |
this.breakOnContextChanged = false; | |
this.breakOnContextReturned = null; // context to break on | |
this.messages = {}; | |
this.startupTime = Date.now(); // base for millisecond clock | |
}, | |
loadInitialContext: function() { | |
var schedAssn = this.specialObjects[Squeak.splOb_SchedulerAssociation]; | |
var sched = schedAssn.pointers[Squeak.Assn_value]; | |
var proc = sched.pointers[Squeak.ProcSched_activeProcess]; | |
this.activeContext = proc.pointers[Squeak.Proc_suspendedContext]; | |
this.activeContext.dirty = true; | |
this.fetchContextRegisters(this.activeContext); | |
this.reclaimableContextCount = 0; | |
}, | |
globalsGetter: function() { | |
// Globals (more specifically the pointers we are interested in) might | |
// change during execution, because a Dictionary needs growing for example. | |
// Therefore answer a getter function to access the actual globals (pointers). | |
// This getter can be used, even if the Dictionary has grown (and thereby the | |
// underlying Array is replaced by a larger one), because it uses the reference | |
// to the 'outer' Dictionary instead of the pointers to the values. | |
var smalltalk = this.specialObjects[Squeak.splOb_SmalltalkDictionary], | |
smalltalkClass = smalltalk.sqClass.className(); | |
if (smalltalkClass === "Association") { | |
smalltalk = smalltalk.pointers[1]; | |
smalltalkClass = smalltalk.sqClass.className(); | |
} | |
if (smalltalkClass === "SystemDictionary") | |
return function() { return smalltalk.pointers[1].pointers; }; | |
if (smalltalkClass === "SmalltalkImage") { | |
var globals = smalltalk.pointers[0], | |
globalsClass = globals.sqClass.className(); | |
if (globalsClass === "SystemDictionary") | |
return function() { return globals.pointers[1].pointers; }; | |
if (globalsClass === "Environment") | |
return function() { return globals.pointers[2].pointers[1].pointers; }; | |
} | |
console.warn("cannot find global dict"); | |
return function() { return []; }; | |
}, | |
initCompiler: function() { | |
if (!Squeak.Compiler) | |
return console.warn("Squeak.Compiler not loaded, using interpreter only"); | |
// some JS environments disallow creating functions at runtime (e.g. FireFox OS apps) | |
try { | |
if (new Function("return 42")() !== 42) | |
return console.warn("function constructor not working, disabling JIT"); | |
} catch (e) { | |
return console.warn("disabling JIT: " + e); | |
} | |
// disable JIT on slow machines, which are likely memory-limited | |
var kObjPerSec = this.image.oldSpaceCount / (this.startupTime - this.image.startupTime); | |
if (kObjPerSec < 10) | |
return console.warn("Slow machine detected (loaded " + (kObjPerSec*1000|0) + " objects/sec), using interpreter only"); | |
// compiler might decide to not handle current image | |
try { | |
console.log("squeak: initializing JIT compiler"); | |
var compiler = new Squeak.Compiler(this); | |
if (compiler.compile) this.compiler = compiler; | |
} catch(e) { | |
console.warn("Compiler: " + e); | |
} | |
if (!this.compiler) { | |
console.warn("SqueakJS will be running in interpreter mode only (slow)"); | |
} | |
}, | |
hackImage: function() { | |
// hack methods to make work / speed up | |
var returnSelf = 256, | |
returnTrue = 257, | |
returnFalse = 258, | |
returnNil = 259, | |
sista = this.method.methodSignFlag(); | |
[ | |
// Etoys fallback for missing translation files is hugely inefficient. | |
// This speeds up opening a viewer by 10x (!) | |
// Remove when we added translation files. | |
//{method: "String>>translated", primitive: returnSelf, enabled: true}, | |
//{method: "String>>translatedInAllDomains", primitive: returnSelf, enabled: true}, | |
// 64 bit Squeak does not flush word size on snapshot | |
{method: "SmalltalkImage>>wordSize", literal: {index: 1, old: 8, hack: 4, skip: this.nilObj}, enabled: true}, | |
// Squeak 5.3 disable wizard by replacing #open send with pop | |
{method: "ReleaseBuilder class>>prepareEnvironment", bytecode: {pc: 28, old: 0xD8, hack: 0x87}, enabled: !sista & this.options.wizard===false}, | |
// Squeak 6.0 disable wizard by replacing #openWelcomeWorkspacesWith: send with pop | |
{method: "ReleaseBuilder class>>prepareEnvironment", bytecode: {closure: 9, pc: 5, old: 0x81, hack: 0xD8}, enabled: sista & this.options.wizard===false}, | |
// Squeak 6.0 disable welcome workspace by replacing #open send with pop | |
{method: "ReleaseBuilder class>>prepareEnvironment", bytecode: {closure: 9, pc: 2, old: 0x90, hack: 0xD8}, enabled: sista & this.options.welcome===false}, | |
// Squeak source file should use UTF8 not MacRoman (both V3 and Sista) | |
{method: "Latin1Environment class>>systemConverterClass", bytecode: {pc: 53, old: 0x45, hack: 0x49}, enabled: !this.image.isSpur}, | |
{method: "Latin1Environment class>>systemConverterClass", bytecode: {pc: 38, old: 0x16, hack: 0x13}, enabled: this.image.isSpur && sista}, | |
{method: "Latin1Environment class>>systemConverterClass", bytecode: {pc: 50, old: 0x44, hack: 0x48}, enabled: this.image.isSpur && !sista}, | |
// New FFI can't detect platform – pretend to be 32 bit intel | |
{method: "FFIPlatformDescription>>abi", literal: { index: 21, old_str: 'UNKNOWN_ABI', new_str: 'IA32'}, enabled: sista}, | |
].forEach(function(each) { | |
try { | |
var m = each.enabled && this.findMethod(each.method); | |
if (m) { | |
var prim = each.primitive, | |
byte = each.bytecode, | |
lit = each.literal, | |
hacked = true; | |
if (byte && byte.closure) m = m.pointers[byte.closure]; | |
if (prim) m.pointers[0] |= prim; | |
else if (byte && m.bytes[byte.pc] === byte.old) m.bytes[byte.pc] = byte.hack; | |
else if (byte && m.bytes[byte.pc] === byte.hack) hacked = false; // already there | |
else if (lit && lit.old_str && m.pointers[lit.index].bytesAsString() === lit.old_str) m.pointers[lit.index] = this.primHandler.makeStString(lit.new_str); | |
else if (lit && m.pointers[lit.index].pointers?.[1] === lit.skip) hacked = false; // not needed | |
else if (lit && m.pointers[lit.index].pointers?.[1] === lit.old) m.pointers[lit.index].pointers[1] = lit.hack; | |
else if (lit && m.pointers[lit.index].pointers?.[1] === lit.hack) hacked = false; // already there | |
else { hacked = false; console.warn("Not hacking " + each.method); } | |
if (hacked) console.warn("Hacking " + each.method); | |
} | |
} catch (error) { | |
console.error("Failed to hack " + each.method + " with error " + error); | |
} | |
}, this); | |
}, | |
}, | |
'interpreting', { | |
interpretOne: function(singleStep) { | |
if (this.method.compiled) { | |
if (singleStep) { | |
if (!this.compiler.enableSingleStepping(this.method)) { | |
this.method.compiled = null; | |
return this.interpretOne(singleStep); | |
} | |
this.breakNow(); | |
} | |
this.method.compiled(this); | |
return; | |
} | |
if (this.method.methodSignFlag()) { | |
return this.interpretOneSistaWithExtensions(singleStep, 0, 0); | |
} | |
var Squeak = this.Squeak; // avoid dynamic lookup of "Squeak" in Lively | |
var b, b2; | |
this.byteCodeCount++; | |
b = this.nextByte(); | |
switch (b) { /* The Main V3 Bytecode Dispatch Loop */ | |
// 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.push(this.receiver.pointers[b&0xF]); return; | |
// load temporary 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.push(this.homeContext.pointers[Squeak.Context_tempFrameStart+(b&0xF)]); return; | |
// loadLiteral | |
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.push(this.method.methodGetLiteral(b&0x1F)); return; | |
// loadLiteralIndirect | |
case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: | |
case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F: | |
case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: | |
case 0x58: case 0x59: case 0x5A: case 0x5B: case 0x5C: case 0x5D: case 0x5E: case 0x5F: | |
this.push((this.method.methodGetLiteral(b&0x1F)).pointers[Squeak.Assn_value]); return; | |
// storeAndPop rcvr, temp | |
case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: | |
this.receiver.dirty = true; | |
this.receiver.pointers[b&7] = this.pop(); return; | |
case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: | |
this.homeContext.pointers[Squeak.Context_tempFrameStart+(b&7)] = this.pop(); return; | |
// Quick push | |
case 0x70: this.push(this.receiver); return; | |
case 0x71: this.push(this.trueObj); return; | |
case 0x72: this.push(this.falseObj); return; | |
case 0x73: this.push(this.nilObj); return; | |
case 0x74: this.push(-1); return; | |
case 0x75: this.push(0); return; | |
case 0x76: this.push(1); return; | |
case 0x77: this.push(2); return; | |
// Quick return | |
case 0x78: this.doReturn(this.receiver); return; | |
case 0x79: this.doReturn(this.trueObj); return; | |
case 0x7A: this.doReturn(this.falseObj); return; | |
case 0x7B: this.doReturn(this.nilObj); return; | |
case 0x7C: this.doReturn(this.pop()); return; | |
case 0x7D: this.doReturn(this.pop(), this.activeContext.pointers[Squeak.BlockContext_caller]); return; // blockReturn | |
case 0x7E: this.nono(); return; | |
case 0x7F: this.nono(); return; | |
// Sundry | |
case 0x80: this.extendedPush(this.nextByte()); return; | |
case 0x81: this.extendedStore(this.nextByte()); return; | |
case 0x82: this.extendedStorePop(this.nextByte()); return; | |
// singleExtendedSend | |
case 0x83: b2 = this.nextByte(); this.send(this.method.methodGetSelector(b2&31), b2>>5, false); return; | |
case 0x84: this.doubleExtendedDoAnything(this.nextByte()); return; | |
// singleExtendedSendToSuper | |
case 0x85: b2= this.nextByte(); this.send(this.method.methodGetSelector(b2&31), b2>>5, true); return; | |
// secondExtendedSend | |
case 0x86: b2= this.nextByte(); this.send(this.method.methodGetSelector(b2&63), b2>>6, false); return; | |
case 0x87: this.pop(); return; // pop | |
case 0x88: this.push(this.top()); return; // dup | |
// thisContext | |
case 0x89: this.push(this.exportThisContext()); return; | |
// Closures | |
case 0x8A: this.pushNewArray(this.nextByte()); // create new temp vector | |
return; | |
case 0x8B: this.callPrimBytecode(0x81); | |
return; | |
case 0x8C: b2 = this.nextByte(); // remote push from temp vector | |
this.push(this.homeContext.pointers[Squeak.Context_tempFrameStart+this.nextByte()].pointers[b2]); | |
return; | |
case 0x8D: b2 = this.nextByte(); // remote store into temp vector | |
var vec = this.homeContext.pointers[Squeak.Context_tempFrameStart+this.nextByte()]; | |
vec.pointers[b2] = this.top(); | |
vec.dirty = true; | |
return; | |
case 0x8E: b2 = this.nextByte(); // remote store and pop into temp vector | |
var vec = this.homeContext.pointers[Squeak.Context_tempFrameStart+this.nextByte()]; | |
vec.pointers[b2] = this.pop(); | |
vec.dirty = true; | |
return; | |
case 0x8F: this.pushClosureCopy(); return; | |
// Short jmp | |
case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: | |
this.pc += (b&7)+1; return; | |
// Short conditional jump on false | |
case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F: | |
this.jumpIfFalse((b&7)+1); return; | |
// Long jump, forward and back | |
case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: | |
b2 = this.nextByte(); | |
this.pc += (((b&7)-4)*256 + b2); | |
if ((b&7)<4) // check for process switch on backward jumps (loops) | |
if (this.interruptCheckCounter-- <= 0) this.checkForInterrupts(); | |
return; | |
// Long conditional jump on true | |
case 0xA8: case 0xA9: case 0xAA: case 0xAB: | |
this.jumpIfTrue((b&3)*256 + this.nextByte()); return; | |
// Long conditional jump on false | |
case 0xAC: case 0xAD: case 0xAE: case 0xAF: | |
this.jumpIfFalse((b&3)*256 + this.nextByte()); return; | |
// Arithmetic Ops... + - < > <= >= = ~= * / @ lshift: lxor: land: lor: | |
case 0xB0: this.success = true; this.resultIsFloat = false; | |
if (!this.pop2AndPushNumResult(this.stackIntOrFloat(1) + this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // PLUS + | |
case 0xB1: this.success = true; this.resultIsFloat = false; | |
if (!this.pop2AndPushNumResult(this.stackIntOrFloat(1) - this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // MINUS - | |
case 0xB2: this.success = true; | |
if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) < this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // LESS < | |
case 0xB3: this.success = true; | |
if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) > this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // GRTR > | |
case 0xB4: this.success = true; | |
if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) <= this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // LEQ <= | |
case 0xB5: this.success = true; | |
if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) >= this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // GEQ >= | |
case 0xB6: this.success = true; | |
if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) === this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // EQU = | |
case 0xB7: this.success = true; | |
if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) !== this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // NEQ ~= | |
case 0xB8: this.success = true; this.resultIsFloat = false; | |
if (!this.pop2AndPushNumResult(this.stackIntOrFloat(1) * this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // TIMES * | |
case 0xB9: this.success = true; | |
if (!this.pop2AndPushIntResult(this.quickDivide(this.stackInteger(1),this.stackInteger(0)))) this.sendSpecial(b&0xF); return; // Divide / | |
case 0xBA: this.success = true; | |
if (!this.pop2AndPushIntResult(this.mod(this.stackInteger(1),this.stackInteger(0)))) this.sendSpecial(b&0xF); return; // MOD \ | |
case 0xBB: this.success = true; | |
if (!this.primHandler.primitiveMakePoint(1, true)) this.sendSpecial(b&0xF); return; // MakePt int@int | |
case 0xBC: this.success = true; | |
if (!this.pop2AndPushIntResult(this.safeShift(this.stackInteger(1),this.stackInteger(0)))) this.sendSpecial(b&0xF); return; // bitShift: | |
case 0xBD: this.success = true; | |
if (!this.pop2AndPushIntResult(this.div(this.stackInteger(1),this.stackInteger(0)))) this.sendSpecial(b&0xF); return; // Divide // | |
case 0xBE: this.success = true; | |
if (!this.pop2AndPushIntResult(this.stackInteger(1) & this.stackInteger(0))) this.sendSpecial(b&0xF); return; // bitAnd: | |
case 0xBF: this.success = true; | |
if (!this.pop2AndPushIntResult(this.stackInteger(1) | this.stackInteger(0))) this.sendSpecial(b&0xF); return; // bitOr: | |
// at:, at:put:, size, next, nextPut:, ... | |
case 0xC0: case 0xC1: case 0xC2: case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7: | |
case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: | |
if (!this.primHandler.quickSendOther(this.receiver, b&0xF)) | |
this.sendSpecial((b&0xF)+16); return; | |
// Send Literal Selector with 0, 1, and 2 args | |
case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD5: case 0xD6: case 0xD7: | |
case 0xD8: case 0xD9: case 0xDA: case 0xDB: case 0xDC: case 0xDD: case 0xDE: case 0xDF: | |
this.send(this.method.methodGetSelector(b&0xF), 0, false); return; | |
case 0xE0: case 0xE1: case 0xE2: case 0xE3: case 0xE4: case 0xE5: case 0xE6: case 0xE7: | |
case 0xE8: case 0xE9: case 0xEA: case 0xEB: case 0xEC: case 0xED: case 0xEE: case 0xEF: | |
this.send(this.method.methodGetSelector(b&0xF), 1, false); return; | |
case 0xF0: case 0xF1: case 0xF2: case 0xF3: case 0xF4: case 0xF5: case 0xF6: case 0xF7: | |
case 0xF8: case 0xF9: case 0xFA: case 0xFB: case 0xFC: case 0xFD: case 0xFE: case 0xFF: | |
this.send(this.method.methodGetSelector(b&0xF), 2, false); return; | |
} | |
throw Error("not a bytecode: " + b); | |
}, | |
interpretOneSistaWithExtensions: function(singleStep, extA, extB) { | |
var Squeak = this.Squeak; // avoid dynamic lookup of "Squeak" in Lively | |
var b, b2; | |
this.byteCodeCount++; | |
b = this.nextByte(); | |
switch (b) { /* The Main Sista Bytecode Dispatch Loop */ | |
// 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.push(this.receiver.pointers[b&0xF]); return; | |
// 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.push((this.method.methodGetLiteral(b&0xF)).pointers[Squeak.Assn_value]); return; | |
// 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.push(this.method.methodGetLiteral(b&0x1F)); return; | |
// load temporary variable | |
case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: | |
this.push(this.homeContext.pointers[Squeak.Context_tempFrameStart+(b&0x7)]); return; | |
case 0x48: case 0x49: case 0x4A: case 0x4B: | |
this.push(this.homeContext.pointers[Squeak.Context_tempFrameStart+(b&0x3)+8]); return; | |
case 0x4C: this.push(this.receiver); return; | |
case 0x4D: this.push(this.trueObj); return; | |
case 0x4E: this.push(this.falseObj); return; | |
case 0x4F: this.push(this.nilObj); return; | |
case 0x50: this.push(0); return; | |
case 0x51: this.push(1); return; | |
case 0x52: | |
if (extB == 0) { | |
this.push(this.exportThisContext()); return; | |
} else { | |
this.nono(); return; | |
} | |
case 0x53: this.push(this.top()); return; | |
case 0x54: case 0x55: case 0x56: case 0x57: this.nono(); return; // unused | |
case 0x58: this.doReturn(this.receiver); return; | |
case 0x59: this.doReturn(this.trueObj); return; | |
case 0x5A: this.doReturn(this.falseObj); return; | |
case 0x5B: this.doReturn(this.nilObj); return; | |
case 0x5C: this.doReturn(this.pop()); return; | |
case 0x5D: this.doReturn(this.nilObj, this.activeContext.pointers[Squeak.BlockContext_caller]); return; // blockReturn nil | |
case 0x5E: | |
if (extA == 0) { | |
this.doReturn(this.pop(), this.activeContext.pointers[Squeak.BlockContext_caller]); return; // blockReturn | |
} else { | |
this.nono(); return; | |
} | |
case 0x5F: | |
return; // nop | |
// Arithmetic Ops... + - < > <= >= = ~= * / @ lshift: lxor: land: lor: | |
case 0x60: this.success = true; this.resultIsFloat = false; | |
if (!this.pop2AndPushNumResult(this.stackIntOrFloat(1) + this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // PLUS + | |
case 0x61: this.success = true; this.resultIsFloat = false; | |
if (!this.pop2AndPushNumResult(this.stackIntOrFloat(1) - this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // MINUS - | |
case 0x62: this.success = true; | |
if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) < this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // LESS < | |
case 0x63: this.success = true; | |
if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) > this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // GRTR > | |
case 0x64: this.success = true; | |
if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) <= this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // LEQ <= | |
case 0x65: this.success = true; | |
if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) >= this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // GEQ >= | |
case 0x66: this.success = true; | |
if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) === this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // EQU = | |
case 0x67: this.success = true; | |
if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) !== this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // NEQ ~= | |
case 0x68: this.success = true; this.resultIsFloat = false; | |
if (!this.pop2AndPushNumResult(this.stackIntOrFloat(1) * this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // TIMES * | |
case 0x69: this.success = true; | |
if (!this.pop2AndPushIntResult(this.quickDivide(this.stackInteger(1),this.stackInteger(0)))) this.sendSpecial(b&0xF); return; // Divide / | |
case 0x6A: this.success = true; | |
if (!this.pop2AndPushIntResult(this.mod(this.stackInteger(1),this.stackInteger(0)))) this.sendSpecial(b&0xF); return; // MOD \ | |
case 0x6B: this.success = true; | |
if (!this.primHandler.primitiveMakePoint(1, true)) this.sendSpecial(b&0xF); return; // MakePt int@int | |
case 0x6C: this.success = true; | |
if (!this.pop2AndPushIntResult(this.safeShift(this.stackInteger(1),this.stackInteger(0)))) this.sendSpecial(b&0xF); return; // bitShift: | |
case 0x6D: this.success = true; | |
if (!this.pop2AndPushIntResult(this.div(this.stackInteger(1),this.stackInteger(0)))) this.sendSpecial(b&0xF); return; // Divide // | |
case 0x6E: this.success = true; | |
if (!this.pop2AndPushIntResult(this.stackInteger(1) & this.stackInteger(0))) this.sendSpecial(b&0xF); return; // bitAnd: | |
case 0x6F: this.success = true; | |
if (!this.pop2AndPushIntResult(this.stackInteger(1) | this.stackInteger(0))) this.sendSpecial(b&0xF); return; // bitOr: | |
// at:, at:put:, size, next, nextPut:, ... | |
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: | |
if (!this.primHandler.quickSendOther(this.receiver, b&0xF)) | |
this.sendSpecial((b&0xF)+16); return; | |
// Send Literal Selector with 0, 1, and 2 args | |
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.send(this.method.methodGetSelector(b&0xF), 0, false); return; | |
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.send(this.method.methodGetSelector(b&0xF), 1, false); return; | |
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.send(this.method.methodGetSelector(b&0xF), 2, false); return; | |
// Short jmp | |
case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: | |
this.pc += (b&7)+1; return; | |
// Short conditional jump on true | |
case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: | |
this.jumpIfTrue((b&7)+1); return; | |
// Short conditional jump on false | |
case 0xC0: case 0xC1: case 0xC2: case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7: | |
this.jumpIfFalse((b&7)+1); return; | |
// storeAndPop rcvr, temp | |
case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: | |
this.receiver.dirty = true; | |
this.receiver.pointers[b&7] = this.pop(); return; | |
case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD5: case 0xD6: case 0xD7: | |
this.homeContext.pointers[Squeak.Context_tempFrameStart+(b&7)] = this.pop(); return; | |
case 0xD8: this.pop(); return; // pop | |
case 0xD9: this.nono(); return; // FIXME: Unconditional trap | |
case 0xDA: case 0xDB: case 0xDC: case 0xDD: case 0xDE: case 0xDF: | |
this.nono(); return; // unused | |
// 2 Byte Bytecodes | |
case 0xE0: | |
b2 = this.nextByte(); this.interpretOneSistaWithExtensions(singleStep, (extA << 8) + b2, extB); return; | |
case 0xE1: | |
b2 = this.nextByte(); this.interpretOneSistaWithExtensions(singleStep, extA, (extB << 8) + (b2 < 128 ? b2 : b2-256)); return; | |
case 0xE2: | |
b2 = this.nextByte(); this.push(this.receiver.pointers[b2 + (extA << 8)]); return; | |
case 0xE3: | |
b2 = this.nextByte(); this.push((this.method.methodGetLiteral(b2 + (extA << 8))).pointers[Squeak.Assn_value]); return; | |
case 0xE4: | |
b2 = this.nextByte(); this.push(this.method.methodGetLiteral(b2 + (extA << 8))); return; | |
case 0xE5: | |
b2 = this.nextByte(); this.push(this.homeContext.pointers[Squeak.Context_tempFrameStart+b2]); return; | |
case 0xE6: this.nono(); return; // unused | |
case 0xE7: this.pushNewArray(this.nextByte()); return; // create new temp vector | |
case 0xE8: b2 = this.nextByte(); this.push(b2 + (extB << 8)); return; // push SmallInteger | |
case 0xE9: b2 = this.nextByte(); this.push(this.image.getCharacter(b2 + (extB << 8))); return; // push Character | |
case 0xEA: | |
b2 = this.nextByte(); | |
this.send(this.method.methodGetSelector((b2 >> 3) + (extA << 5)), (b2 & 7) + (extB << 3), false); return; | |
case 0xEB: | |
b2 = this.nextByte(); | |
var literal = this.method.methodGetSelector((b2 >> 3) + (extA << 5)); | |
if (extB >= 64) { | |
this.sendSuperDirected(literal, (b2 & 7) + ((extB & 63) << 3)); return; | |
} else { | |
this.send(literal, (b2 & 7) + (extB << 3), true); return; | |
} | |
case 0xEC: this.nono(); return; // unused | |
case 0xED: // long jump, forward and back | |
var offset = this.nextByte() + (extB << 8); | |
this.pc += offset; | |
if (offset < 0) // check for process switch on backward jumps (loops) | |
if (this.interruptCheckCounter-- <= 0) this.checkForInterrupts(); | |
return; | |
case 0xEE: // long conditional jump on true | |
this.jumpIfTrue(this.nextByte() + (extB << 8)); return; | |
case 0xEF: // long conditional jump on false | |
this.jumpIfFalse(this.nextByte() + (extB << 8)); return; | |
case 0xF0: // pop into receiver | |
this.receiver.dirty = true; | |
this.receiver.pointers[this.nextByte() + (extA << 8)] = this.pop(); | |
return; | |
case 0xF1: // pop into literal | |
var assoc = this.method.methodGetLiteral(this.nextByte() + (extA << 8)); | |
assoc.dirty = true; | |
assoc.pointers[Squeak.Assn_value] = this.pop(); | |
return; | |
case 0xF2: // pop into temp | |
this.homeContext.pointers[Squeak.Context_tempFrameStart + this.nextByte()] = this.pop(); | |
return; | |
case 0xF3: // store into receiver | |
this.receiver.dirty = true; | |
this.receiver.pointers[this.nextByte() + (extA << 8)] = this.top(); | |
return; | |
case 0xF4: // store into literal | |
var assoc = this.method.methodGetLiteral(this.nextByte() + (extA << 8)); | |
assoc.dirty = true; | |
assoc.pointers[Squeak.Assn_value] = this.top(); | |
return; | |
case 0xF5: // store into temp | |
this.homeContext.pointers[Squeak.Context_tempFrameStart + this.nextByte()] = this.top(); | |
return; | |
case 0xF6: case 0xF7: this.nono(); return; // unused | |
// 3 Byte Bytecodes | |
case 0xF8: this.callPrimBytecode(0xF5); return; | |
case 0xF9: this.pushFullClosure(extA); return; | |
case 0xFA: this.pushClosureCopyExtended(extA, extB); return; | |
case 0xFB: b2 = this.nextByte(); // remote push from temp vector | |
this.push(this.homeContext.pointers[Squeak.Context_tempFrameStart+this.nextByte()].pointers[b2]); | |
return; | |
case 0xFC: b2 = this.nextByte(); // remote store into temp vector | |
var vec = this.homeContext.pointers[Squeak.Context_tempFrameStart+this.nextByte()]; | |
vec.pointers[b2] = this.top(); | |
vec.dirty = true; | |
return; | |
case 0xFD: b2 = this.nextByte(); // remote store and pop into temp vector | |
var vec = this.homeContext.pointers[Squeak.Context_tempFrameStart+this.nextByte()]; | |
vec.pointers[b2] = this.pop(); | |
vec.dirty = true; | |
return; | |
case 0xFE: case 0xFF: this.nono(); return; // unused | |
} | |
throw Error("not a bytecode: " + b); | |
}, | |
interpret: function(forMilliseconds, thenDo) { | |
// run for a couple milliseconds (but only until idle or break) | |
// answer milliseconds to sleep (until next timer wakeup) | |
// or 'break' if reached breakpoint | |
// call thenDo with that result when done | |
if (this.frozen) return 'frozen'; | |
this.isIdle = false; | |
this.breakOutOfInterpreter = false; | |
this.breakAfter(forMilliseconds || 500); | |
while (this.breakOutOfInterpreter === false) | |
if (this.method.compiled) { | |
this.method.compiled(this); | |
} else { | |
this.interpretOne(); | |
} | |
// this is to allow 'freezing' the interpreter and restarting it asynchronously. See freeze() | |
if (typeof this.breakOutOfInterpreter == "function") | |
return this.breakOutOfInterpreter(thenDo); | |
// normally, we answer regularly | |
var result = this.breakOutOfInterpreter == 'break' ? 'break' | |
: !this.isIdle ? 0 | |
: !this.nextWakeupTick ? 'sleep' // all processes waiting | |
: Math.max(1, this.nextWakeupTick - this.primHandler.millisecondClockValue()); | |
if (thenDo) thenDo(result); | |
return result; | |
}, | |
goIdle: function() { | |
// make sure we tend to pending delays | |
var hadTimer = this.nextWakeupTick !== 0; | |
this.forceInterruptCheck(); | |
this.checkForInterrupts(); | |
var hasTimer = this.nextWakeupTick !== 0; | |
// go idle unless a timer just expired | |
this.isIdle = hasTimer || !hadTimer; | |
this.breakOut(); | |
}, | |
freeze: function(frozenDo) { | |
// Stop the interpreter. Answer a function that can be | |
// called to continue interpreting. | |
// Optionally, frozenDo is called asynchronously when frozen | |
var continueFunc; | |
this.frozen = true; | |
this.breakOutOfInterpreter = function(thenDo) { | |
if (!thenDo) throw Error("need function to restart interpreter"); | |
continueFunc = thenDo; | |
return "frozen"; | |
}.bind(this); | |
var unfreeze = function() { | |
this.frozen = false; | |
if (!continueFunc) throw Error("no continue function"); | |
continueFunc(0); //continue without timeout | |
}.bind(this); | |
if (frozenDo) self.setTimeout(function(){frozenDo(unfreeze)}, 0); | |
return unfreeze; | |
}, | |
breakOut: function() { | |
this.breakOutOfInterpreter = this.breakOutOfInterpreter || true; // do not overwrite break string | |
}, | |
nextByte: function() { | |
return this.method.bytes[this.pc++]; | |
}, | |
nono: function() { | |
throw Error("Oh No!"); | |
}, | |
forceInterruptCheck: function() { | |
this.interruptCheckCounter = -1000; | |
}, | |
checkForInterrupts: function() { | |
//Check for interrupts at sends and backward jumps | |
var now = this.primHandler.millisecondClockValue(); | |
if (now < this.lastTick) { // millisecond clock wrapped | |
this.nextPollTick = now + (this.nextPollTick - this.lastTick); | |
this.breakOutTick = now + (this.breakOutTick - this.lastTick); | |
if (this.nextWakeupTick !== 0) | |
this.nextWakeupTick = now + (this.nextWakeupTick - this.lastTick); | |
} | |
//Feedback logic attempts to keep interrupt response around 3ms... | |
if (this.interruptCheckCounter > -100) { // only if not a forced check | |
if ((now - this.lastTick) < this.interruptChecksEveryNms) { //wrapping is not a concern | |
this.interruptCheckCounterFeedBackReset += 10; | |
} else { // do a thousand sends even if we are too slow for 3ms | |
if (this.interruptCheckCounterFeedBackReset <= 1000) | |
this.interruptCheckCounterFeedBackReset = 1000; | |
else | |
this.interruptCheckCounterFeedBackReset -= 12; | |
} | |
} | |
this.interruptCheckCounter = this.interruptCheckCounterFeedBackReset; //reset the interrupt check counter | |
this.lastTick = now; //used to detect wraparound of millisecond clock | |
if (this.signalLowSpace) { | |
this.signalLowSpace = false; // reset flag | |
var sema = this.specialObjects[Squeak.splOb_TheLowSpaceSemaphore]; | |
if (!sema.isNil) this.primHandler.synchronousSignal(sema); | |
} | |
// if (now >= nextPollTick) { | |
// ioProcessEvents(); //sets interruptPending if interrupt key pressed | |
// nextPollTick= now + 500; } //msecs to wait before next call to ioProcessEvents" | |
if (this.interruptPending) { | |
this.interruptPending = false; //reset interrupt flag | |
var sema = this.specialObjects[Squeak.splOb_TheInterruptSemaphore]; | |
if (!sema.isNil) this.primHandler.synchronousSignal(sema); | |
} | |
if ((this.nextWakeupTick !== 0) && (now >= this.nextWakeupTick)) { | |
this.nextWakeupTick = 0; //reset timer interrupt | |
var sema = this.specialObjects[Squeak.splOb_TheTimerSemaphore]; | |
if (!sema.isNil) this.primHandler.synchronousSignal(sema); | |
} | |
if (this.pendingFinalizationSignals > 0) { //signal any pending finalizations | |
var sema = this.specialObjects[Squeak.splOb_TheFinalizationSemaphore]; | |
this.pendingFinalizationSignals = 0; | |
if (!sema.isNil) this.primHandler.synchronousSignal(sema); | |
} | |
if (this.primHandler.semaphoresToSignal.length > 0) | |
this.primHandler.signalExternalSemaphores(); // signal pending semaphores, if any | |
// if this is a long-running do-it, compile it | |
if (!this.method.compiled) this.compileIfPossible(this.method); | |
// have to return to web browser once in a while | |
if (now >= this.breakOutTick) | |
this.breakOut(); | |
}, | |
extendedPush: function(nextByte) { | |
var lobits = nextByte & 63; | |
switch (nextByte>>6) { | |
case 0: this.push(this.receiver.pointers[lobits]);break; | |
case 1: this.push(this.homeContext.pointers[Squeak.Context_tempFrameStart+lobits]); break; | |
case 2: this.push(this.method.methodGetLiteral(lobits)); break; | |
case 3: this.push(this.method.methodGetLiteral(lobits).pointers[Squeak.Assn_value]); break; | |
} | |
}, | |
extendedStore: function( nextByte) { | |
var lobits = nextByte & 63; | |
switch (nextByte>>6) { | |
case 0: | |
this.receiver.dirty = true; | |
this.receiver.pointers[lobits] = this.top(); | |
break; | |
case 1: | |
this.homeContext.pointers[Squeak.Context_tempFrameStart+lobits] = this.top(); | |
break; | |
case 2: | |
this.nono(); | |
break; | |
case 3: | |
var assoc = this.method.methodGetLiteral(lobits); | |
assoc.dirty = true; | |
assoc.pointers[Squeak.Assn_value] = this.top(); | |
break; | |
} | |
}, | |
extendedStorePop: function(nextByte) { | |
var lobits = nextByte & 63; | |
switch (nextByte>>6) { | |
case 0: | |
this.receiver.dirty = true; | |
this.receiver.pointers[lobits] = this.pop(); | |
break; | |
case 1: | |
this.homeContext.pointers[Squeak.Context_tempFrameStart+lobits] = this.pop(); | |
break; | |
case 2: | |
this.nono(); | |
break; | |
case 3: | |
var assoc = this.method.methodGetLiteral(lobits); | |
assoc.dirty = true; | |
assoc.pointers[Squeak.Assn_value] = this.pop(); | |
break; | |
} | |
}, | |
doubleExtendedDoAnything: function(byte2) { | |
var byte3 = this.nextByte(); | |
switch (byte2>>5) { | |
case 0: this.send(this.method.methodGetSelector(byte3), byte2&31, false); break; | |
case 1: this.send(this.method.methodGetSelector(byte3), byte2&31, true); break; | |
case 2: this.push(this.receiver.pointers[byte3]); break; | |
case 3: this.push(this.method.methodGetLiteral(byte3)); break; | |
case 4: this.push(this.method.methodGetLiteral(byte3).pointers[Squeak.Assn_value]); break; | |
case 5: this.receiver.dirty = true; this.receiver.pointers[byte3] = this.top(); break; | |
case 6: this.receiver.dirty = true; this.receiver.pointers[byte3] = this.pop(); break; | |
case 7: var assoc = this.method.methodGetLiteral(byte3); | |
assoc.dirty = true; | |
assoc.pointers[Squeak.Assn_value] = this.top(); break; | |
} | |
}, | |
jumpIfTrue: function(delta) { | |
var top = this.pop(); | |
if (top.isTrue) {this.pc += delta; return;} | |
if (top.isFalse) return; | |
this.push(top); //Uh-oh it's not even a boolean (that we know of ;-). Restore stack... | |
this.send(this.specialObjects[Squeak.splOb_SelectorMustBeBoolean], 0, false); | |
}, | |
jumpIfFalse: function(delta) { | |
var top = this.pop(); | |
if (top.isFalse) {this.pc += delta; return;} | |
if (top.isTrue) return; | |
this.push(top); //Uh-oh it's not even a boolean (that we know of ;-). Restore stack... | |
this.send(this.specialObjects[Squeak.splOb_SelectorMustBeBoolean], 0, false); | |
}, | |
sendSpecial: function(lobits) { | |
this.send(this.specialSelectors[lobits*2], | |
this.specialSelectors[(lobits*2)+1], | |
false); //specialSelectors is {...sel,nArgs,sel,nArgs,...) | |
}, | |
callPrimBytecode: function(extendedStoreBytecode) { | |
this.pc += 2; // skip over primitive number | |
if (this.primFailCode) { | |
if (this.method.bytes[this.pc] === extendedStoreBytecode) | |
this.stackTopPut(this.getErrorObjectFromPrimFailCode()); | |
this.primFailCode = 0; | |
} | |
}, | |
getErrorObjectFromPrimFailCode: function() { | |
var primErrTable = this.specialObjects[Squeak.splOb_PrimErrTableIndex]; | |
if (primErrTable && primErrTable.pointers) { | |
var errorObject = primErrTable.pointers[this.primFailCode - 1]; | |
if (errorObject) return errorObject; | |
} | |
return this.primFailCode; | |
}, | |
}, | |
'closures', { | |
pushNewArray: function(nextByte) { | |
var popValues = nextByte > 127, | |
count = nextByte & 127, | |
array = this.instantiateClass(this.specialObjects[Squeak.splOb_ClassArray], count); | |
if (popValues) { | |
for (var i = 0; i < count; i++) | |
array.pointers[i] = this.stackValue(count - i - 1); | |
this.popN(count); | |
} | |
this.push(array); | |
}, | |
pushClosureCopy: function() { | |
// The compiler has pushed the values to be copied, if any. Find numArgs and numCopied in the byte following. | |
// Create a Closure with space for the copiedValues and pop numCopied values off the stack into the closure. | |
// Set numArgs as specified, and set startpc to the pc following the block size and jump over that code. | |
var numArgsNumCopied = this.nextByte(), | |
numArgs = numArgsNumCopied & 0xF, | |
numCopied = numArgsNumCopied >> 4, | |
blockSizeHigh = this.nextByte(), | |
blockSize = blockSizeHigh * 256 + this.nextByte(), | |
initialPC = this.encodeSqueakPC(this.pc, this.method), | |
closure = this.newClosure(numArgs, initialPC, numCopied); | |
closure.pointers[Squeak.Closure_outerContext] = this.activeContext; | |
this.reclaimableContextCount = 0; // The closure refers to thisContext so it can't be reclaimed | |
if (numCopied > 0) { | |
for (var i = 0; i < numCopied; i++) | |
closure.pointers[Squeak.Closure_firstCopiedValue + i] = this.stackValue(numCopied - i - 1); | |
this.popN(numCopied); | |
} | |
this.pc += blockSize; | |
this.push(closure); | |
}, | |
pushClosureCopyExtended: function(extA, extB) { | |
var byteA = this.nextByte(); | |
var byteB = this.nextByte(); | |
var numArgs = (byteA & 7) + this.mod(extA, 16) * 8, | |
numCopied = (byteA >> 3 & 0x7) + this.div(extA, 16) * 8, | |
blockSize = byteB + (extB << 8), | |
initialPC = this.encodeSqueakPC(this.pc, this.method), | |
closure = this.newClosure(numArgs, initialPC, numCopied); | |
closure.pointers[Squeak.Closure_outerContext] = this.activeContext; | |
this.reclaimableContextCount = 0; // The closure refers to thisContext so it can't be reclaimed | |
if (numCopied > 0) { | |
for (var i = 0; i < numCopied; i++) | |
closure.pointers[Squeak.Closure_firstCopiedValue + i] = this.stackValue(numCopied - i - 1); | |
this.popN(numCopied); | |
} | |
this.pc += blockSize; | |
this.push(closure); | |
}, | |
pushFullClosure: function(extA) { | |
var byteA = this.nextByte(); | |
var byteB = this.nextByte(); | |
var literalIndex = byteA + (extA << 8); | |
var numCopied = byteB & 63; | |
var context; | |
if ((byteB >> 6 & 1) == 1) { | |
context = this.vm.nilObj; | |
} else { | |
context = this.activeContext; | |
} | |
var compiledBlock = this.method.methodGetLiteral(literalIndex); | |
var closure = this.newFullClosure(context, numCopied, compiledBlock); | |
if ((byteB >> 7 & 1) == 1) { | |
throw Error("on-stack receiver not yet supported"); | |
} else { | |
closure.pointers[Squeak.ClosureFull_receiver] = this.receiver; | |
} | |
this.reclaimableContextCount = 0; // The closure refers to thisContext so it can't be reclaimed | |
if (numCopied > 0) { | |
for (var i = 0; i < numCopied; i++) | |
closure.pointers[Squeak.ClosureFull_firstCopiedValue + i] = this.stackValue(numCopied - i - 1); | |
this.popN(numCopied); | |
} | |
this.push(closure); | |
}, | |
newClosure: function(numArgs, initialPC, numCopied) { | |
var closure = this.instantiateClass(this.specialObjects[Squeak.splOb_ClassBlockClosure], numCopied); | |
closure.pointers[Squeak.Closure_startpc] = initialPC; | |
closure.pointers[Squeak.Closure_numArgs] = numArgs; | |
return closure; | |
}, | |
newFullClosure: function(context, numCopied, compiledBlock) { | |
var closure = this.instantiateClass(this.specialObjects[Squeak.splOb_ClassFullBlockClosure], numCopied); | |
closure.pointers[Squeak.Closure_outerContext] = context; | |
closure.pointers[Squeak.ClosureFull_method] = compiledBlock; | |
closure.pointers[Squeak.Closure_numArgs] = compiledBlock.methodNumArgs(); | |
return closure; | |
}, | |
}, | |
'sending', { | |
send: function(selector, argCount, doSuper) { | |
var newRcvr = this.stackValue(argCount); | |
var lookupClass; | |
if (doSuper) { | |
lookupClass = this.method.methodClassForSuper(); | |
lookupClass = lookupClass.superclass(); | |
} else { | |
lookupClass = this.getClass(newRcvr); | |
} | |
var entry = this.findSelectorInClass(selector, argCount, lookupClass); | |
if (entry.primIndex) { | |
//note details for verification of at/atput primitives | |
this.verifyAtSelector = selector; | |
this.verifyAtClass = lookupClass; | |
} | |
this.executeNewMethod(newRcvr, entry.method, entry.argCount, entry.primIndex, entry.mClass, selector); | |
}, | |
sendSuperDirected: function(selector, argCount) { | |
var lookupClass = this.pop().superclass(); | |
var newRcvr = this.stackValue(argCount); | |
var entry = this.findSelectorInClass(selector, argCount, lookupClass); | |
if (entry.primIndex) { | |
//note details for verification of at/atput primitives | |
this.verifyAtSelector = selector; | |
this.verifyAtClass = lookupClass; | |
} | |
this.executeNewMethod(newRcvr, entry.method, entry.argCount, entry.primIndex, entry.mClass, selector); | |
}, | |
sendAsPrimitiveFailure: function(rcvr, method, argCount) { | |
this.executeNewMethod(rcvr, method, argCount, 0); | |
}, | |
/** | |
* @param {*} trueArgCount The number of arguments for the method to be found | |
* @param {*} argCount The number of arguments currently on the stack (may be different from trueArgCount in the context of primitive 84 etc.) | |
*/ | |
findSelectorInClass: function(selector, trueArgCount, startingClass, argCount = trueArgCount) { | |
this.currentSelector = selector; // for primitiveInvokeObjectAsMethod | |
var cacheEntry = this.findMethodCacheEntry(selector, startingClass); | |
if (cacheEntry.method) return cacheEntry; // Found it in the method cache | |
var currentClass = startingClass; | |
var mDict; | |
while (!currentClass.isNil) { | |
mDict = currentClass.pointers[Squeak.Class_mdict]; | |
if (mDict.isNil) { | |
// MethodDict pointer is nil (hopefully due a swapped out stub) | |
// -- send #cannotInterpret: | |
var cantInterpSel = this.specialObjects[Squeak.splOb_SelectorCannotInterpret], | |
cantInterpMsg = this.createActualMessage(selector, trueArgCount, startingClass); | |
this.popNandPush(argCount + 1, cantInterpMsg); | |
return this.findSelectorInClass(cantInterpSel, 1, currentClass.superclass()); | |
} | |
var newMethod = this.lookupSelectorInDict(mDict, selector); | |
if (!newMethod.isNil) { | |
cacheEntry.method = newMethod; | |
if (newMethod.isMethod()) { | |
cacheEntry.primIndex = newMethod.methodPrimitiveIndex(); | |
cacheEntry.argCount = newMethod.methodNumArgs(); | |
} else { | |
// if method is not actually a CompiledMethod, let primitiveInvokeObjectAsMethod (576) handle it | |
cacheEntry.primIndex = 576; | |
cacheEntry.argCount = trueArgCount; | |
} | |
cacheEntry.mClass = currentClass; | |
return cacheEntry; | |
} | |
currentClass = currentClass.superclass(); | |
} | |
//Cound not find a normal message -- send #doesNotUnderstand: | |
var dnuSel = this.specialObjects[Squeak.splOb_SelectorDoesNotUnderstand]; | |
if (selector === dnuSel) // Cannot find #doesNotUnderstand: -- unrecoverable error. | |
throw Error("Recursive not understood error encountered"); | |
var dnuMsg = this.createActualMessage(selector, trueArgCount, startingClass); // The argument to doesNotUnderstand: | |
if (this.breakOnMessageNotUnderstood) { | |
var receiver = this.stackValue(argCount); | |
this.breakNow("Message not understood: " + receiver + " " + startingClass.className() + ">>" + selector.bytesAsString()); | |
} | |
this.popNandPush(argCount, dnuMsg); | |
return this.findSelectorInClass(dnuSel, 1, startingClass); | |
}, | |
lookupSelectorInDict: function(mDict, messageSelector) { | |
//Returns a method or nilObject | |
var dictSize = mDict.pointersSize(); | |
var mask = (dictSize - Squeak.MethodDict_selectorStart) - 1; | |
var index = (mask & messageSelector.hash) + Squeak.MethodDict_selectorStart; | |
// If there are no nils (should always be), then stop looping on second wrap. | |
var hasWrapped = false; | |
while (true) { | |
var nextSelector = mDict.pointers[index]; | |
if (nextSelector === messageSelector) { | |
var methArray = mDict.pointers[Squeak.MethodDict_array]; | |
return methArray.pointers[index - Squeak.MethodDict_selectorStart]; | |
} | |
if (nextSelector.isNil) return this.nilObj; | |
if (++index === dictSize) { | |
if (hasWrapped) return this.nilObj; | |
index = Squeak.MethodDict_selectorStart; | |
hasWrapped = true; | |
} | |
} | |
}, | |
executeNewMethod: function(newRcvr, newMethod, argumentCount, primitiveIndex, optClass, optSel) { | |
this.sendCount++; | |
if (newMethod === this.breakOnMethod) this.breakNow("executing method " + this.printMethod(newMethod, optClass, optSel)); | |
if (this.logSends) { | |
var indent = ' '; | |
var ctx = this.activeContext; | |
while (!ctx.isNil) { indent += '| '; ctx = ctx.pointers[Squeak.Context_sender]; } | |
var args = this.activeContext.pointers.slice(this.sp + 1 - argumentCount, this.sp + 1); | |
console.log(this.sendCount + indent + this.printMethod(newMethod, optClass, optSel, args)); | |
} | |
if (this.breakOnContextChanged) { | |
this.breakOnContextChanged = false; | |
this.breakNow(); | |
} | |
if (primitiveIndex > 0) | |
if (this.tryPrimitive(primitiveIndex, argumentCount, newMethod)) | |
return; //Primitive succeeded -- end of story | |
var newContext = this.allocateOrRecycleContext(newMethod.methodNeedsLargeFrame()); | |
var tempCount = newMethod.methodTempCount(); | |
var newPC = 0; // direct zero-based index into byte codes | |
var newSP = Squeak.Context_tempFrameStart + tempCount - 1; // direct zero-based index into context pointers | |
newContext.pointers[Squeak.Context_method] = newMethod; | |
//Following store is in case we alloc without init; all other fields get stored | |
newContext.pointers[Squeak.BlockContext_initialIP] = this.nilObj; | |
newContext.pointers[Squeak.Context_sender] = this.activeContext; | |
//Copy receiver and args to new context | |
//Note this statement relies on the receiver slot being contiguous with args... | |
this.arrayCopy(this.activeContext.pointers, this.sp-argumentCount, newContext.pointers, Squeak.Context_tempFrameStart-1, argumentCount+1); | |
//...and fill the remaining temps with nil | |
this.arrayFill(newContext.pointers, Squeak.Context_tempFrameStart+argumentCount, Squeak.Context_tempFrameStart+tempCount, this.nilObj); | |
this.popN(argumentCount+1); | |
this.reclaimableContextCount++; | |
this.storeContextRegisters(); | |
/////// Woosh ////// | |
this.activeContext = newContext; //We're off and running... | |
//Following are more efficient than fetchContextRegisters() in newActiveContext() | |
this.activeContext.dirty = true; | |
this.homeContext = newContext; | |
this.method = newMethod; | |
this.pc = newPC; | |
this.sp = newSP; | |
this.receiver = newContext.pointers[Squeak.Context_receiver]; | |
if (this.receiver !== newRcvr) | |
throw Error("receivers don't match"); | |
if (!newMethod.compiled) this.compileIfPossible(newMethod, optClass, optSel); | |
// check for process switch on full method activation | |
if (this.interruptCheckCounter-- <= 0) this.checkForInterrupts(); | |
}, | |
compileIfPossible(newMethod, optClass, optSel) { | |
if (!newMethod.compiled && this.compiler) { | |
this.compiler.compile(newMethod, optClass, optSel); | |
} | |
}, | |
doReturn: function(returnValue, targetContext) { | |
// get sender from block home or closure's outerContext | |
if (!targetContext) { | |
var ctx = this.homeContext; | |
if (this.hasClosures) { | |
var closure; | |
while (!(closure = ctx.pointers[Squeak.Context_closure]).isNil) | |
ctx = closure.pointers[Squeak.Closure_outerContext]; | |
} | |
targetContext = ctx.pointers[Squeak.Context_sender]; | |
} | |
if (targetContext.isNil || targetContext.pointers[Squeak.Context_instructionPointer].isNil) | |
return this.cannotReturn(returnValue); | |
// search up stack for unwind | |
var thisContext = this.activeContext.pointers[Squeak.Context_sender]; | |
while (thisContext !== targetContext) { | |
if (thisContext.isNil) | |
return this.cannotReturn(returnValue); | |
if (this.isUnwindMarked(thisContext)) | |
return this.aboutToReturnThrough(returnValue, thisContext); | |
thisContext = thisContext.pointers[Squeak.Context_sender]; | |
} | |
// no unwind to worry about, just peel back the stack (usually just to sender) | |
var nextContext; | |
thisContext = this.activeContext; | |
while (thisContext !== targetContext) { | |
if (this.breakOnContextReturned === thisContext) { | |
this.breakOnContextReturned = null; | |
this.breakNow(); | |
} | |
nextContext = thisContext.pointers[Squeak.Context_sender]; | |
thisContext.pointers[Squeak.Context_sender] = this.nilObj; | |
thisContext.pointers[Squeak.Context_instructionPointer] = this.nilObj; | |
if (this.reclaimableContextCount > 0) { | |
this.reclaimableContextCount--; | |
this.recycleIfPossible(thisContext); | |
} | |
thisContext = nextContext; | |
} | |
this.activeContext = thisContext; | |
this.activeContext.dirty = true; | |
this.fetchContextRegisters(this.activeContext); | |
this.push(returnValue); | |
if (this.breakOnContextChanged) { | |
this.breakOnContextChanged = false; | |
this.breakNow(); | |
} | |
}, | |
aboutToReturnThrough: function(resultObj, aContext) { | |
this.push(this.exportThisContext()); | |
this.push(resultObj); | |
this.push(aContext); | |
var aboutToReturnSel = this.specialObjects[Squeak.splOb_SelectorAboutToReturn]; | |
this.send(aboutToReturnSel, 2); | |
}, | |
cannotReturn: function(resultObj) { | |
this.push(this.exportThisContext()); | |
this.push(resultObj); | |
var cannotReturnSel = this.specialObjects[Squeak.splOb_SelectorCannotReturn]; | |
this.send(cannotReturnSel, 1); | |
}, | |
tryPrimitive: function(primIndex, argCount, newMethod) { | |
if ((primIndex > 255) && (primIndex < 520)) { | |
if (primIndex >= 264) {//return instvars | |
this.popNandPush(1, this.top().pointers[primIndex - 264]); | |
return true; | |
} | |
switch (primIndex) { | |
case 256: //return self | |
return true; | |
case 257: this.popNandPush(1, this.trueObj); //return true | |
return true; | |
case 258: this.popNandPush(1, this.falseObj); //return false | |
return true; | |
case 259: this.popNandPush(1, this.nilObj); //return nil | |
return true; | |
} | |
this.popNandPush(1, primIndex - 261); //return -1...2 | |
return true; | |
} | |
var sp = this.sp; | |
var context = this.activeContext; | |
var success = this.primHandler.doPrimitive(primIndex, argCount, newMethod); | |
if (success | |
&& this.sp !== sp - argCount | |
&& context === this.activeContext | |
&& primIndex !== 117 // named prims are checked separately (see namedPrimitive) | |
&& primIndex !== 118 // primitiveDoPrimitiveWithArgs (will call tryPrimitive again) | |
&& primIndex !== 218 // primitiveDoNamedPrimitive (will call namedPrimitive) | |
&& !this.frozen) { | |
this.warnOnce("stack unbalanced after primitive " + primIndex, "error"); | |
} | |
return success; | |
}, | |
createActualMessage: function(selector, argCount, cls) { | |
//Bundle up receiver, args and selector as a messageObject | |
var message = this.instantiateClass(this.specialObjects[Squeak.splOb_ClassMessage], 0); | |
var argArray = this.instantiateClass(this.specialObjects[Squeak.splOb_ClassArray], argCount); | |
this.arrayCopy(this.activeContext.pointers, this.sp-argCount+1, argArray.pointers, 0, argCount); //copy args from stack | |
message.pointers[Squeak.Message_selector] = selector; | |
message.pointers[Squeak.Message_arguments] = argArray; | |
if (message.pointers.length > Squeak.Message_lookupClass) //Early versions don't have lookupClass | |
message.pointers[Squeak.Message_lookupClass] = cls; | |
return message; | |
}, | |
primitivePerform: function(argCount) { | |
var selector = this.stackValue(argCount-1); | |
var rcvr = this.stackValue(argCount); | |
var trueArgCount = argCount - 1; | |
var entry = this.findSelectorInClass(selector, trueArgCount, this.getClass(rcvr), argCount); | |
if (entry.selector === selector) { | |
// selector has been found, rearrange stack | |
if (entry.argCount !== trueArgCount) | |
return false; | |
var stack = this.activeContext.pointers; // slide eveything down... | |
var selectorIndex = this.sp - trueArgCount; | |
this.arrayCopy(stack, selectorIndex+1, stack, selectorIndex, trueArgCount); | |
this.sp--; // adjust sp accordingly | |
} else { | |
// stack has already been arranged for #doesNotUnderstand:/#cannotInterpret: | |
rcvr = this.stackValue(entry.argCount); | |
} | |
this.executeNewMethod(rcvr, entry.method, entry.argCount, entry.primIndex, entry.mClass, selector); | |
return true; | |
}, | |
primitivePerformWithArgs: function(argCount, supered) { | |
var rcvrPos = supered ? 3 : 2; | |
var rcvr = this.stackValue(rcvrPos); | |
var selector = this.stackValue(rcvrPos - 1); | |
var args = this.stackValue(rcvrPos - 2); | |
if (args.sqClass !== this.specialObjects[Squeak.splOb_ClassArray]) | |
return false; | |
var lookupClass = supered ? this.top() : this.getClass(rcvr); | |
if (supered) { // verify that lookupClass is in fact in superclass chain of receiver; | |
var cls = this.getClass(rcvr); | |
while (cls !== lookupClass) { | |
cls = cls.superclass(); | |
if (cls.isNil) return false; | |
} | |
} | |
var trueArgCount = args.pointersSize(); | |
var entry = this.findSelectorInClass(selector, trueArgCount, lookupClass, argCount); | |
if (entry.selector === selector) { | |
// selector has been found, rearrange stack | |
if (entry.argCount !== trueArgCount) | |
return false; | |
var stack = this.activeContext.pointers; | |
var selectorIndex = this.sp - (argCount - 1); | |
stack[selectorIndex - 1] = rcvr; | |
this.arrayCopy(args.pointers, 0, stack, selectorIndex, trueArgCount); | |
this.sp += trueArgCount - argCount; // pop old args then push new args | |
} else { | |
// stack has already been arranged for #doesNotUnderstand: or #cannotInterpret: | |
rcvr = this.stackValue(entry.argCount); | |
} | |
this.executeNewMethod(rcvr, entry.method, entry.argCount, entry.primIndex, entry.mClass, selector); | |
return true; | |
}, | |
primitiveInvokeObjectAsMethod: function(argCount, method) { | |
// invoked from VM if non-method found in lookup | |
var orgArgs = this.instantiateClass(this.specialObjects[Squeak.splOb_ClassArray], argCount); | |
for (var i = 0; i < argCount; i++) | |
orgArgs.pointers[argCount - i - 1] = this.pop(); | |
var orgReceiver = this.pop(), | |
orgSelector = this.currentSelector; | |
// send run:with:in: to non-method object | |
var runWithIn = this.specialObjects[Squeak.splOb_SelectorRunWithIn]; | |
this.push(method); // not actually a method | |
this.push(orgSelector); | |
this.push(orgArgs); | |
this.push(orgReceiver); | |
this.send(runWithIn, 3, false); | |
return true; | |
}, | |
findMethodCacheEntry: function(selector, lkupClass) { | |
//Probe the cache, and return the matching entry if found | |
//Otherwise return one that can be used (selector and class set) with method == null. | |
//Initial probe is class xor selector, reprobe delta is selector | |
//We do not try to optimize probe time -- all are equally 'fast' compared to lookup | |
//Instead we randomize the reprobe so two or three very active conflicting entries | |
//will not keep dislodging each other | |
var entry; | |
this.methodCacheRandomish = (this.methodCacheRandomish + 1) & 3; | |
var firstProbe = (selector.hash ^ lkupClass.hash) & this.methodCacheMask; | |
var probe = firstProbe; | |
for (var i = 0; i < 4; i++) { // 4 reprobes for now | |
entry = this.methodCache[probe]; | |
if (entry.selector === selector && entry.lkupClass === lkupClass) return entry; | |
if (i === this.methodCacheRandomish) firstProbe = probe; | |
probe = (probe + selector.hash) & this.methodCacheMask; | |
} | |
entry = this.methodCache[firstProbe]; | |
entry.lkupClass = lkupClass; | |
entry.selector = selector; | |
entry.method = null; | |
return entry; | |
}, | |
flushMethodCache: function() { //clear all cache entries (prim 89) | |
for (var i = 0; i < this.methodCacheSize; i++) { | |
this.methodCache[i].selector = null; // mark it free | |
this.methodCache[i].method = null; // release the method | |
} | |
return true; | |
}, | |
flushMethodCacheForSelector: function(selector) { //clear cache entries for selector (prim 119) | |
for (var i = 0; i < this.methodCacheSize; i++) | |
if (this.methodCache[i].selector === selector) { | |
this.methodCache[i].selector = null; // mark it free | |
this.methodCache[i].method = null; // release the method | |
} | |
return true; | |
}, | |
flushMethodCacheForMethod: function(method) { //clear cache entries for method (prim 116) | |
for (var i = 0; i < this.methodCacheSize; i++) | |
if (this.methodCache[i].method === method) { | |
this.methodCache[i].selector = null; // mark it free | |
this.methodCache[i].method = null; // release the method | |
} | |
return true; | |
}, | |
flushMethodCacheAfterBecome: function(mutations) { | |
// could be selective by checking lkupClass, selector, | |
// and method against mutations dict | |
this.flushMethodCache(); | |
}, | |
}, | |
'contexts', { | |
isUnwindMarked: function(ctx) { | |
if (!this.isMethodContext(ctx)) return false; | |
var method = ctx.pointers[Squeak.Context_method]; | |
return method.methodPrimitiveIndex() == 198; | |
}, | |
newActiveContext: function(newContext) { | |
// Note: this is inlined in executeNewMethod() and doReturn() | |
this.storeContextRegisters(); | |
this.activeContext = newContext; //We're off and running... | |
this.activeContext.dirty = true; | |
this.fetchContextRegisters(newContext); | |
if (this.breakOnContextChanged) { | |
this.breakOnContextChanged = false; | |
this.breakNow(); | |
} | |
}, | |
exportThisContext: function() { | |
this.storeContextRegisters(); | |
this.reclaimableContextCount = 0; | |
return this.activeContext; | |
}, | |
fetchContextRegisters: function(ctxt) { | |
var meth = ctxt.pointers[Squeak.Context_method]; | |
if (this.isSmallInt(meth)) { //if the Method field is an integer, activeCntx is a block context | |
this.homeContext = ctxt.pointers[Squeak.BlockContext_home]; | |
meth = this.homeContext.pointers[Squeak.Context_method]; | |
} else { //otherwise home==ctxt | |
this.homeContext = ctxt; | |
} | |
this.receiver = this.homeContext.pointers[Squeak.Context_receiver]; | |
this.method = meth; | |
this.pc = this.decodeSqueakPC(ctxt.pointers[Squeak.Context_instructionPointer], meth); | |
this.sp = this.decodeSqueakSP(ctxt.pointers[Squeak.Context_stackPointer]); | |
}, | |
storeContextRegisters: function() { | |
//Save pc, sp into activeContext object, prior to change of context | |
// see fetchContextRegisters for symmetry | |
// expects activeContext, pc, sp, and method state vars to still be valid | |
this.activeContext.pointers[Squeak.Context_instructionPointer] = this.encodeSqueakPC(this.pc, this.method); | |
this.activeContext.pointers[Squeak.Context_stackPointer] = this.encodeSqueakSP(this.sp); | |
}, | |
encodeSqueakPC: function(intPC, method) { | |
// Squeak pc is offset by header and literals | |
// and 1 for z-rel addressing | |
return intPC + method.pointers.length * 4 + 1; | |
}, | |
decodeSqueakPC: function(squeakPC, method) { | |
return squeakPC - method.pointers.length * 4 - 1; | |
}, | |
encodeSqueakSP: function(intSP) { | |
// sp is offset by tempFrameStart, -1 for z-rel addressing | |
return intSP - (Squeak.Context_tempFrameStart - 1); | |
}, | |
decodeSqueakSP: function(squeakSP) { | |
return squeakSP + (Squeak.Context_tempFrameStart - 1); | |
}, | |
recycleIfPossible: function(ctxt) { | |
if (!this.isMethodContext(ctxt)) return; | |
if (ctxt.pointersSize() === (Squeak.Context_tempFrameStart+Squeak.Context_smallFrameSize)) { | |
// Recycle small contexts | |
ctxt.pointers[0] = this.freeContexts; | |
this.freeContexts = ctxt; | |
} else { // Recycle large contexts | |
if (ctxt.pointersSize() !== (Squeak.Context_tempFrameStart+Squeak.Context_largeFrameSize)) | |
return; | |
ctxt.pointers[0] = this.freeLargeContexts; | |
this.freeLargeContexts = ctxt; | |
} | |
}, | |
allocateOrRecycleContext: function(needsLarge) { | |
//Return a recycled context or a newly allocated one if none is available for recycling." | |
var freebie; | |
if (needsLarge) { | |
if (!this.freeLargeContexts.isNil) { | |
freebie = this.freeLargeContexts; | |
this.freeLargeContexts = freebie.pointers[0]; | |
this.nRecycledContexts++; | |
return freebie; | |
} | |
this.nAllocatedContexts++; | |
return this.instantiateClass(this.specialObjects[Squeak.splOb_ClassMethodContext], Squeak.Context_largeFrameSize); | |
} else { | |
if (!this.freeContexts.isNil) { | |
freebie = this.freeContexts; | |
this.freeContexts = freebie.pointers[0]; | |
this.nRecycledContexts++; | |
return freebie; | |
} | |
this.nAllocatedContexts++; | |
return this.instantiateClass(this.specialObjects[Squeak.splOb_ClassMethodContext], Squeak.Context_smallFrameSize); | |
} | |
}, | |
}, | |
'stack access', { | |
pop: function() { | |
//Note leaves garbage above SP. Cleaned out by fullGC. | |
return this.activeContext.pointers[this.sp--]; | |
}, | |
popN: function(nToPop) { | |
this.sp -= nToPop; | |
}, | |
push: function(object) { | |
this.activeContext.pointers[++this.sp] = object; | |
}, | |
popNandPush: function(nToPop, object) { | |
this.activeContext.pointers[this.sp -= nToPop - 1] = object; | |
}, | |
top: function() { | |
return this.activeContext.pointers[this.sp]; | |
}, | |
stackTopPut: function(object) { | |
this.activeContext.pointers[this.sp] = object; | |
}, | |
stackValue: function(depthIntoStack) { | |
return this.activeContext.pointers[this.sp - depthIntoStack]; | |
}, | |
stackInteger: function(depthIntoStack) { | |
return this.checkSmallInt(this.stackValue(depthIntoStack)); | |
}, | |
stackIntOrFloat: function(depthIntoStack) { | |
var num = this.stackValue(depthIntoStack); | |
// is it a SmallInt? | |
if (typeof num === "number") return num; | |
if (num === undefined) {this.success = false; return 0;} | |
// is it a Float? | |
if (num.isFloat) { | |
this.resultIsFloat = true; // need to return result as Float | |
return num.float; | |
} | |
// maybe a 32-bit LargeInt? | |
var bytes = num.bytes; | |
if (bytes && bytes.length == 4) { | |
var value = 0; | |
for (var i = 3; i >= 0; i--) | |
value = value * 256 + bytes[i]; | |
if (num.sqClass === this.specialObjects[Squeak.splOb_ClassLargePositiveInteger]) | |
return value; | |
if (num.sqClass === this.specialObjects[Squeak.splOb_ClassLargeNegativeInteger]) | |
return -value; | |
} | |
// none of the above | |
this.success = false; | |
return 0; | |
}, | |
pop2AndPushIntResult: function(intResult) {// returns success boolean | |
if (this.success && this.canBeSmallInt(intResult)) { | |
this.popNandPush(2, intResult); | |
return true; | |
} | |
return false; | |
}, | |
pop2AndPushNumResult: function(numResult) {// returns success boolean | |
if (this.success) { | |
if (this.resultIsFloat) { | |
this.popNandPush(2, this.primHandler.makeFloat(numResult)); | |
return true; | |
} | |
if (numResult >= Squeak.MinSmallInt && numResult <= Squeak.MaxSmallInt) { | |
this.popNandPush(2, numResult); | |
return true; | |
} | |
if (numResult >= -0xFFFFFFFF && numResult <= 0xFFFFFFFF) { | |
var negative = numResult < 0, | |
unsigned = negative ? -numResult : numResult, | |
lgIntClass = negative ? Squeak.splOb_ClassLargeNegativeInteger : Squeak.splOb_ClassLargePositiveInteger, | |
lgIntObj = this.instantiateClass(this.specialObjects[lgIntClass], 4), | |
bytes = lgIntObj.bytes; | |
bytes[0] = unsigned & 255; | |
bytes[1] = unsigned>>8 & 255; | |
bytes[2] = unsigned>>16 & 255; | |
bytes[3] = unsigned>>24 & 255; | |
this.popNandPush(2, lgIntObj); | |
return true; | |
} | |
} | |
return false; | |
}, | |
pop2AndPushBoolResult: function(boolResult) { | |
if (!this.success) return false; | |
this.popNandPush(2, boolResult ? this.trueObj : this.falseObj); | |
return true; | |
}, | |
}, | |
'numbers', { | |
getClass: function(obj) { | |
if (this.isSmallInt(obj)) | |
return this.specialObjects[Squeak.splOb_ClassInteger]; | |
return obj.sqClass; | |
}, | |
canBeSmallInt: function(anInt) { | |
return (anInt >= Squeak.MinSmallInt) && (anInt <= Squeak.MaxSmallInt); | |
}, | |
isSmallInt: function(object) { | |
return typeof object === "number"; | |
}, | |
checkSmallInt: function(maybeSmallInt) { // returns an int and sets success | |
if (typeof maybeSmallInt === "number") | |
return maybeSmallInt; | |
this.success = false; | |
return 1; | |
}, | |
quickDivide: function(rcvr, arg) { // must only handle exact case | |
if (arg === 0) return Squeak.NonSmallInt; // fail if divide by zero | |
var result = rcvr / arg | 0; | |
if (result * arg === rcvr) return result; | |
return Squeak.NonSmallInt; // fail if result is not exact | |
}, | |
div: function(rcvr, arg) { | |
if (arg === 0) return Squeak.NonSmallInt; // fail if divide by zero | |
return Math.floor(rcvr/arg); | |
}, | |
mod: function(rcvr, arg) { | |
if (arg === 0) return Squeak.NonSmallInt; // fail if divide by zero | |
return rcvr - Math.floor(rcvr/arg) * arg; | |
}, | |
safeShift: function(smallInt, shiftCount) { | |
// must only be used if smallInt is actually a SmallInt! | |
// the logic is complex because JS shifts only up to 31 bits | |
// and treats e.g. 1<<32 as 1<<0, so we have to do our own checks | |
if (shiftCount < 0) { | |
if (shiftCount < -31) return smallInt < 0 ? -1 : 0; | |
// this would wrongly return a negative result if | |
// smallInt >= 0x80000000, but the largest smallInt | |
// is 0x3FFFFFFF so we're ok | |
return smallInt >> -shiftCount; // OK to lose bits shifting right | |
} | |
if (shiftCount > 31) return smallInt === 0 ? 0 : Squeak.NonSmallInt; | |
var shifted = smallInt << shiftCount; | |
// check for lost bits by seeing if computation is reversible | |
if ((shifted>>shiftCount) !== smallInt) return Squeak.NonSmallInt; // fail | |
return shifted; // caller will check if still within SmallInt range | |
}, | |
}, | |
'utils', | |
{ | |
isContext: function(obj) {//either block or methodContext | |
if (obj.sqClass === this.specialObjects[Squeak.splOb_ClassMethodContext]) return true; | |
if (obj.sqClass === this.specialObjects[Squeak.splOb_ClassBlockContext]) return true; | |
return false; | |
}, | |
isMethodContext: function(obj) { | |
return obj.sqClass === this.specialObjects[Squeak.splOb_ClassMethodContext]; | |
}, | |
instantiateClass: function(aClass, indexableSize) { | |
return this.image.instantiateClass(aClass, indexableSize, this.nilObj); | |
}, | |
arrayFill: function(array, fromIndex, toIndex, value) { | |
// assign value to range from fromIndex (inclusive) to toIndex (exclusive) | |
for (var i = fromIndex; i < toIndex; i++) | |
array[i] = value; | |
}, | |
arrayCopy: function(src, srcPos, dest, destPos, length) { | |
// copy length elements from src at srcPos to dest at destPos | |
if (src === dest && srcPos < destPos) | |
for (var i = length - 1; i >= 0; i--) | |
dest[destPos + i] = src[srcPos + i]; | |
else | |
for (var i = 0; i < length; i++) | |
dest[destPos + i] = src[srcPos + i]; | |
}, | |
signalLowSpaceIfNecessary: function(bytesLeft) { | |
if (bytesLeft < this.lowSpaceThreshold && this.lowSpaceThreshold > 0) { | |
var increase = prompt && prompt("Out of memory, " + Math.ceil(this.image.totalMemory/1000000) | |
+ " MB used.\nEnter additional MB, or 0 to signal low space in image", "0"); | |
if (increase) { | |
var bytes = parseInt(increase, 10) * 1000000; | |
this.image.totalMemory += bytes; | |
this.signalLowSpaceIfNecessary(this.image.bytesLeft()); | |
} else { | |
console.warn("squeak: low memory (" + bytesLeft + "/" + this.image.totalMemory + " bytes left), signaling low space"); | |
this.signalLowSpace = true; | |
this.lowSpaceThreshold = 0; | |
var lastSavedProcess = this.specialObjects[Squeak.splOb_ProcessSignalingLowSpace]; | |
if (lastSavedProcess.isNil) { | |
this.specialObjects[Squeak.splOb_ProcessSignalingLowSpace] = this.primHandler.activeProcess(); | |
} | |
this.forceInterruptCheck(); | |
} | |
} | |
}, | |
}, | |
'debugging', { | |
addMessage: function(message) { | |
return this.messages[message] ? ++this.messages[message] : this.messages[message] = 1; | |
}, | |
warnOnce: function(message, what) { | |
if (this.addMessage(message) == 1) { | |
console[what || "warn"](message); | |
return true; | |
} | |
}, | |
printMethod: function(aMethod, optContext, optSel, optArgs) { | |
// return a 'class>>selector' description for the method | |
if (aMethod.sqClass != this.specialObjects[Squeak.splOb_ClassCompiledMethod]) { | |
return this.printMethod(aMethod.blockOuterCode(), optContext, optSel, optArgs) | |
} | |
var found; | |
if (optSel) { | |
var printed = optContext.className() + '>>'; | |
var selector = optSel.bytesAsString(); | |
if (!optArgs || !optArgs.length) printed += selector; | |
else { | |
var parts = selector.split(/(?<=:)/); // keywords | |
for (var i = 0; i < optArgs.length; i++) { | |
if (i > 0) printed += ' '; | |
printed += parts[i] + ' ' + optArgs[i]; | |
} | |
} | |
return printed; | |
} | |
// this is expensive, we have to search all classes | |
if (!aMethod) aMethod = this.activeContext.contextMethod(); | |
this.allMethodsDo(function(classObj, methodObj, selectorObj) { | |
if (methodObj === aMethod) | |
return found = classObj.className() + '>>' + selectorObj.bytesAsString(); | |
}); | |
if (found) return found; | |
if (optContext) { | |
var rcvr = optContext.pointers[Squeak.Context_receiver]; | |
return "(" + rcvr + ")>>?"; | |
} | |
return "?>>?"; | |
}, | |
allInstancesOf: function(classObj, callback) { | |
if (typeof classObj === "string") classObj = this.globalNamed(classObj); | |
var instances = [], | |
inst = this.image.someInstanceOf(classObj); | |
while (inst) { | |
if (callback) callback(inst); | |
else instances.push(inst); | |
inst = this.image.nextInstanceAfter(inst); | |
} | |
return instances; | |
}, | |
globalNamed: function(name) { | |
return this.allGlobalsDo(function(nameObj, globalObj){ | |
if (nameObj.bytesAsString() === name) return globalObj; | |
}); | |
}, | |
allGlobalsDo: function(callback) { | |
// callback(globalNameObj, globalObj), truish result breaks out of iteration | |
var globals = this.getGlobals(); | |
for (var i = 0; i < globals.length; i++) { | |
var assn = globals[i]; | |
if (!assn.isNil) { | |
var result = callback(assn.pointers[0], assn.pointers[1]); | |
if (result) return result; | |
} | |
} | |
}, | |
allMethodsDo: function(callback) { | |
// callback(classObj, methodObj, selectorObj), truish result breaks out of iteration | |
this.allGlobalsDo(function(globalNameObj, globalObj) { | |
if (globalObj.pointers && globalObj.pointers.length >= 9) { | |
var clsAndMeta = [globalObj, globalObj.sqClass]; | |
for (var c = 0; c < clsAndMeta.length; c++) { | |
var cls = clsAndMeta[c]; | |
var mdict = cls.pointers[1]; | |
if (!mdict.pointers || !mdict.pointers[1]) continue; | |
var methods = mdict.pointers[1].pointers; | |
if (!methods) continue; | |
var selectors = mdict.pointers; | |
if (methods.length + 2 !== selectors.length) continue; | |
for (var j = 0; j < methods.length; j++) { | |
var method = methods[j]; | |
var selector = selectors[2+j]; | |
if (!method.isMethod || !method.isMethod()) continue; | |
if (!selector.bytesSize || !selector.bytesSize()) continue; | |
var result = callback.call(null, cls, method, selector); | |
if (result) return true; | |
} | |
} | |
} | |
}); | |
}, | |
printStack: function(ctx, limit, indent) { | |
// both args are optional | |
if (typeof ctx == "number") {limit = ctx; ctx = null;} | |
if (!ctx) ctx = this.activeContext; | |
if (!limit) limit = 100; | |
var contexts = [], | |
hardLimit = Math.max(limit, 1000000); | |
while (!ctx.isNil && hardLimit-- > 0) { | |
contexts.push(ctx); | |
ctx = ctx.pointers[Squeak.Context_sender]; | |
} | |
var extra = 200; | |
if (contexts.length > limit + extra) { | |
if (!ctx.isNil) contexts.push('...'); // over hard limit | |
contexts = contexts.slice(0, limit).concat(['...']).concat(contexts.slice(-extra)); | |
} | |
var stack = [], | |
i = contexts.length, | |
indents = ''; | |
if (indent && this.logSends) indents = Array((""+this.sendCount).length + 2).join(' '); | |
while (i-- > 0) { | |
var ctx = contexts[i]; | |
if (!ctx.pointers) { | |
stack.push('...\n'); | |
} else { | |
var block = '', | |
method = ctx.pointers[Squeak.Context_method]; | |
if (typeof method === 'number') { // it's a block context, fetch home | |
method = ctx.pointers[Squeak.BlockContext_home].pointers[Squeak.Context_method]; | |
block = '[] in '; | |
} else if (!ctx.pointers[Squeak.Context_closure].isNil) { | |
block = '[] in '; // it's a closure activation | |
} | |
var line = block + this.printMethod(method, ctx); | |
if (indent) line = indents + line; | |
stack.push(line + '\n'); | |
if (indent) indents += indent; | |
} | |
} | |
return stack.join(''); | |
}, | |
findMethod: function(classAndMethodString) { | |
// classAndMethodString is 'Class>>method' | |
var found, | |
className = classAndMethodString.split('>>')[0], | |
methodName = classAndMethodString.split('>>')[1]; | |
this.allMethodsDo(function(classObj, methodObj, selectorObj) { | |
if (methodName.length == selectorObj.bytesSize() | |
&& methodName == selectorObj.bytesAsString() | |
&& className == classObj.className()) | |
return found = methodObj; | |
}); | |
return found; | |
}, | |
breakAfter: function(ms) { | |
this.breakOutTick = this.primHandler.millisecondClockValue() + ms; | |
}, | |
breakNow: function(msg) { | |
if (msg) console.log("Break: " + msg); | |
this.breakOutOfInterpreter = 'break'; | |
}, | |
breakOn: function(classAndMethodString) { | |
// classAndMethodString is 'Class>>method' | |
return this.breakOnMethod = classAndMethodString && this.findMethod(classAndMethodString); | |
}, | |
breakOnReturnFromThisContext: function() { | |
this.breakOnContextChanged = false; | |
this.breakOnContextReturned = this.activeContext; | |
}, | |
breakOnSendOrReturn: function() { | |
this.breakOnContextChanged = true; | |
this.breakOnContextReturned = null; | |
}, | |
printContext: function(ctx, maxWidth) { | |
if (!this.isContext(ctx)) return "NOT A CONTEXT: " + printObj(ctx); | |
if (!maxWidth) maxWidth = 72; | |
function printObj(obj) { | |
var value = typeof obj === 'number' || typeof obj === 'object' ? obj.sqInstName() : "<" + obj + ">"; | |
value = JSON.stringify(value).slice(1, -1); | |
if (value.length > maxWidth - 3) value = value.slice(0, maxWidth - 3) + '...'; | |
return value; | |
} | |
// temps and stack in current context | |
var isBlock = typeof ctx.pointers[Squeak.BlockContext_argumentCount] === 'number'; | |
var closure = ctx.pointers[Squeak.Context_closure]; | |
var isClosure = !isBlock && !closure.isNil; | |
var homeCtx = isBlock ? ctx.pointers[Squeak.BlockContext_home] : ctx; | |
var tempCount = isClosure | |
? closure.pointers[Squeak.Closure_numArgs] | |
: homeCtx.pointers[Squeak.Context_method].methodTempCount(); | |
var stackBottom = this.decodeSqueakSP(0); | |
var stackTop = homeCtx.contextSizeWithStack(this) - 1; | |
var firstTemp = stackBottom + 1; | |
var lastTemp = firstTemp + tempCount - 1; | |
var lastArg = firstTemp + homeCtx.pointers[Squeak.Context_method].methodNumArgs() - 1; | |
var stack = ''; | |
for (var i = stackBottom; i <= stackTop; i++) { | |
var value = printObj(homeCtx.pointers[i]); | |
var label = ''; | |
if (i === stackBottom) { | |
label = '=rcvr'; | |
} else { | |
if (i <= lastTemp) label = '=tmp' + (i - firstTemp); | |
if (i <= lastArg) label += '/arg' + (i - firstTemp); | |
} | |
stack += '\nctx[' + i + ']' + label +': ' + value; | |
} | |
if (isBlock) { | |
stack += '\n'; | |
var nArgs = ctx.pointers[Squeak.BlockContext_argumentCount]; | |
var firstArg = this.decodeSqueakSP(1); | |
var lastArg = firstArg + nArgs; | |
var sp = ctx === this.activeContext ? this.sp : ctx.pointers[Squeak.Context_stackPointer]; | |
if (sp < firstArg) stack += '\nblk <stack empty>'; | |
for (var i = firstArg; i <= sp; i++) { | |
var value = printObj(ctx.pointers[i]); | |
var label = ''; | |
if (i < lastArg) label = '=arg' + (i - firstArg); | |
stack += '\nblk[' + i + ']' + label +': ' + value; | |
} | |
} | |
return stack; | |
}, | |
printActiveContext: function(maxWidth) { | |
return this.printContext(this.activeContext, maxWidth); | |
}, | |
printAllProcesses: function() { | |
var schedAssn = this.specialObjects[Squeak.splOb_SchedulerAssociation], | |
sched = schedAssn.pointers[Squeak.Assn_value]; | |
// print active process | |
var activeProc = sched.pointers[Squeak.ProcSched_activeProcess], | |
result = "Active: " + this.printProcess(activeProc, true); | |
// print other runnable processes in order of priority | |
var lists = sched.pointers[Squeak.ProcSched_processLists].pointers; | |
for (var priority = lists.length - 1; priority >= 0; priority--) { | |
var process = lists[priority].pointers[Squeak.LinkedList_firstLink]; | |
while (!process.isNil) { | |
result += "\n------------------------------------------"; | |
result += "\nRunnable: " + this.printProcess(process); | |
process = process.pointers[Squeak.Link_nextLink]; | |
} | |
} | |
// print all processes waiting on a semaphore in order of priority | |
var semaClass = this.specialObjects[Squeak.splOb_ClassSemaphore], | |
sema = this.image.someInstanceOf(semaClass), | |
waiting = []; | |
while (sema) { | |
var process = sema.pointers[Squeak.LinkedList_firstLink]; | |
while (!process.isNil) { | |
waiting.push(process); | |
process = process.pointers[Squeak.Link_nextLink]; | |
} | |
sema = this.image.nextInstanceAfter(sema); | |
} | |
waiting.sort(function(a, b){ | |
return b.pointers[Squeak.Proc_priority] - a.pointers[Squeak.Proc_priority]; | |
}); | |
for (var i = 0; i < waiting.length; i++) { | |
result += "\n------------------------------------------"; | |
result += "\nWaiting: " + this.printProcess(waiting[i]); | |
} | |
return result; | |
}, | |
printProcess: function(process, active, indent) { | |
if (!process) { | |
var schedAssn = this.specialObjects[Squeak.splOb_SchedulerAssociation], | |
sched = schedAssn.pointers[Squeak.Assn_value]; | |
process = sched.pointers[Squeak.ProcSched_activeProcess], | |
active = true; | |
} | |
var context = active ? this.activeContext : process.pointers[Squeak.Proc_suspendedContext], | |
priority = process.pointers[Squeak.Proc_priority], | |
stack = this.printStack(context, 20, indent), | |
values = indent && this.logSends ? "" : this.printContext(context) + "\n"; | |
return process.toString() +" at priority " + priority + "\n" + stack + values; | |
}, | |
printByteCodes: function(aMethod, optionalIndent, optionalHighlight, optionalPC) { | |
if (!aMethod) aMethod = this.method; | |
var printer = new Squeak.InstructionPrinter(aMethod, this); | |
return printer.printInstructions(optionalIndent, optionalHighlight, optionalPC); | |
}, | |
logStack: function() { | |
// useful for debugging interactively: | |
// SqueakJS.vm.logStack() | |
console.log(this.printStack() | |
+ this.printActiveContext() + '\n\n' | |
+ this.printByteCodes(this.method, ' ', '=> ', this.pc), | |
this.activeContext.pointers.slice(0, this.sp + 1)); | |
}, | |
willSendOrReturn: function() { | |
// Answer whether the next bytecode corresponds to a Smalltalk | |
// message send or return | |
var byte = this.method.bytes[this.pc]; | |
if (this.method.methodSignFlag()) { | |
if (0x60 <= byte && byte <= 0x7F) { | |
selectorObj = this.specialSelectors[2 * (byte - 0x60)]; | |
} else if (0x80 <= byte && byte <= 0xAF) { | |
selectorObj = this.method.methodGetSelector(byte&0xF); | |
} else if (byte == 0xEA || byte == 0xEB) { | |
this.method.methodGetSelector((this.method.bytes[this.pc+1] >> 3)); // (extA << 5) | |
} else if (0x58 <= byte && byte <= 0x5E) { | |
return true; // return | |
} | |
} else { | |
if (byte >= 120 && byte <= 125) return true; // return | |
if (byte < 131 || byte == 200) return false; | |
if (byte >= 176) return true; // special send or short send | |
if (byte <= 134) { // long sends | |
// long form support demands we check the selector | |
var litIndex; | |
if (byte === 132) { | |
if ((this.method.bytes[this.pc + 1] >> 5) > 1) return false; | |
litIndex = this.method.bytes[this.pc + 2]; | |
} else | |
litIndex = this.method.bytes[this.pc + 1] & (byte === 134 ? 63 : 31); | |
var selectorObj = this.method.methodGetLiteral(litIndex); | |
if (selectorObj.bytesAsString() !== 'blockCopy:') return true; | |
} | |
} | |
return false; | |
}, | |
nextSendSelector: function() { | |
// if the next bytecode corresponds to a Smalltalk | |
// message send, answer the selector | |
var byte = this.method.bytes[this.pc]; | |
var selectorObj; | |
if (this.method.methodSignFlag()) { | |
if (0x60 <= byte && byte <= 0x7F) { | |
selectorObj = this.specialSelectors[2 * (byte - 0x60)]; | |
} else if (0x80 <= byte && byte <= 0xAF) { | |
selectorObj = this.method.methodGetSelector(byte&0xF); | |
} else if (byte == 0xEA || byte == 0xEB) { | |
this.method.methodGetSelector((this.method.bytes[this.pc+1] >> 3)); // (extA << 5) | |
} else { | |
return null; | |
} | |
} else { | |
if (byte < 131 || byte == 200) return null; | |
if (byte >= 0xD0 ) { | |
selectorObj = this.method.methodGetLiteral(byte & 0x0F); | |
} else if (byte >= 0xB0 ) { | |
selectorObj = this.specialSelectors[2 * (byte - 0xB0)]; | |
} else if (byte <= 134) { | |
// long form support demands we check the selector | |
var litIndex; | |
if (byte === 132) { | |
if ((this.method.bytes[this.pc + 1] >> 5) > 1) return null; | |
litIndex = this.method.bytes[this.pc + 2]; | |
} else | |
litIndex = this.method.bytes[this.pc + 1] & (byte === 134 ? 63 : 31); | |
selectorObj = this.method.methodGetLiteral(litIndex); | |
} | |
} | |
if (selectorObj) { | |
var selector = selectorObj.bytesAsString(); | |
if (selector !== 'blockCopy:') return selector; | |
} | |
}, | |
}); | |