-
Notifications
You must be signed in to change notification settings - Fork 78
/
vm.object.spur.js
459 lines (456 loc) · 20.4 KB
/
vm.object.spur.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
"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[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[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[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";
},
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();
},
});