"use strict"; /* * Copyright (c) 2013-2024 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. */ Squeak.Object.subclass('Squeak.ObjectSpur', 'initialization', { initInstanceOf: function(aClass, indexableSize, hash, nilObj) { this.sqClass = aClass; this.hash = hash; var instSpec = aClass.pointers[Squeak.Class_format], instSize = instSpec & 0xFFFF, format = (instSpec>>16) & 0x1F this._format = format; if (format < 12) { if (format < 10) { if (instSize + indexableSize > 0) this.pointers = this.fillArray(instSize + indexableSize, nilObj); } else // Words if (indexableSize > 0) if (aClass.isFloatClass) { this.isFloat = true; this.float = 0.0; } else this.words = new Uint32Array(indexableSize); } else // Bytes if (indexableSize > 0) { // this._format |= -indexableSize & 3; //deferred to writeTo() this.bytes = new Uint8Array(indexableSize); //Methods require further init of pointers } // Definition of Spur's format code... // // 0 = 0 sized objects (UndefinedObject True False et al) // 1 = non-indexable objects with inst vars (Point et al) // 2 = indexable objects with no inst vars (Array et al) // 3 = indexable objects with inst vars (MethodContext AdditionalMethodState et al) // 4 = weak indexable objects with inst vars (WeakArray et al) // 5 = weak non-indexable objects with inst vars (ephemerons) (Ephemeron) // 6 = unused // 7 = immediates (SmallInteger, Character) // 8 = unused // 9 = 64-bit indexable // 10-11 = 32-bit indexable (Bitmap) (plus one odd bit, unused in 32-bits) // 12-15 = 16-bit indexable (plus two odd bits, one unused in 32-bits) // 16-23 = 8-bit indexable (plus three odd bits, one unused in 32-bits) // 24-31 = compiled methods (CompiledMethod) (plus three odd bits, one unused in 32-bits) }, installFromImage: function(oopMap, rawBits, classTable, floatClass, littleEndian, getCharacter, is64Bit) { //Install this object by decoding format, and rectifying pointers var classID = this.sqClass; if (classID < 32) throw Error("Invalid class ID: " + classID); this.sqClass = classTable[classID]; if (!this.sqClass) throw Error("Class ID not in class table: " + classID); var bits = rawBits.get(this.oop), nWords = bits.length; switch (this._format) { case 0: // zero sized object // Pharo bug: Pharo 6.0 still has format 0 objects that actually do have inst vars // https://pharo.fogbugz.com/f/cases/19010/ImmediateLayout-and-EphemeronLayout-have-wrong-object-format // so we pretend these are regular objects and rely on nWords case 1: // only inst vars case 2: // only indexed vars case 3: // inst vars and indexed vars case 4: // only indexed vars (weak) case 5: // only inst vars (weak) if (nWords > 0) { var oops = bits; // endian conversion was already done this.pointers = this.decodePointers(nWords, oops, oopMap, getCharacter, is64Bit); } break; case 11: // 32 bit array (odd length in 64 bits) nWords--; this._format = 10; case 10: // 32 bit array if (this.sqClass === floatClass) { //These words are actually a Float this.isFloat = true; this.float = this.decodeFloat(bits, littleEndian, true); } else if (nWords > 0) { this.words = this.decodeWords(nWords, bits, littleEndian); } break; case 12: // 16 bit array case 13: // 16 bit array (odd length) throw Error("16 bit arrays not supported yet"); case 20: // 8 bit array, length-4 (64 bit image) case 21: // ... length-5 case 22: // ... length-6 case 23: // ... length-7 nWords--; this._format -= 4; // fall through case 16: // 8 bit array case 17: // ... length-1 case 18: // ... length-2 case 19: // ... length-3 if (nWords > 0) this.bytes = this.decodeBytes(nWords, bits, 0, this._format & 3); break; case 28: // CompiledMethod, length-4 (64 bit image) case 29: // ... length-5 case 30: // ... length-6 case 31: // ... length-7 nWords--; this._format -= 4; // fall through case 24: // CompiledMethod case 25: // ... length-1 case 26: // ... length-2 case 27: // ... length-3 var rawHeader = this.decodeWords(1, bits, littleEndian)[0]; var intHeader = rawHeader >> (is64Bit ? 3 : 1); var numLits = intHeader & 0x7FFF, oops = is64Bit ? this.decodeWords64(numLits+1, bits, littleEndian) : this.decodeWords(numLits+1, bits, littleEndian), ptrWords = is64Bit ? (numLits + 1) * 2 : numLits + 1; this.pointers = this.decodePointers(numLits+1, oops, oopMap, getCharacter, is64Bit); //header+lits this.bytes = this.decodeBytes(nWords-ptrWords, bits, ptrWords, this._format & 3); if (is64Bit) this.pointers[0] = (bits[1] & 0x80000000) | intHeader; // fix header break; default: throw Error("Unknown object format: " + this._format); } this.mark = false; // for GC }, decodeWords64: function(nWords, theBits, littleEndian) { // we assume littleEndian for now var words = new Array(nWords); for (var i = 0; i < nWords; i++) { var lo = theBits[i*2], hi = theBits[i*2+1]; words[i] = Squeak.word64FromUint32(hi, lo); } return words; }, decodePointers: function(nWords, theBits, oopMap, getCharacter, is64Bit) { //Convert immediate objects and look up object pointers in oopMap var ptrs = new Array(nWords); for (var i = 0; i < nWords; i++) { var oop = theBits[i]; // in 64 bits, oops > 53 bits are read as [hi, lo] if (typeof oop !== "number") { if ((oop[1] & 7) === 4) { ptrs[i] = this.decodeSmallFloat(oop[0], oop[1], is64Bit); } else if ((oop[1] & 7) === 1) { ptrs[i] = is64Bit.makeLargeFromSmall(oop[0], oop[1]); } else if ((oop[1] & 7) === 2) { throw Error("Large Immediate Characters not implemented yet"); } else { throw Error("Large OOPs not implemented yet"); } } else if ((oop & 1) === 1) { // SmallInteger if (is64Bit) { // if it fits in a 31 bit SmallInt ... ptrs[i] = (oop >= 0 ? oop <= 0x1FFFFFFFF : oop >= -0x200000000) ? oop / 4 >> 1 // ... then convert directly, otherwise make large : is64Bit.makeLargeFromSmall((oop - (oop >>> 0)) / 0x100000000 >>> 0, oop >>> 0); } else ptrs[i] = oop >> 1; } else if ((oop & 3) === 2) { // Character if (oop < 0 || oop > 0x1FFFFFFFF) throw Error("Large Immediate Characters not implemented yet"); ptrs[i] = getCharacter(oop >>> (is64Bit ? 3 : 2)); } else if (is64Bit && (oop & 7) === 4) { // SmallFloat ptrs[i] = this.decodeSmallFloat((oop - (oop >>> 0)) / 0x100000000 >>> 0, oop >>> 0, is64Bit); } else { // Object ptrs[i] = oopMap.get(oop) || 42424242; // when loading a context from image segment, there is // garbage beyond its stack pointer, resulting in the oop // not being found in oopMap. We just fill in an arbitrary // SmallInteger - it's never accessed anyway // until 64 bit is working correctly, leave this here as a check ... if (ptrs[i] === 42424242) debugger; } } return ptrs; }, decodeSmallFloat: function(hi, lo, is64Bit) { // SmallFloats are stored with full 52 bit mantissa, but shortened exponent. // The lowest 3 bits are tags, the next is the sign bit var newHi = 0, newLo = 0, sign = (lo & 8) << (32-4), // shift sign bit to msb isZero = (hi | (lo & 0xFFFFFFF0)) === 0; // ignore sign and tag bits if (isZero) { // zero is special - can be positive or negative newHi = sign; } else { // shift everything right by 4, fix exponent, add sign newHi = (hi >>> 4) + 0x38000000 | sign; newLo = (lo >>> 4) | (hi & 0xF) << (32-4); // 1023 is the bias of the 11-bit exponent in an IEEE 754 64-bit float, // and 127 is the bias of our 8-bit exponent. 1023-127 == 0x380 } return is64Bit.makeFloat(new Uint32Array([newLo, newHi])); }, overhead64: function(bits) { // the number of bytes this object is larger in 64 bits than in 32 bits // (due to 8-byte alignment even in 32 bits this only affects pointer objects) var overhead = 0; var words32 = 0; var words64 = 0; if (this._format <= 5) { // pointer objects overhead = bits.length & ~1; // each oop occupied 2 words instead of 1 ... // ... but odd lengths get padded so we subtract 1 // words32 === words64 because same number of oops } else if (this._format >= 24) { // compiled methods var numLits = (bits[0] >> 3) & 0x7FFF; // assumes 64 bit little endian var overhead = numLits + 1; // each oop occupied 2 words instead of 1 ... var oddOops = (overhead & 1) === 1; var oddBytes = this._format >= 28; // ... odd-word lengths would get padded so we subtract 1, // but if there is also odd-word bytecodes it cancels out so we save 1 word instead if (oddOops) overhead += oddBytes ? +1 : -1; words64 = bits.length / 2; words32 = bits.length - overhead; } else { // non-pointer objects have no oop overhead words32 = bits.length; words64 = words32 / 2; } // we need an extra header in 32 bits if we now use more words than before return { bytes: overhead * 4, sizeHeader: words32 >= 255 && words64 < 255, } }, initInstanceOfChar: function(charClass, unicode) { this.oop = (unicode << 2) | 2; this.sqClass = charClass; this.hash = unicode; this._format = 7; this.mark = true; // stays always marked so not traced by GC }, initInstanceOfFloat: function(floatClass, bits) { this.sqClass = floatClass; this.hash = 0; this._format = 10; this.isFloat = true; this.float = this.decodeFloat(bits, true, true); }, initInstanceOfLargeInt: function(largeIntClass, size) { this.sqClass = largeIntClass; this.hash = 0; this._format = 16; // this._format |= -indexableSize & 3; //deferred to writeTo() this.bytes = new Uint8Array(size); }, classNameFromImage: function(oopMap, rawBits) { var name = oopMap.get(rawBits.get(this.oop)[Squeak.Class_name]); if (name && name._format >= 16 && name._format < 24) { var bits = rawBits.get(name.oop), bytes = name.decodeBytes(bits.length, bits, 0, name._format & 7); return Squeak.bytesAsString(bytes); } return "Class"; }, renameFromImage: function(oopMap, rawBits, classTable) { var classObj = classTable[this.sqClass]; if (!classObj) return this; var instProto = classObj.instProto || classObj.classInstProto(classObj.classNameFromImage(oopMap, rawBits)); if (!instProto) return this; var renamedObj = new instProto; // Squeak.SpurObject renamedObj.oop = this.oop; renamedObj.sqClass = this.sqClass; renamedObj._format = this._format; renamedObj.hash = this.hash; return renamedObj; }, }, 'accessing', { instSize: function() {//same as class.classInstSize, but faster from format if (this._format < 2) return this.pointersSize(); //fixed fields only return this.sqClass.classInstSize(); }, indexableSize: function(primHandler) { var fmt = this._format; if (fmt < 2) return -1; //not indexable if (fmt === 3 && primHandler.vm.isContext(this)) return this.pointers[Squeak.Context_stackPointer]; // no access beyond top of stacks if (fmt < 6) return this.pointersSize() - this.instSize(); // pointers if (fmt < 12) return this.wordsSize(); // words if (fmt < 16) return this.shortsSize(); // shorts if (fmt < 24) return this.bytesSize(); // bytes return 4 * this.pointersSize() + this.bytesSize(); // methods }, snapshotSize: function() { // words of extra object header and body this object would take up in image snapshot // body size includes header size that is always present var nWords = this.isFloat ? 2 : this.words ? this.words.length : this.pointers ? this.pointers.length : 0; // methods have both pointers and bytes if (this.bytes) nWords += (this.bytes.length + 3) >>> 2; var extraHeader = nWords >= 255 ? 2 : 0; nWords += nWords & 1; // align to 8 bytes nWords += 2; // one 64 bit header always present if (nWords < 4) nWords = 4; // minimum object size return {header: extraHeader, body: nWords}; }, writeTo: function(data, pos, littleEndian, objToOop) { var nWords = this.isFloat ? 2 : this.words ? this.words.length : this.pointers ? this.pointers.length : 0; if (this.bytes) { nWords += (this.bytes.length + 3) >>> 2; this._format |= -this.bytes.length & 3; } var beforePos = pos, formatAndClass = (this._format << 24) | (this.sqClass.hash & 0x003FFFFF), sizeAndHash = (nWords << 24) | (this.hash & 0x003FFFFF); // write extra header if needed if (nWords >= 255) { data.setUint32(pos, nWords, littleEndian); pos += 4; sizeAndHash = (255 << 24) | (this.hash & 0x003FFFFF); data.setUint32(pos, sizeAndHash, littleEndian); pos += 4; } // write regular header data.setUint32(pos, formatAndClass, littleEndian); pos += 4; data.setUint32(pos, sizeAndHash, littleEndian); pos += 4; // now write body, if any if (this.isFloat) { data.setFloat64(pos, this.float, littleEndian); pos += 8; } else if (this.words) { for (var i = 0; i < this.words.length; i++) { data.setUint32(pos, this.words[i], littleEndian); pos += 4; } } else if (this.pointers) { var startIndex = 0; if (this._format >= 24) { // preserve signFlag in method header var mask = this.methodSignFlag() ? 0x80000000 : 0; var taggedHeader = this.pointers[0] << 1 | 1 | mask; data.setUint32(pos, taggedHeader, littleEndian); pos += 4; startIndex = 1; } for (var i = startIndex; i < this.pointers.length; i++) { data.setUint32(pos, objToOop(this.pointers[i]), littleEndian); pos += 4; } } // no "else" because CompiledMethods have both pointers and bytes if (this.bytes) { for (var i = 0; i < this.bytes.length; i++) data.setUint8(pos++, this.bytes[i]); // skip to next word pos += -this.bytes.length & 3; } // minimum object size is 16, align to 8 bytes if (nWords === 0) pos += 8; else pos += (nWords & 1) * 4; // done if (pos !== beforePos + this.totalBytes()) throw Error("written size does not match"); return pos; }, }, 'testing', { isBytes: function() { var fmt = this._format; return fmt >= 16 && fmt <= 23; }, isPointers: function() { return this._format <= 6; }, isWords: function() { return this._format === 10; }, isWordsOrBytes: function() { var fmt = this._format; return fmt === 10 || (fmt >= 16 && fmt <= 23); }, isWeak: function() { return this._format === 4; }, isMethod: function() { return this._format >= 24; }, sameFormats: function(a, b) { return a < 16 ? a === b : (a & 0xF8) === (b & 0xF8); }, }, 'as class', { defaultInst: function() { return Squeak.ObjectSpur; }, classInstFormat: function() { return (this.pointers[Squeak.Class_format] >> 16) & 0x1F; }, classInstSize: function() { // this is a class, answer number of named inst vars return this.pointers[Squeak.Class_format] & 0xFFFF; }, classInstIsBytes: function() { var fmt = this.classInstFormat(); return fmt >= 16 && fmt <= 23; }, classInstIsPointers: function() { return this.classInstFormat() <= 6; }, classByteSizeOfInstance: function(nElements) { var format = this.classInstFormat(), nWords = this.classInstSize(); if (format < 9) nWords += nElements; // 32 bit else if (format >= 16) nWords += (nElements + 3) / 4 | 0; // 8 bit else if (format >= 12) nWords += (nElements + 1) / 2 | 0; // 16 bit else if (format >= 10) nWords += nElements; // 32 bit else nWords += nElements * 2; // 64 bit nWords += nWords & 1; // align to 64 bits nWords += nWords >= 255 ? 4 : 2; // header words if (nWords < 4) nWords = 4; // minimum object size return nWords * 4; }, }, 'as compiled block', { blockOuterCode: function() { return this.pointers[this.pointers.length - 1]; }, }, 'as method', { methodSignFlag: function() { return this.pointers[0] < 0; }, methodNumLits: function() { return this.pointers[0] & 0x7FFF; }, methodPrimitiveIndex: function() { if ((this.pointers[0] & 0x10000) === 0) return 0; return this.bytes[1] + 256 * this.bytes[2]; }, methodAsString: function() { var cls = this.pointers[this.pointers.length - 1].pointers[Squeak.ClassBinding_value]; var selector = this.pointers[this.pointers.length - 2]; if (selector.pointers) selector = selector.pointers[Squeak.AdditionalMethodState_selector]; return cls.className() + ">>" + selector.bytesAsString(); }, });