From a2b57cf9611e91c75adbb1f113f519dd53756eba Mon Sep 17 00:00:00 2001 From: Vanessa Freudenberg Date: Wed, 5 Jun 2024 18:01:34 -0700 Subject: [PATCH 01/10] Readme for V2 --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index 026d06d9..8932deca 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,34 @@ +V2 BRANCH +========= +This is the work-in-progress branch for SqueakJS 2.0. Things I want to change: + +* each fixed inst var gets its own property instead for direct access instead of being indexed in `pointers[]`. There will be a compatibility accessor for primitives that use indexed access. + + Still need to decide between named inst vars (using inst var names from image) or suffixed (like `p0`, `p1`, ...) + + The goal is faster access than via the `pointers[]` array. + Also, nicer debuggability if we use actual names. + +* new high-performance JIT without context allocation but direct function calls, args passed directly via function parameters, and direct instance var access (see above) + + The goal is to make the jitted methods look as close to "normal" JavaScript functions as possible, so that the JS JIT can optimize them, even with inlining etc. + +* (maybe) use `WeakRef` and `WeakMap`? All JS runtimes now support weak objects (`WeakRef` is still pretty new, since 2021). + + The goal would be to have faster GCs while still supporting object enumeration. + +* (maybe) `BigInt` for large integer primitives? Supported in browsers since 2020 (and allowed to fail if not available). Need to measure fastest way to convert from/to `Uint8Array` representation. + + The goal is faster LargeInteger calculations. + +* (maybe) no more `.oop` property: it's not needed at runtime, and updating it makes the GC slower. + + Goal: faster GC + +* (maybe) export as 64 bit image: on load, 64-bit images are converted to 32 bits. On export, we could store them as 64 bits (and if we get rid of the `.oop` as mentioned above it may actually be almost as fast as snapshotting in 32 bits) + + The goal here is compatibility with other VMs, which on some systems only run 64 bit images + SqueakJS: A Squeak VM for the Web and Node.js ============================================= From 4b63e3d5c6596fb5c4a2e2d0a419f16dc4c290ef Mon Sep 17 00:00:00 2001 From: Vanessa Freudenberg Date: Wed, 5 Jun 2024 18:06:52 -0700 Subject: [PATCH 02/10] More README --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8932deca..dba4a6dd 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This is the work-in-progress branch for SqueakJS 2.0. Things I want to change: The goal is faster access than via the `pointers[]` array. Also, nicer debuggability if we use actual names. -* new high-performance JIT without context allocation but direct function calls, args passed directly via function parameters, and direct instance var access (see above) +* new high-performance JIT without per-frame context allocation, but instead using direct function calls, function temps as stack, args passed directly via function parameters, and direct instance var access (see above). Contexts would only be allocated if needed The goal is to make the jitted methods look as close to "normal" JavaScript functions as possible, so that the JS JIT can optimize them, even with inlining etc. @@ -17,6 +17,10 @@ This is the work-in-progress branch for SqueakJS 2.0. Things I want to change: The goal would be to have faster GCs while still supporting object enumeration. +* (maybe) use WASM for BitBlt etc. To avoid copying in and out of the WASM heap, we could use binary arrays allocated via WASM (but would need to implement GC for that) + + Goal: speed + * (maybe) `BigInt` for large integer primitives? Supported in browsers since 2020 (and allowed to fail if not available). Need to measure fastest way to convert from/to `Uint8Array` representation. The goal is faster LargeInteger calculations. From 107ff7cdf661e1165230b20b6290a865672b0aa4 Mon Sep 17 00:00:00 2001 From: Vanessa Freudenberg Date: Wed, 5 Jun 2024 18:14:27 -0700 Subject: [PATCH 03/10] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index dba4a6dd..3b5f56c2 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ This is the work-in-progress branch for SqueakJS 2.0. Things I want to change: The goal here is compatibility with other VMs, which on some systems only run 64 bit images +Feedback and ideas: please comment on the [Pull Request](https://github.com/codefrau/SqueakJS/pull/168) or use the [vm-dev](http://lists.squeak.org/mailman/listinfo/vm-dev) mailing list and `#squeakjs` channel on the [Squeak Slack](https://join.slack.com/t/squeak/shared_invite/zt-2ahdbewgl-56nPdkf1hYACBmc8xCOXRQ). + SqueakJS: A Squeak VM for the Web and Node.js ============================================= From 05f9c17861eaf83d7c251b71fae7ba85493e28c7 Mon Sep 17 00:00:00 2001 From: Vanessa Freudenberg Date: Wed, 5 Jun 2024 18:30:49 -0700 Subject: [PATCH 04/10] Add link to JIT experiments --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3b5f56c2..cd7234ea 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This is the work-in-progress branch for SqueakJS 2.0. Things I want to change: The goal is faster access than via the `pointers[]` array. Also, nicer debuggability if we use actual names. -* new high-performance JIT without per-frame context allocation, but instead using direct function calls, function temps as stack, args passed directly via function parameters, and direct instance var access (see above). Contexts would only be allocated if needed +* new high-performance JIT without per-frame context allocation, but instead using direct function calls, function temps as stack, args passed directly via function parameters, and direct instance var access (see above). Contexts would only be allocated if needed (also see my [JIT experiments](https://squeak.js.org/docs/jit.md.html)) The goal is to make the jitted methods look as close to "normal" JavaScript functions as possible, so that the JS JIT can optimize them, even with inlining etc. From 3f2a86f12ac4f4b61703ba814bc4ff93277b6123 Mon Sep 17 00:00:00 2001 From: Vanessa Freudenberg Date: Thu, 6 Jun 2024 19:59:49 -0700 Subject: [PATCH 05/10] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cd7234ea..091b2f51 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This is the work-in-progress branch for SqueakJS 2.0. Things I want to change: The goal is faster access than via the `pointers[]` array. Also, nicer debuggability if we use actual names. -* new high-performance JIT without per-frame context allocation, but instead using direct function calls, function temps as stack, args passed directly via function parameters, and direct instance var access (see above). Contexts would only be allocated if needed (also see my [JIT experiments](https://squeak.js.org/docs/jit.md.html)) +* new high-performance JIT without per-frame context allocation, but instead using direct function calls, function temps as stack, args passed directly via function parameters, and direct instance var access (see above). Contexts would only be allocated if needed (also see the existing [discussion](https://github.com/codefrau/SqueakJS/issues/121) and my [JIT experiments](https://squeak.js.org/docs/jit.md.html)) The goal is to make the jitted methods look as close to "normal" JavaScript functions as possible, so that the JS JIT can optimize them, even with inlining etc. @@ -21,7 +21,7 @@ This is the work-in-progress branch for SqueakJS 2.0. Things I want to change: Goal: speed -* (maybe) `BigInt` for large integer primitives? Supported in browsers since 2020 (and allowed to fail if not available). Need to measure fastest way to convert from/to `Uint8Array` representation. +* (maybe) `BigInt` for large integer primitives? Supported in browsers since 2020 (and allowed to fail if not available). Need to measure fastest way to convert from/to `Uint8Array` representation. (this is actually independent of v2, see the existing [discussion](https://github.com/codefrau/SqueakJS/issues/37)) The goal is faster LargeInteger calculations. From d35d3d42203474094ca7ca2d1004f91c10a8040f Mon Sep 17 00:00:00 2001 From: Vanessa Freudenberg Date: Thu, 6 Jun 2024 20:47:00 -0700 Subject: [PATCH 06/10] we can use modern JS now --- vm.image.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/vm.image.js b/vm.image.js index 86b30b12..641e29dc 100644 --- a/vm.image.js +++ b/vm.image.js @@ -324,12 +324,8 @@ Object.subclass('Squeak.Image', var stop = done + (this.oldSpaceCount / 20 | 0); // do it in 20 chunks while (obj && done < stop) { obj.installFromImage(oopMap, rawBits, compactClasses, floatClass, littleEndian, nativeFloats, is64Bit && { - makeFloat: function makeFloat(bits) { - return this.instantiateFloat(bits); - }.bind(this), - makeLargeFromSmall: function makeLargeFromSmall(hi, lo) { - return this.instantiateLargeFromSmall(hi, lo); - }.bind(this), + makeFloat: bits => this.instantiateFloat(bits), + makeLargeFromSmall: (hi, lo) => this.instantiateLargeFromSmall(hi, lo), }); obj = obj.nextObject; done++; From e393cbd3fa7199b4db0f1c8578a0ba0f42c0098b Mon Sep 17 00:00:00 2001 From: Vanessa Freudenberg Date: Tue, 11 Jun 2024 16:51:18 -0700 Subject: [PATCH 07/10] Rename fromImage to fromBits --- vm.image.js | 14 +++++++------- vm.object.js | 10 +++++----- vm.object.spur.js | 8 ++++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/vm.image.js b/vm.image.js index 641e29dc..d9109587 100644 --- a/vm.image.js +++ b/vm.image.js @@ -188,7 +188,7 @@ Object.subclass('Squeak.Image', hash = (header>>>17) & 4095, bits = readBits(nWords, format < 5); var object = new Squeak.Object(); - object.initFromImage(oop, classInt, format, hash); + object.initFromBits(oop, classInt, format, hash); if (classInt < 32) object.hash |= 0x10000000; // see fixCompactOops() if (prevObj) prevObj.nextObject = object; this.oldSpaceCount++; @@ -236,7 +236,7 @@ Object.subclass('Squeak.Image', // low class ids are internal to Spur if (classID >= 32) { var object = new Squeak.ObjectSpur(); - object.initFromImage(oop, classID, format, hash); + object.initFromBits(oop, classID, format, hash); if (prevObj) prevObj.nextObject = object; this.oldSpaceCount++; prevObj = object; @@ -296,7 +296,7 @@ Object.subclass('Squeak.Image', prevObj = null; while (object) { prevObj = renamedObj; - renamedObj = object.renameFromImage(oopMap, rawBits, cc); + renamedObj = object.renameFromBits(oopMap, rawBits, cc); if (prevObj) prevObj.nextObject = renamedObj; else this.firstOldObject = renamedObj; oopMap[oldBaseAddr + object.oop] = renamedObj; @@ -310,7 +310,7 @@ Object.subclass('Squeak.Image', var splObs = oopMap[specialObjectsOopInt]; var compactClasses = rawBits[oopMap[rawBits[splObs.oop][Squeak.splOb_CompactClasses]].oop]; var floatClass = oopMap[rawBits[splObs.oop][Squeak.splOb_ClassFloat]]; - // Spur needs different arguments for installFromImage() + // Spur needs different arguments for installFromBits() if (this.isSpur) { this.initImmediateClasses(oopMap, rawBits, splObs); compactClasses = this.spurClassTable(oopMap, rawBits, classPages, splObs); @@ -323,7 +323,7 @@ Object.subclass('Squeak.Image', if (obj) { var stop = done + (this.oldSpaceCount / 20 | 0); // do it in 20 chunks while (obj && done < stop) { - obj.installFromImage(oopMap, rawBits, compactClasses, floatClass, littleEndian, nativeFloats, is64Bit && { + obj.installFromBits(oopMap, rawBits, compactClasses, floatClass, littleEndian, nativeFloats, is64Bit && { makeFloat: bits => this.instantiateFloat(bits), makeLargeFromSmall: (hi, lo) => this.instantiateLargeFromSmall(hi, lo), }); @@ -1136,7 +1136,7 @@ Object.subclass('Squeak.Image', bits = readBits(nWords, format); var object = new Squeak.Object(); - object.initFromImage(oop + oopOffset, classInt, format, hash); + object.initFromBits(oop + oopOffset, classInt, format, hash); prevObj.nextObject = object; this.oldSpaceCount++; prevObj = object; @@ -1160,7 +1160,7 @@ Object.subclass('Squeak.Image', floatClass = this.specialObjectsArray.pointers[Squeak.splOb_ClassFloat], obj = roots; do { - obj.installFromImage(oopMap, rawBits, compactClassOops, floatClass, littleEndian, nativeFloats); + obj.installFromBits(oopMap, rawBits, compactClassOops, floatClass, littleEndian, nativeFloats); obj = obj.nextObject; } while (obj !== endMarker); return roots; diff --git a/vm.object.js b/vm.object.js index a0e8c1e0..e2c70634 100644 --- a/vm.object.js +++ b/vm.object.js @@ -78,14 +78,14 @@ Object.subclass('Squeak.Object', if (original.bytes) this.bytes = new Uint8Array(original.bytes); // copy } }, - initFromImage: function(oop, cls, fmt, hsh) { + initFromBits: function(oop, cls, fmt, hsh) { // initial creation from Image, with unmapped data this.oop = oop; this.sqClass = cls; this._format = fmt; this.hash = hsh; }, - classNameFromImage: function(oopMap, rawBits) { + classNameFromBits: function(oopMap, rawBits) { var name = oopMap[rawBits[this.oop][Squeak.Class_name]]; if (name && name._format >= 8 && name._format < 12) { var bits = rawBits[name.oop], @@ -94,10 +94,10 @@ Object.subclass('Squeak.Object', } return "Class"; }, - renameFromImage: function(oopMap, rawBits, ccArray) { + renameFromBits: function(oopMap, rawBits, ccArray) { var classObj = this.sqClass < 32 ? oopMap[ccArray[this.sqClass-1]] : oopMap[this.sqClass]; if (!classObj) return this; - var instProto = classObj.instProto || classObj.classInstProto(classObj.classNameFromImage(oopMap, rawBits)); + var instProto = classObj.instProto || classObj.classInstProto(classObj.classNameFromBits(oopMap, rawBits)); if (!instProto) return this; var renamedObj = new instProto; // Squeak.Object renamedObj.oop = this.oop; @@ -106,7 +106,7 @@ Object.subclass('Squeak.Object', renamedObj.hash = this.hash; return renamedObj; }, - installFromImage: function(oopMap, rawBits, ccArray, floatClass, littleEndian, nativeFloats) { + installFromBits: function(oopMap, rawBits, ccArray, floatClass, littleEndian, nativeFloats) { //Install this object by decoding format, and rectifying pointers var ccInt = this.sqClass; // map compact classes diff --git a/vm.object.spur.js b/vm.object.spur.js index 1b1dd1e7..222347d4 100644 --- a/vm.object.spur.js +++ b/vm.object.spur.js @@ -64,7 +64,7 @@ Squeak.Object.subclass('Squeak.ObjectSpur', // 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) { + installFromBits: 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); @@ -268,7 +268,7 @@ Squeak.Object.subclass('Squeak.ObjectSpur', // this._format |= -indexableSize & 3; //deferred to writeTo() this.bytes = new Uint8Array(size); }, - classNameFromImage: function(oopMap, rawBits) { + classNameFromBits: function(oopMap, rawBits) { var name = oopMap[rawBits[this.oop][Squeak.Class_name]]; if (name && name._format >= 16 && name._format < 24) { var bits = rawBits[name.oop], @@ -277,10 +277,10 @@ Squeak.Object.subclass('Squeak.ObjectSpur', } return "Class"; }, - renameFromImage: function(oopMap, rawBits, classTable) { + renameFromBits: function(oopMap, rawBits, classTable) { var classObj = classTable[this.sqClass]; if (!classObj) return this; - var instProto = classObj.instProto || classObj.classInstProto(classObj.classNameFromImage(oopMap, rawBits)); + var instProto = classObj.instProto || classObj.classInstProto(classObj.classNameFromBits(oopMap, rawBits)); if (!instProto) return this; var renamedObj = new instProto; // Squeak.SpurObject renamedObj.oop = this.oop; From b6a32befdc537ef28431378b9c1971480be3c086 Mon Sep 17 00:00:00 2001 From: Vanessa Freudenberg Date: Tue, 11 Jun 2024 23:33:31 -0700 Subject: [PATCH 08/10] use actual inst var names This uses Smalltalk inst var names for property names prefixed with a `$` (e.g. a Point would use `{ $x: 3, $y: 4 }` in JS). Indexable fields are in an array named `$$` (e.g. in Context you would have `{$sender: ..., $$: [tmp0, tmp1, ...]}`. The `.pointers` property is now a proxy to allow the rest of the VM and primitives to still work. The JIT and optimized primitives will not use this proxy. --- vm.image.js | 24 ++++++ vm.js | 2 +- vm.object.js | 196 ++++++++++++++++++++++++++++++++++++++++------ vm.object.spur.js | 56 ++++++++++--- vm.primitives.js | 8 +- 5 files changed, 247 insertions(+), 39 deletions(-) diff --git a/vm.image.js b/vm.image.js index d9109587..34e6ff7f 100644 --- a/vm.image.js +++ b/vm.image.js @@ -317,6 +317,9 @@ Object.subclass('Squeak.Image', nativeFloats = this.getCharacter.bind(this); this.initSpurOverrides(); } + // figure out if Class_instVars is 3 or 4 or unknown + Squeak.Class_instVars = this.detectClassInstVarIndex(splObs, oopMap, rawBits); + // now "install" the objects, i.e. decode the bits into proper references, etc. var obj = this.firstOldObject, done = 0; var mapSomeObjects = function() { @@ -360,6 +363,27 @@ Object.subclass('Squeak.Image', self.setTimeout(mapSomeObjectsAsync, 0); } }, + detectClassInstVarIndex: function(splObs, oopMap, rawBits) { + // the VM really should only make assumptions about inst vars 0-2 + // but we want to use the actual instance variable names + // which are at index 3 or 4 in the class + var classPoint = oopMap[rawBits[splObs.oop][Squeak.splOb_ClassPoint]]; + var classBits = rawBits[classPoint.oop]; + // we check if the array #(x y) is anywhere in the Point class + // starting at index 3 (indices 0-2 are known to the VM) + for (var index = 3; index < classBits.length; index++) { + var names = oopMap[classBits[index]]; if (!names) continue; + var namesBits = rawBits[names.oop]; if (namesBits.length !== 2) continue; + var x = oopMap[namesBits[0]]; + var xBits = rawBits[x.oop]; + if (String.fromCharCode(xBits[0]) !== 'x') continue; + var y = oopMap[namesBits[1]]; + var yBits = rawBits[y.oop]; + if (String.fromCharCode(yBits[0]) !== 'y') continue; + return index; + } + return 0; // unknown + }, decorateKnownObjects: function() { var splObjs = this.specialObjectsArray.pointers; splObjs[Squeak.splOb_NilObject].isNil = true; diff --git a/vm.js b/vm.js index 3f8c4df7..62bb13cf 100644 --- a/vm.js +++ b/vm.js @@ -109,7 +109,7 @@ Object.extend(Squeak, Class_superclass: 0, Class_mdict: 1, Class_format: 2, - Class_instVars: null, // 3 or 4 depending on image, see instVarNames() + Class_instVars: null, // 3 or 4 or unknown depending on image, see detectClassInstVarIndex() Class_name: 6, // ClassBinding layout: ClassBinding_value: 1, diff --git a/vm.object.js b/vm.object.js index e2c70634..3847579c 100644 --- a/vm.object.js +++ b/vm.object.js @@ -32,8 +32,19 @@ Object.subclass('Squeak.Object', if (this._format < 8) { if (this._format != 6) { - if (instSize + indexableSize > 0) - this.pointers = this.fillArray(instSize + indexableSize, nilObj); + if (instSize > 0) { + const vars = aClass.allInstVarNames(); + for (var i = 0; i < vars.length; i++) { + this[vars[i]] = nilObj; + } + this.pointers = indexableSize > 0 + ? this.instVarAndIndexableProxy(vars) + : this.instVarProxy(vars); + } + if (indexableSize > 0) { + this.$$ = this.fillArray(indexableSize, nilObj); + if (!this.pointers) this.pointers = this.$$; + } } else // Words if (indexableSize > 0) if (aClass.isFloatClass) { @@ -73,7 +84,19 @@ Object.subclass('Squeak.Object', this.isFloat = original.isFloat; this.float = original.float; } else { - if (original.pointers) this.pointers = original.pointers.slice(0); // copy + const vars = original.sqClass.allInstVarNames(); + if (vars && vars.length) { + for (var i = 0; i < vars.length; i++) { + this[vars[i]] = original[vars[i]]; + } + this.pointers = original.$$ + ? this.instVarAndIndexableProxy(vars) + : this.instVarProxy(vars); + } + if (original.$$) { + this.$$ = [...original.$$]; // copy + if (!this.pointers) this.pointers = this.$$; + } if (original.words) this.words = new Uint32Array(original.words); // copy if (original.bytes) this.bytes = new Uint8Array(original.bytes); // copy } @@ -85,14 +108,56 @@ Object.subclass('Squeak.Object', this._format = fmt; this.hash = hsh; }, + stringFromBits: function(rawBits) { + if (this._format < 8 || this._format >= 12) return ''; + var bits = rawBits[this.oop], + bytes = this.decodeBytes(bits.length, bits, 0, this._format & 3); + return Squeak.bytesAsString(bytes); + }, classNameFromBits: function(oopMap, rawBits) { var name = oopMap[rawBits[this.oop][Squeak.Class_name]]; - if (name && name._format >= 8 && name._format < 12) { - var bits = rawBits[name.oop], - bytes = name.decodeBytes(bits.length, bits, 0, name._format & 3); - return Squeak.bytesAsString(bytes); + return name?.stringFromBits(rawBits) || "Class"; + }, + classInstSizeFromBits: function(rawBits) { + var spec = rawBits[this.oop][Squeak.Class_format] >> 1; + return ((spec >> 10) & 0xC0) + ((spec >> 1) & 0x3F) - 1; + }, + classOwnInstVarNamesFromBits: function(oopMap, rawBits) { + const ownInstVarNames = []; + const myBits = rawBits[this.oop]; + if (Squeak.Class_instVars > 0) { + const varNamesArray = rawBits[myBits[Squeak.Class_instVars]]; + for (let i = 0; i < varNamesArray.length; i++) { + const varName = oopMap[varNamesArray[i]]; + const varStr = varName.stringFromBits(rawBits); + if (!varStr) { debugger ; throw Error("classOwnInstVarNamesFromBits: not a string"); } + ownInstVarNames.push('$' + varStr); // add $ to avoid name clashes + } } - return "Class"; + return ownInstVarNames; + }, + classAllInstVarNamesFromBits: function(oopMap, rawBits) { + if (this._classAllInstVarNames) return this._classAllInstVarNames; + let names; + const instSize = this.classInstSizeFromBits(rawBits); + if (instSize === 0) { + names = []; + } else if (Squeak.Class_instVars > 0) { + const ownInstVarNames = this.classOwnInstVarNamesFromBits(oopMap, rawBits); + if (instSize === ownInstVarNames.length) { + names = ownInstVarNames; + } else { + const superclass = oopMap[rawBits[this.oop][Squeak.Class_superclass]]; + const superInstVarNames = superclass.classAllInstVarNamesFromBits(oopMap, rawBits); + names = superInstVarNames.concat(ownInstVarNames); + } + if (instSize !== names.length) throw Error("allInstVarNames: wrong number of inst vars"); + } else { + names = []; + for (let i = 0; i < instSize; i++) names.push('$' + i); + } + this._classAllInstVarNames = names; + return names; }, renameFromBits: function(oopMap, rawBits, ccArray) { var classObj = this.sqClass < 32 ? oopMap[ccArray[this.sqClass-1]] : oopMap[this.sqClass]; @@ -120,15 +185,33 @@ Object.subclass('Squeak.Object', //Formats 0...4 -- Pointer fields if (nWords > 0) { var oops = bits; // endian conversion was already done - this.pointers = this.decodePointers(nWords, oops, oopMap); + var pointers = this.decodePointers(nWords, oops, oopMap); + var instVarNames = this.sqClass.classAllInstVarNamesFromBits(oopMap, rawBits); + for (var i = 0; i < instVarNames.length; i++) { + this[instVarNames[i]] = pointers[i]; + } + if (pointers.length === instVarNames.length) { + // only inst vars, no indexable fields + this.pointers = this.instVarProxy(instVarNames); + } else { + if (instVarNames.length === 0) { + // no inst vars, only indexable fields + this.$$ = pointers; + this.pointers = this.$$; // no proxy needed + } else { + this.$$ = pointers.slice(instVarNames.length); + this.pointers = this.instVarAndIndexableProxy(instVarNames); + } + } } } else if (this._format >= 12) { //Formats 12-15 -- CompiledMethods both pointers and bits var methodHeader = this.decodeWords(1, bits, littleEndian)[0], numLits = (methodHeader>>10) & 255, oops = this.decodeWords(numLits+1, bits, littleEndian); - this.pointers = this.decodePointers(numLits+1, oops, oopMap); //header+lits + this.$$ = this.decodePointers(numLits+1, oops, oopMap); //header+lits this.bytes = this.decodeBytes(nWords-(numLits+1), bits, numLits+1, this._format & 3); + this.pointers = this.$$; } else if (this._format >= 8) { //Formats 8..11 -- ByteArrays (and ByteStrings) if (nWords > 0) @@ -193,6 +276,59 @@ Object.subclass('Squeak.Object', array[i] = filler; return array; }, + instVarProxy: function(instVarNames) { + // emulate pointers access + return new Proxy(this, { + get: function(obj, key) { + if (key === 'length') return instVarNames.length; + if (key === 'slice') return (...args) => instVarNames.slice(...args).map(name => obj[name]); + const index = parseInt(key); + if (!isNaN(index)) return obj[instVarNames[index]]; + debugger; throw Error("unexpected getter: pointers." + key); + }, + set: function(obj, key, value) { + const index = parseInt(key); + if (isNaN(index)) { debugger; throw Error("unexpected setter: pointers." + key); } + obj[instVarNames[index]] = value; + return true; + } + }); + }, + instVarAndIndexableProxy: function(instVarNames) { + // emulate pointers access + return new Proxy(this, { + get: function(obj, key) { + if (key === 'length') return instVarNames.length + obj.$$.length; + if (key === 'slice') return (start, end) => { + if (start !== undefined && start === end) return []; // optimization + if (!start) start = 0; + if (start < 0) start += instVarNames.length + obj.$$.length; + if (!end) end = instVarNames.length + obj.$$.length; + if (end < 0) end += instVarNames.length + obj.$$.length; + const result = []; + for (let i = start; i < end; i++) { + if (i < instVarNames.length) result.push(obj[instVarNames[i]]); + else result.push(obj.$$[i - instVarNames.length]); + } + return result; + }; + const index = parseInt(key); + if (!isNaN(index)) { + return index < instVarNames.length + ? obj[instVarNames[index]] + : obj.$$[index - instVarNames.length]; + } + debugger; throw Error("unexpected getter: pointers." + key); + }, + set: function(obj, key, value) { + const index = parseInt(key); + if (isNaN(index)) { debugger; throw Error("unexpected setter: pointers." + key); } + if (key < instVarNames.length) obj[instVarNames[key]] = value; + else obj.$$[key - instVarNames.length] = value; + return true; + } + }); + }, }, 'testing', { isWords: function() { @@ -436,24 +572,39 @@ Object.subclass('Squeak.Object', classInstIsPointers: function() { return this.classInstFormat() <= 4; }, - instVarNames: function() { - // index changed from 4 to 3 in newer images - for (var index = 3; index <= 4; index++) { + ownInstVarNames: function() { + const index = Squeak.Class_instVars; // 3 or 4 or unknown + if (index > 0) { var varNames = this.pointers[index].pointers; if (varNames && varNames.length && varNames[0].bytes) { - return varNames.map(function(each) { - return each.bytesAsString(); - }); + return varNames.map(name => name.bytesAsString()); } } return []; }, allInstVarNames: function() { - var superclass = this.superclass(); - if (superclass.isNil) - return this.instVarNames(); - else - return superclass.allInstVarNames().concat(this.instVarNames()); + if (this._classAllInstVarNames) return this._classAllInstVarNames; + let names; + const instSize = this.classInstSize(); + if (instSize === 0) { + names = []; + } else if (Squeak.Class_instVars > 0) { + const ownInstVarNames = this.ownInstVarNames(); + if (instSize === ownInstVarNames.length) { + names = ownInstVarNames; + } else { + const superclass = this.superclass(); + const superInstVarNames = superclass.allInstVarNames(); + names = superInstVarNames.concat(ownInstVarNames); + } + if (instSize !== names.length) throw Error("allInstVarNames: wrong number of inst vars"); + } else { + names = []; + for (let i = 0; i < instSize; i++) names.push('$' + i); + } + this._classAllInstVarNames = names; + return names; + }, superclass: function() { return this.pointers[0]; @@ -522,7 +673,8 @@ Object.subclass('Squeak.Object', return (this.pointers[0] & 0x20000) > 0; }, methodAddPointers: function(headerAndLits) { - this.pointers = headerAndLits; + this.$$ = headerAndLits; + this.pointers = this.$$; }, methodTempCount: function() { return (this.pointers[0]>>18) & 63; diff --git a/vm.object.spur.js b/vm.object.spur.js index 222347d4..38a6790b 100644 --- a/vm.object.spur.js +++ b/vm.object.spur.js @@ -33,8 +33,19 @@ Squeak.Object.subclass('Squeak.ObjectSpur', this._format = format; if (format < 12) { if (format < 10) { - if (instSize + indexableSize > 0) - this.pointers = this.fillArray(instSize + indexableSize, nilObj); + if (instSize > 0) { + const vars = aClass.allInstVarNames(); + for (var i = 0; i < vars.length; i++) { + this[vars[i]] = nilObj; + } + this.pointers = indexableSize > 0 + ? this.instVarAndIndexableProxy(vars) + : this.instVarProxy(vars); + } + if (indexableSize > 0) { + this.$$ = this.fillArray(indexableSize, nilObj); + if (!this.pointers) this.pointers = this.$$; + } } else // Words if (indexableSize > 0) if (aClass.isFloatClass) { @@ -84,7 +95,24 @@ Squeak.Object.subclass('Squeak.ObjectSpur', 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); + var pointers = this.decodePointers(nWords, oops, oopMap, getCharacter, is64Bit); + var instVarNames = this.sqClass.classAllInstVarNamesFromBits(oopMap, rawBits); + for (var i = 0; i < instVarNames.length; i++) { + this[instVarNames[i]] = pointers[i]; + } + if (pointers.length === instVarNames.length) { + // only inst vars, no indexable fields + this.pointers = this.instVarProxy(instVarNames); + } else { + if (instVarNames.length === 0) { + // no inst vars, only indexable fields + this.$$ = pointers; + this.pointers = this.$$; // no proxy needed + } else { + this.$$ = pointers.slice(instVarNames.length); + this.pointers = this.instVarAndIndexableProxy(instVarNames); + } + } } break; case 11: // 32 bit array (odd length in 64 bits) @@ -134,9 +162,10 @@ Squeak.Object.subclass('Squeak.ObjectSpur', ? 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.$$ = 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 + if (is64Bit) this.$$[0] = (bits[1] & 0x80000000) | intHeader; // fix header + this.pointers = this.$$; break; default: throw Error("Unknown object format: " + this._format); @@ -268,14 +297,15 @@ Squeak.Object.subclass('Squeak.ObjectSpur', // this._format |= -indexableSize & 3; //deferred to writeTo() this.bytes = new Uint8Array(size); }, - classNameFromBits: function(oopMap, rawBits) { - var name = oopMap[rawBits[this.oop][Squeak.Class_name]]; - if (name && name._format >= 16 && name._format < 24) { - var bits = rawBits[name.oop], - bytes = name.decodeBytes(bits.length, bits, 0, name._format & 7); - return Squeak.bytesAsString(bytes); - } - return "Class"; + stringFromBits: function(rawBits) { + if (this._format < 16 || this._format >= 24) return ''; + var bits = rawBits[this.oop], + bytes = this.decodeBytes(bits.length, bits, 0, this._format & 7); + return Squeak.bytesAsString(bytes); + }, + classInstSizeFromBits: function(rawBits) { + var format = rawBits[this.oop][Squeak.Class_format] >> 1; + return format & 0xFFFF; }, renameFromBits: function(oopMap, rawBits, classTable) { var classObj = classTable[this.sqClass]; diff --git a/vm.primitives.js b/vm.primitives.js index c57b8d1c..3ff2b6f0 100644 --- a/vm.primitives.js +++ b/vm.primitives.js @@ -1191,7 +1191,8 @@ Object.subclass('Squeak.Primitives', allInstancesOf: function(clsObj) { var instances = this.vm.image.allInstancesOf(clsObj); var array = this.vm.instantiateClass(this.vm.specialObjects[Squeak.splOb_ClassArray], instances.length); - array.pointers = instances; + array.$$ = instances; + array.pointers = array.$$; return array; }, identityHash: function(obj) { @@ -1429,10 +1430,11 @@ Object.subclass('Squeak.Primitives', var bytecodeCount = this.stackInteger(1); if (!this.success) return 0; var method = this.vm.instantiateClass(this.vm.stackValue(2), bytecodeCount); - method.pointers = [header]; + method.$$ = [header]; + method.pointers = method.$$; var litCount = method.methodNumLits(); for (var i = 0; i < litCount; i++) - method.pointers.push(this.vm.nilObj); + method.$$.push(this.vm.nilObj); this.vm.popNandPush(1+argCount, method); if (this.vm.breakOnNewMethod) // break on doit this.vm.breakOnMethod = method; From 0512e2c0ae92bba653720900c1aa5545d0b30927 Mon Sep 17 00:00:00 2001 From: Vanessa Freudenberg Date: Tue, 11 Jun 2024 23:45:52 -0700 Subject: [PATCH 09/10] Inst var fix --- vm.object.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vm.object.js b/vm.object.js index 3847579c..e853a958 100644 --- a/vm.object.js +++ b/vm.object.js @@ -126,7 +126,8 @@ Object.subclass('Squeak.Object', const ownInstVarNames = []; const myBits = rawBits[this.oop]; if (Squeak.Class_instVars > 0) { - const varNamesArray = rawBits[myBits[Squeak.Class_instVars]]; + const varNames = oopMap[myBits[Squeak.Class_instVars]]; + const varNamesArray = rawBits[varNames.oop]; for (let i = 0; i < varNamesArray.length; i++) { const varName = oopMap[varNamesArray[i]]; const varStr = varName.stringFromBits(rawBits); From e0bd0af4d0648b3e4372e2daf4e3b20660c3fea9 Mon Sep 17 00:00:00 2001 From: Vanessa Freudenberg Date: Tue, 11 Jun 2024 23:57:55 -0700 Subject: [PATCH 10/10] Fix 64bit inst vars --- vm.object.js | 6 +++--- vm.object.spur.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/vm.object.js b/vm.object.js index e853a958..b1d8e720 100644 --- a/vm.object.js +++ b/vm.object.js @@ -137,10 +137,10 @@ Object.subclass('Squeak.Object', } return ownInstVarNames; }, - classAllInstVarNamesFromBits: function(oopMap, rawBits) { + classAllInstVarNamesFromBits: function(oopMap, rawBits, is64Bit) { if (this._classAllInstVarNames) return this._classAllInstVarNames; let names; - const instSize = this.classInstSizeFromBits(rawBits); + const instSize = this.classInstSizeFromBits(rawBits, is64Bit); if (instSize === 0) { names = []; } else if (Squeak.Class_instVars > 0) { @@ -149,7 +149,7 @@ Object.subclass('Squeak.Object', names = ownInstVarNames; } else { const superclass = oopMap[rawBits[this.oop][Squeak.Class_superclass]]; - const superInstVarNames = superclass.classAllInstVarNamesFromBits(oopMap, rawBits); + const superInstVarNames = superclass.classAllInstVarNamesFromBits(oopMap, rawBits, is64Bit); names = superInstVarNames.concat(ownInstVarNames); } if (instSize !== names.length) throw Error("allInstVarNames: wrong number of inst vars"); diff --git a/vm.object.spur.js b/vm.object.spur.js index 38a6790b..6a247c8b 100644 --- a/vm.object.spur.js +++ b/vm.object.spur.js @@ -96,7 +96,7 @@ Squeak.Object.subclass('Squeak.ObjectSpur', if (nWords > 0) { var oops = bits; // endian conversion was already done var pointers = this.decodePointers(nWords, oops, oopMap, getCharacter, is64Bit); - var instVarNames = this.sqClass.classAllInstVarNamesFromBits(oopMap, rawBits); + var instVarNames = this.sqClass.classAllInstVarNamesFromBits(oopMap, rawBits, is64Bit); for (var i = 0; i < instVarNames.length; i++) { this[instVarNames[i]] = pointers[i]; } @@ -303,8 +303,8 @@ Squeak.Object.subclass('Squeak.ObjectSpur', bytes = this.decodeBytes(bits.length, bits, 0, this._format & 7); return Squeak.bytesAsString(bytes); }, - classInstSizeFromBits: function(rawBits) { - var format = rawBits[this.oop][Squeak.Class_format] >> 1; + classInstSizeFromBits: function(rawBits, is64Bit) { + var format = rawBits[this.oop][Squeak.Class_format] >> (is64Bit ? 3 : 1); return format & 0xFFFF; }, renameFromBits: function(oopMap, rawBits, classTable) {