Skip to content

Commit 8c97612

Browse files
authored
feat: Generalize runtime (#1503)
BREAKING CHANGE: The `__alloc` family of built-ins has been split, now being named `__new`. `__renew`, `__newString`, `__newArray` etc. when dealing with managed objects. Furthermore, internal memory layouts of managed objects have changed. As a result, the new `heap.alloc/realloc/free` APIs now allow working with unmanaged heap memory more naturally and do not imply a managed object header anymore, more closely resembling `malloc/realloc/free` in C.
1 parent ff80a4e commit 8c97612

File tree

295 files changed

+72864
-58882
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

295 files changed

+72864
-58882
lines changed

lib/loader/index.d.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,6 @@ export interface ASUtil {
2525

2626
/** Explicit start function, if requested. */
2727
_start(): void;
28-
/** Allocates a new string in the module's memory and returns a reference (pointer) to it. */
29-
__allocString(str: string): number;
30-
/** Allocates a new array in the module's memory and returns a reference (pointer) to it. */
31-
__allocArray(id: number, values: ArrayLike<number>): number;
3228

3329
/** Copies a string's value from the module's memory. */
3430
__getString(ptr: number): string;
@@ -85,14 +81,18 @@ export interface ASUtil {
8581
/** Gets a live view on a Float64Array's values in the module's memory. */
8682
__getFloat64ArrayView(ptr: number): Float64Array;
8783

84+
/** Allocates an instance of the class represented by the specified id. */
85+
__new(size: number, id: number): number;
86+
/** Allocates a new string in the module's memory and returns a reference (pointer) to it. */
87+
__newString(str: string): number;
88+
/** Allocates a new array in the module's memory and returns a reference (pointer) to it. */
89+
__newArray(id: number, values: ArrayLike<number>): number;
8890
/** Retains a reference to a managed object externally, making sure that it doesn't become collected prematurely. Returns the pointer. */
8991
__retain(ptr: number): number;
9092
/** Releases a previously retained reference to a managed object, allowing the runtime to collect it once its reference count reaches zero. */
9193
__release(ptr: number): void;
9294
/** Forcefully resets the heap to its initial offset, effectively clearing dynamic memory. Stub runtime only. */
9395
__reset?(): void;
94-
/** Allocates an instance of the class represented by the specified id. */
95-
__alloc(size: number, id: number): number;
9696
/** Tests whether a managed object is an instance of the class represented by the specified base id. */
9797
__instanceof(ptr: number, baseId: number): boolean;
9898
/** Forces a cycle collection. Only relevant if objects potentially forming reference cycles are used. */

lib/loader/index.js

+8-8
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ function postInstantiate(extendedExports, instance) {
8181
const exports = instance.exports;
8282
const memory = exports.memory;
8383
const table = exports.table;
84-
const alloc = exports["__alloc"];
84+
const new_ = exports["__new"];
8585
const retain = exports["__retain"];
8686
const rttiBase = exports["__rtti_base"] || ~0; // oob if not present
8787

@@ -119,15 +119,15 @@ function postInstantiate(extendedExports, instance) {
119119
// }
120120

121121
/** Allocates a new string in the module's memory and returns its retained pointer. */
122-
function __allocString(str) {
122+
function __newString(str) {
123123
const length = str.length;
124-
const ptr = alloc(length << 1, STRING_ID);
124+
const ptr = new_(length << 1, STRING_ID);
125125
const U16 = new Uint16Array(memory.buffer);
126126
for (var i = 0, p = ptr >>> 1; i < length; ++i) U16[p + i] = str.charCodeAt(i);
127127
return ptr;
128128
}
129129

130-
extendedExports.__allocString = __allocString;
130+
extendedExports.__newString = __newString;
131131

132132
/** Reads a string from the module's memory by its pointer. */
133133
function __getString(ptr) {
@@ -159,16 +159,16 @@ function postInstantiate(extendedExports, instance) {
159159
}
160160

161161
/** Allocates a new array in the module's memory and returns its retained pointer. */
162-
function __allocArray(id, values) {
162+
function __newArray(id, values) {
163163
const info = getArrayInfo(id);
164164
const align = getValueAlign(info);
165165
const length = values.length;
166-
const buf = alloc(length << align, info & STATICARRAY ? id : ARRAYBUFFER_ID);
166+
const buf = new_(length << align, info & STATICARRAY ? id : ARRAYBUFFER_ID);
167167
let result;
168168
if (info & STATICARRAY) {
169169
result = buf;
170170
} else {
171-
const arr = alloc(info & ARRAY ? ARRAY_SIZE : ARRAYBUFFERVIEW_SIZE, id);
171+
const arr = new_(info & ARRAY ? ARRAY_SIZE : ARRAYBUFFERVIEW_SIZE, id);
172172
const U32 = new Uint32Array(memory.buffer);
173173
U32[arr + ARRAYBUFFERVIEW_BUFFER_OFFSET >>> 2] = retain(buf);
174174
U32[arr + ARRAYBUFFERVIEW_DATASTART_OFFSET >>> 2] = buf;
@@ -185,7 +185,7 @@ function postInstantiate(extendedExports, instance) {
185185
return result;
186186
}
187187

188-
extendedExports.__allocArray = __allocArray;
188+
extendedExports.__newArray = __newArray;
189189

190190
/** Gets a live view on an array's values in the module's memory. Infers the array type from RTTI. */
191191
function __getArrayView(arr) {

lib/loader/tests/build/default.wasm

317 Bytes
Binary file not shown.

lib/loader/tests/build/legacy.wasm

317 Bytes
Binary file not shown.

lib/loader/tests/index.js

+17-17
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ function test(file) {
3131
// should be able to allocate and work with a new small string
3232
{
3333
let str = "Hello world!𤭢";
34-
let ref = exports.__retain(exports.__allocString(str));
34+
let ref = exports.__retain(exports.__newString(str));
3535
assert.strictEqual(exports.__getString(ref), str);
3636
assert.strictEqual(exports.strlen(ref), str.length);
3737
exports.__release(ref);
@@ -46,7 +46,7 @@ function test(file) {
4646
≔ ≕ ≖ ≗ ≘ ≙ ≚ ≛ ≜ ≝ ≞ ≟ ≠ ≡ ≢ ≣ ≤ ≥ ≦ ≧ ≨ ≩ ≪ ≫ ≬ ≭ ≮ ≯
4747
≰ ≱ ≲ ≳ ≴ ≵ ≶ ≷ ≸ ≹ ≺ ≻ ≼ ≽ ≾ ≿
4848
`;
49-
let ref = exports.__retain(exports.__allocString(str));
49+
let ref = exports.__retain(exports.__newString(str));
5050
assert.strictEqual(exports.__getString(ref), str);
5151
assert.strictEqual(exports.strlen(ref), str.length);
5252
exports.__release(ref);
@@ -55,7 +55,7 @@ function test(file) {
5555
// should be able to allocate a typed array
5656
{
5757
let arr = [1, 2, 3, 4, 5, 0x80000000 | 0];
58-
let ref = exports.__retain(exports.__allocArray(exports.INT32ARRAY_ID, arr));
58+
let ref = exports.__retain(exports.__newArray(exports.INT32ARRAY_ID, arr));
5959
assert(exports.__instanceof(ref, exports.INT32ARRAY_ID));
6060

6161
// should be able to get the values of an array
@@ -75,7 +75,7 @@ function test(file) {
7575
// should be able to allocate a typed array
7676
{
7777
let arr = [1, 2, 3, 4, 5, 0x80000000 | 0];
78-
let ref = exports.__retain(exports.__allocArray(exports.STATICARRAYI32_ID, arr));
78+
let ref = exports.__retain(exports.__newArray(exports.STATICARRAYI32_ID, arr));
7979
assert(exports.__instanceof(ref, exports.STATICARRAYI32_ID));
8080

8181
// should be able to get the values of an array
@@ -95,21 +95,21 @@ function test(file) {
9595
/*
9696
{
9797
let arrU8Arr = new Uint8Array([0, 1, 2]);
98-
let refU8Arr = module.__retain(module.__allocUint8Array(arrU8Arr));
98+
let refU8Arr = module.__retain(module.__newUint8Array(arrU8Arr));
9999
assert(module.__instanceof(refU8Arr, module.UINT8ARRAY_ID));
100100
assert.deepEqual(module.__getUint8Array(refU8Arr), arrU8Arr);
101101
module.__release(refU8Arr);
102102
try { module.__release(refU8Arr); assert(false); } catch (e) {};
103103
104104
let arrU16Arr = new Uint16Array([0, 0x7FFF, 0xFFFF]);
105-
let refU16Arr = module.__retain(module.__allocUint16Array(arrU16Arr));
105+
let refU16Arr = module.__retain(module.__newUint16Array(arrU16Arr));
106106
assert(module.__instanceof(refU16Arr, module.UINT16ARRAY_ID));
107107
assert.deepEqual(module.__getUint16Array(refU16Arr), arrU16Arr);
108108
module.__release(refU16Arr);
109109
try { module.__release(refU16Arr); assert(false); } catch (e) {};
110110
111111
let arrI16Arr = new Int16Array([0, -1, -2]);
112-
let refI16Arr = module.__retain(module.__allocInt16Array(arrI16Arr));
112+
let refI16Arr = module.__retain(module.__newInt16Array(arrI16Arr));
113113
assert(module.__instanceof(refI16Arr, module.INT16ARRAY_ID));
114114
assert.deepEqual(module.__getInt16Array(refI16Arr), arrI16Arr);
115115
module.__release(refI16Arr);
@@ -121,7 +121,7 @@ function test(file) {
121121
{
122122
let values = [0, 255, 127];
123123
let arr = new Uint8Array(values);
124-
let ref = exports.__retain(exports.__allocArray(exports.UINT8ARRAY_ID, arr));
124+
let ref = exports.__retain(exports.__newArray(exports.UINT8ARRAY_ID, arr));
125125
assert(exports.__instanceof(ref, exports.UINT8ARRAY_ID));
126126
assert.deepEqual(exports.__getUint8Array(ref), arr);
127127
assert.deepEqual(exports.__getUint8ArrayView(ref), arr);
@@ -133,7 +133,7 @@ function test(file) {
133133
// should be able to distinguish between signed and unsigned for static array layout
134134
{
135135
let arr = [0, 255, 127];
136-
let ref = exports.__retain(exports.__allocArray(exports.STATICARRAYU8_ID, arr));
136+
let ref = exports.__retain(exports.__newArray(exports.STATICARRAYU8_ID, arr));
137137
assert(exports.__instanceof(ref, exports.STATICARRAYU8_ID));
138138
assert.deepEqual(exports.__getArray(ref), arr);
139139
exports.__release(ref);
@@ -144,7 +144,7 @@ function test(file) {
144144
{
145145
let values = [0, 0xFFFF, -0x00FF];
146146
let arr = new Int16Array(values);
147-
let ref = exports.__retain(exports.__allocArray(exports.INT16ARRAY_ID, arr));
147+
let ref = exports.__retain(exports.__newArray(exports.INT16ARRAY_ID, arr));
148148
assert(exports.__instanceof(ref, exports.INT16ARRAY_ID));
149149
assert.deepEqual(exports.__getInt16Array(ref), arr);
150150
assert.deepEqual(exports.__getInt16ArrayView(ref), arr);
@@ -156,7 +156,7 @@ function test(file) {
156156
// should be able to distinguish between signed and unsigned for static array layout
157157
{
158158
let arr = [0, 0xFFFF, -0x00FF];
159-
let ref = exports.__retain(exports.__allocArray(exports.STATICARRAYI16_ID, arr));
159+
let ref = exports.__retain(exports.__newArray(exports.STATICARRAYI16_ID, arr));
160160
assert(exports.__instanceof(ref, exports.STATICARRAYI16_ID));
161161
assert.deepEqual(exports.__getArray(ref), [0, -1, -255]);
162162
exports.__release(ref);
@@ -167,7 +167,7 @@ function test(file) {
167167
{
168168
let values = [1, -1 >>> 0, 0x80000000];
169169
let arr = new Uint32Array(values);
170-
let ref = exports.__retain(exports.__allocArray(exports.UINT32ARRAY_ID, arr));
170+
let ref = exports.__retain(exports.__newArray(exports.UINT32ARRAY_ID, arr));
171171
assert(exports.__instanceof(ref, exports.UINT32ARRAY_ID));
172172
assert.deepEqual(exports.__getUint32Array(ref), arr);
173173
assert.deepEqual(exports.__getUint32ArrayView(ref), arr);
@@ -179,7 +179,7 @@ function test(file) {
179179
// should be able to distinguish between signed and unsigned with static array layout
180180
{
181181
let arr = [1, -1 >>> 0, 0x80000000];
182-
let ref = exports.__retain(exports.__allocArray(exports.STATICARRAYU32_ID, arr));
182+
let ref = exports.__retain(exports.__newArray(exports.STATICARRAYU32_ID, arr));
183183
assert(exports.__instanceof(ref, exports.STATICARRAYU32_ID));
184184
assert.deepEqual(exports.__getArray(ref), arr);
185185
exports.__release(ref);
@@ -190,7 +190,7 @@ function test(file) {
190190
{
191191
let values = [0.0, 1.5, 2.5];
192192
let arr = new Float32Array(values);
193-
let ref = exports.__retain(exports.__allocArray(exports.FLOAT32ARRAY_ID, arr));
193+
let ref = exports.__retain(exports.__newArray(exports.FLOAT32ARRAY_ID, arr));
194194
assert(exports.__instanceof(ref, exports.FLOAT32ARRAY_ID));
195195
assert.deepEqual(exports.__getFloat32Array(ref), arr);
196196
assert.deepEqual(exports.__getFloat32ArrayView(ref), arr);
@@ -202,7 +202,7 @@ function test(file) {
202202
// should be able to distinguish between integer and float static arrays
203203
{
204204
let arr = [0.0, 1.5, 2.5];
205-
let ref = exports.__retain(exports.__allocArray(exports.STATICARRAYF32_ID, arr));
205+
let ref = exports.__retain(exports.__newArray(exports.STATICARRAYF32_ID, arr));
206206
assert(exports.__instanceof(ref, exports.STATICARRAYF32_ID));
207207
assert.deepEqual(exports.__getArray(ref), arr);
208208
exports.__release(ref);
@@ -212,7 +212,7 @@ function test(file) {
212212
// should be able to work with normal arrays
213213
{
214214
let arr = [1, 2, 3, 4, 5];
215-
let ref = exports.__retain(exports.__allocArray(exports.ARRAYI32_ID, arr));
215+
let ref = exports.__retain(exports.__newArray(exports.ARRAYI32_ID, arr));
216216
assert(exports.__instanceof(ref, exports.ARRAYI32_ID));
217217
exports.changeLength(ref, 3);
218218
assert.deepEqual(exports.__getArray(ref), [1, 2, 3]);
@@ -252,7 +252,7 @@ function test(file) {
252252

253253
// should be able to mutate an array in place using getArrayView
254254
{
255-
let ptr = exports.__retain(exports.__allocArray(exports.FLOAT32ARRAY_ID, [1, 2, 3]));
255+
let ptr = exports.__retain(exports.__newArray(exports.FLOAT32ARRAY_ID, [1, 2, 3]));
256256
let view = exports.__getArrayView(ptr);
257257
assert.deepEqual(view, new Float32Array([1, 2, 3]));
258258
exports.modifyFloat32Array(ptr, 0, 4);

lib/loader/umd/index.js

+8-8
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ var loader = (function(exports) {
9595
const exports = instance.exports;
9696
const memory = exports.memory;
9797
const table = exports.table;
98-
const alloc = exports["__alloc"];
98+
const new_ = exports["__new"];
9999
const retain = exports["__retain"];
100100
const rttiBase = exports["__rtti_base"] || ~0; // oob if not present
101101

@@ -138,17 +138,17 @@ var loader = (function(exports) {
138138
/** Allocates a new string in the module's memory and returns its retained pointer. */
139139

140140

141-
function __allocString(str) {
141+
function __newString(str) {
142142
const length = str.length;
143-
const ptr = alloc(length << 1, STRING_ID);
143+
const ptr = new_(length << 1, STRING_ID);
144144
const U16 = new Uint16Array(memory.buffer);
145145

146146
for (var i = 0, p = ptr >>> 1; i < length; ++i) U16[p + i] = str.charCodeAt(i);
147147

148148
return ptr;
149149
}
150150

151-
extendedExports.__allocString = __allocString;
151+
extendedExports.__newString = __newString;
152152
/** Reads a string from the module's memory by its pointer. */
153153

154154
function __getString(ptr) {
@@ -193,17 +193,17 @@ var loader = (function(exports) {
193193
/** Allocates a new array in the module's memory and returns its retained pointer. */
194194

195195

196-
function __allocArray(id, values) {
196+
function __newArray(id, values) {
197197
const info = getArrayInfo(id);
198198
const align = getValueAlign(info);
199199
const length = values.length;
200-
const buf = alloc(length << align, info & STATICARRAY ? id : ARRAYBUFFER_ID);
200+
const buf = new_(length << align, info & STATICARRAY ? id : ARRAYBUFFER_ID);
201201
let result;
202202

203203
if (info & STATICARRAY) {
204204
result = buf;
205205
} else {
206-
const arr = alloc(info & ARRAY ? ARRAY_SIZE : ARRAYBUFFERVIEW_SIZE, id);
206+
const arr = new_(info & ARRAY ? ARRAY_SIZE : ARRAYBUFFERVIEW_SIZE, id);
207207
const U32 = new Uint32Array(memory.buffer);
208208
U32[arr + ARRAYBUFFERVIEW_BUFFER_OFFSET >>> 2] = retain(buf);
209209
U32[arr + ARRAYBUFFERVIEW_DATASTART_OFFSET >>> 2] = buf;
@@ -223,7 +223,7 @@ var loader = (function(exports) {
223223
return result;
224224
}
225225

226-
extendedExports.__allocArray = __allocArray;
226+
extendedExports.__newArray = __newArray;
227227
/** Gets a live view on an array's values in the module's memory. Infers the array type from RTTI. */
228228

229229
function __getArrayView(arr) {

lib/rtrace/index.js

+6-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const PTR_SIZE = 1 << PTR_SIZE_BITS;
99
const PTR_MASK = PTR_SIZE - 1;
1010
const PTR_VIEW = Uint32Array;
1111

12-
const BLOCK_OVERHEAD = 16;
12+
const BLOCK_OVERHEAD = PTR_SIZE;
1313

1414
function assert(x) {
1515
if (!x) throw Error("assertion failed");
@@ -142,9 +142,10 @@ class Rtrace {
142142

143143
/** Obtains information about a block. */
144144
getBlockInfo(ptr) {
145-
var header = new Uint32Array(this.memory.buffer, ptr, 4);
145+
var header = new Uint32Array(this.memory.buffer, ptr, 5);
146146
var mmInfo = header[0];
147147
var gcInfo = header[1];
148+
var gcInfo2 = header[2];
148149
const mmTags = [ // 0│L│F
149150
[],
150151
["FREE"],
@@ -171,8 +172,9 @@ class Rtrace {
171172
color: gcColor[gcInfo << 1 >>> 29],
172173
rc: gcInfo << 4 >>> 4
173174
},
174-
rtId: header[2],
175-
rtSize: header[3]
175+
gcInfo2,
176+
rtId: header[3],
177+
rtSize: header[4]
176178
}
177179
};
178180
}

src/builtins.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,7 @@ export namespace BuiltinNames {
607607
export const v64x2_load_splat = "~lib/builtins/v64x2.load_splat";
608608

609609
// internals
610-
export const heap_base = "~lib/heap/__heap_base";
610+
export const heap_base = "~lib/memory/__heap_base";
611611
export const rtti_base = "~lib/rt/__rtti_base";
612612
export const visit_globals = "~lib/rt/__visit_globals";
613613
export const visit_members = "~lib/rt/__visit_members";
@@ -2875,7 +2875,7 @@ function builtin_memory_data(ctx: BuiltinContext): ExpressionRef {
28752875
}
28762876
let buf = new Uint8Array(numElements * elementType.byteSize);
28772877
assert(compiler.writeStaticBuffer(buf, 0, elementType, exprs) == buf.byteLength);
2878-
offset = compiler.addMemorySegment(buf, align).offset;
2878+
offset = compiler.addAlignedMemorySegment(buf, align).offset;
28792879
} else { // data(size[, align])
28802880
let arg0 = compiler.compileExpression(operands[0], Type.i32, Constraints.CONV_IMPLICIT);
28812881
let precomp = module.runExpression(arg0, ExpressionRunnerFlags.PreserveSideeffects);
@@ -2904,7 +2904,7 @@ function builtin_memory_data(ctx: BuiltinContext): ExpressionRef {
29042904
return module.unreachable();
29052905
}
29062906
}
2907-
offset = compiler.addMemorySegment(new Uint8Array(size), align).offset;
2907+
offset = compiler.addAlignedMemorySegment(new Uint8Array(size), align).offset;
29082908
}
29092909
// FIXME: what if recompiles happen? recompiles are bad.
29102910
compiler.currentType = usizeType;
@@ -8885,7 +8885,7 @@ export function compileRTTI(compiler: Compiler): void {
88858885
}
88868886
assert(off == size);
88878887
var usizeType = program.options.usizeType;
8888-
var segment = compiler.addMemorySegment(data);
8888+
var segment = compiler.addAlignedMemorySegment(data);
88898889
if (usizeType.size == 8) {
88908890
let offset = segment.offset;
88918891
module.addGlobal(BuiltinNames.rtti_base, NativeType.I64, false, module.i64(i64_low(offset), i64_high(offset)));

src/common.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -229,14 +229,18 @@ export namespace CommonNames {
229229
export const alloc = "__alloc";
230230
export const realloc = "__realloc";
231231
export const free = "__free";
232+
export const new_ = "__new";
233+
export const renew = "__renew";
232234
export const retain = "__retain";
233235
export const release = "__release";
234236
export const collect = "__collect";
235237
export const typeinfo = "__typeinfo";
236238
export const instanceof_ = "__instanceof";
237239
export const visit = "__visit";
238-
export const allocBuffer = "__allocBuffer";
239-
export const allocArray = "__allocArray";
240+
export const newBuffer = "__newBuffer";
241+
export const newArray = "__newArray";
242+
export const BLOCK = "~lib/rt/common/BLOCK";
243+
export const OBJECT = "~lib/rt/common/OBJECT";
240244
}
241245

242246
// shared

0 commit comments

Comments
 (0)