Skip to content

Commit 9c770d8

Browse files
committed
Add initial newArray and getArray helpers to loader
Essentially creates an unmanaged typed array in memory that one can work with and free again respectively obtain from the AS side. No support for GC or generic arrays yet, and is likely to change substentially once WASM GC becomes a thing.
1 parent 16d1a83 commit 9c770d8

File tree

11 files changed

+132
-9
lines changed

11 files changed

+132
-9
lines changed

dist/asc.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/asc.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/assemblyscript.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/assemblyscript.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/loader/README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Instances are automatically populated with useful utility:
6161
A 64-bit float view on the memory.
6262

6363
* **newString**(str: `string`): `number`<br />
64-
Allocates a new string in the module's memory and returns its pointer. Requires `allocate_memory` to be exported from your module's entry file, i.e.:
64+
Allocates a new string in the module's memory and returns its pointer. Requires `memory.allocate` to be exported from your module's entry file, i.e.:
6565

6666
```js
6767
import "allocator/tlsf";
@@ -71,6 +71,18 @@ Instances are automatically populated with useful utility:
7171
* **getString**(ptr: `number`): `string`<br />
7272
Gets a string from the module's memory by its pointer.
7373

74+
* **newArray**(view: `TypedArray`, length?: `number`, unsafe?: `boolean`): `number`<br />
75+
Copies a typed array into the module's memory and returns its pointer.
76+
77+
* **newArray**(ctor: `TypedArrayConstructor`, length: `number`): `number`<br />
78+
Creates a typed array in the module's memory and returns its pointer.
79+
80+
* **getArray**(ctor: `TypedArrayConstructor`, ptr: `number`): `TypedArray`<br />
81+
Gets a view on a typed array in the module's memory by its pointer.
82+
83+
* **freeArray**(ptr: `number`): `void`<br />
84+
Frees a typed array in the module's memory. Must not be accessed anymore afterwards.
85+
7486
<sup>1</sup> This feature has not yet landed in any VM as of this writing.
7587

7688
Examples
@@ -125,7 +137,7 @@ myModule.F64[ptrToFloat64 >>> 3] = newValue;
125137
var str = "Hello world!";
126138
var ptr = module.newString(str);
127139

128-
// Disposing a string that is no longer needed (requires free_memory to be exported)
140+
// Disposing a string that is no longer needed (requires memory.free to be exported)
129141
module.memory.free(ptr);
130142

131143
// Obtaining a string, i.e. as returned by an export

lib/loader/index.d.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,26 @@ interface ImportsObject {
1010
}
1111
}
1212

13+
type TypedArray
14+
= Int8Array
15+
| Uint8Array
16+
| Int16Array
17+
| Uint16Array
18+
| Int32Array
19+
| Uint32Array
20+
| Float32Array
21+
| Float64Array;
22+
23+
type TypedArrayConstructor
24+
= Int8ArrayConstructor
25+
| Uint8ArrayConstructor
26+
| Int16ArrayConstructor
27+
| Uint16ArrayConstructor
28+
| Int32ArrayConstructor
29+
| Uint32ArrayConstructor
30+
| Float32ArrayConstructor
31+
| Float32ArrayConstructor;
32+
1333
/** Utility mixed in by the loader. */
1434
interface ASUtil {
1535
/** An 8-bit signed integer view on the memory. */
@@ -36,6 +56,14 @@ interface ASUtil {
3656
newString(str: string): number;
3757
/** Gets a string from the module's memory by its pointer. */
3858
getString(ptr: number): string;
59+
/** Copies a typed array into the module's memory and returns its pointer. */
60+
newArray(view: TypedArray, length?: number): number;
61+
/** Creates a typed array in the module's memory and returns its pointer. */
62+
newArray(ctor: TypedArrayConstructor, length: number, unsafe?: boolean): number;
63+
/** Gets a view on a typed array in the module's memory by its pointer. */
64+
getArray(ctor: TypedArrayConstructor, ptr: number): TypedArray;
65+
/** Frees a typed array in the module's memory. Must not be accessed anymore afterwards. */
66+
freeArray(ptr: number): void;
3967
}
4068

4169
/** Instantiates an AssemblyScript module using the specified imports. */

lib/loader/index.js

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ function instantiate(module, imports) {
1717
// Instantiate the module and obtain its (flat) exports
1818
var instance = new WebAssembly.Instance(module, imports);
1919
var exports = instance.exports;
20+
var memory_allocate = exports["memory.allocate"];
21+
var memory_fill = exports["memory.fill"];
22+
var memory_free = exports["memory.free"];
2023

2124
// Provide views for all sorts of basic values
2225
var mem, I8, U8, I16, U16, I32, U32, F32, F64, I64, U64;
@@ -45,7 +48,7 @@ function instantiate(module, imports) {
4548
/** Allocates a new string in the module's memory and returns its pointer. */
4649
function newString(str) {
4750
var dataLength = str.length;
48-
var ptr = exports["memory.allocate"](4 + (dataLength << 1));
51+
var ptr = memory_allocate(4 + (dataLength << 1));
4952
var dataOffset = (4 + ptr) >>> 1;
5053
checkMem();
5154
U32[ptr >>> 2] = dataLength;
@@ -71,6 +74,62 @@ function instantiate(module, imports) {
7174
return parts.join("") + String.fromCharCode.apply(String, U16.subarray(dataOffset, dataOffset + dataRemain));
7275
}
7376

77+
function computeBufferSize(byteLength) {
78+
const HEADER_SIZE = 8;
79+
return 1 << (32 - Math.clz32(byteLength + HEADER_SIZE - 1));
80+
}
81+
82+
/** Creates a new typed array in the module's memory and returns its pointer. */
83+
function newArray(view, length, unsafe) {
84+
var ctor = view.constructor;
85+
if (ctor === Function) { // TypedArray constructor created in memory
86+
ctor = view;
87+
view = null;
88+
} else { // TypedArray instance copied into memory
89+
if (length === undefined) length = view.length;
90+
}
91+
var elementSize = ctor.BYTES_PER_ELEMENT;
92+
if (!elementSize) throw Error("not a typed array");
93+
var byteLength = elementSize * length;
94+
var ptr = memory_allocate(12); // TypedArray header
95+
var buf = memory_allocate(computeBufferSize(byteLength)); // ArrayBuffer
96+
checkMem();
97+
U32[ ptr >>> 2] = buf; // .buffer
98+
U32[(ptr + 4) >>> 2] = 0; // .byteOffset
99+
U32[(ptr + 8) >>> 2] = byteLength; // .byteLength
100+
U32[ buf >>> 2] = byteLength; // .byteLength
101+
U32[(buf + 4) >>> 2] = 0; // 0
102+
if (view) {
103+
new ctor(mem, buf + 8, length).set(view);
104+
if (view.length < length && !unsafe) {
105+
let setLength = elementSize * view.length;
106+
memory_fill(buf + 8 + setLength, 0, byteLength - setLength);
107+
}
108+
} else if (!unsafe) {
109+
memory_fill(buf + 8, 0, byteLength);
110+
}
111+
return ptr;
112+
}
113+
114+
/** Gets a view on a typed array in the module's memory by its pointer. */
115+
function getArray(ctor, ptr) {
116+
var elementSize = ctor.BYTES_PER_ELEMENT;
117+
if (!elementSize) throw Error("not a typed array");
118+
checkMem();
119+
var buf = U32[ ptr >>> 2];
120+
var byteOffset = U32[(ptr + 4) >>> 2];
121+
var byteLength = U32[(ptr + 8) >>> 2];
122+
return new ctor(mem, buf + 8 + byteOffset, (byteLength - byteOffset) / elementSize);
123+
}
124+
125+
/** Frees a typed array in the module's memory. Must not be accessed anymore afterwards. */
126+
function freeArray(ptr) {
127+
checkMem();
128+
var buf = U32[ptr >>> 2];
129+
memory_free(buf);
130+
memory_free(ptr);
131+
}
132+
74133
// Demangle exports and provide the usual utility on the prototype
75134
return demangle(exports, {
76135
get I8() { checkMem(); return I8; },
@@ -84,7 +143,10 @@ function instantiate(module, imports) {
84143
get F32() { checkMem(); return F32; },
85144
get F64() { checkMem(); return F64; },
86145
newString,
87-
getString
146+
getString,
147+
newArray,
148+
getArray,
149+
freeArray
88150
});
89151
}
90152

lib/loader/tests/assembly/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import "allocator/arena";
1+
import "allocator/tlsf";
22

33
export { memory };
44

@@ -44,3 +44,9 @@ export class Car {
4444
memory.free(changetype<usize>(this));
4545
}
4646
}
47+
48+
export function sum(arr: Int32Array): i32 {
49+
var v = 0;
50+
for (let i = 0, k = arr.length; i < k; ++i) v += arr[i];
51+
return v;
52+
}

lib/loader/tests/build/untouched.wasm

3.27 KB
Binary file not shown.

lib/loader/tests/index.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,16 @@ var str = "Hello world!𤭢";
3333
var ptr = module.newString(str);
3434
assert.strictEqual(module.getString(ptr), str);
3535
assert.strictEqual(module.strlen(ptr), str.length);
36+
37+
// should be able to allocate a typed array and sum up its values
38+
var arr = [1, 2, 3, 4, 5, 0x7fffffff];
39+
ptr = module.newArray(new Int32Array(arr));
40+
assert.strictEqual(module.sum(ptr), arr.reduce((a, b) => a + b, 0) | 0);
41+
42+
// should be able to get a view on an internal typed array
43+
assert.deepEqual(module.getArray(Int32Array, ptr), arr);
44+
45+
// should be able to free and reuse the space of an internal typed array
46+
module.freeArray(ptr);
47+
var ptr2 = module.newArray(new Int32Array(arr));
48+
assert.strictEqual(ptr, ptr2);

src/compiler.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6523,6 +6523,8 @@ export class Compiler extends DiagnosticEmitter {
65236523
// if present, check that the constructor is compatible with object literals
65246524
var ctor = classReference.constructorInstance;
65256525
if (ctor) {
6526+
// TODO: if the constructor requires parameters, check whether these are given as part of the
6527+
// object literal and use them to call the ctor while not generating a store.
65266528
if (ctor.signature.requiredParameters) {
65276529
this.error(
65286530
DiagnosticCode.Constructor_of_class_0_must_not_require_any_arguments,

0 commit comments

Comments
 (0)