porffor 0.57.19 → 0.57.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,61 +1,10 @@
1
1
  import { Valtype, FuncType, ExportDesc, Section, Magic, Opcodes, PageSize, Reftype } from './wasmSpec.js';
2
- import { encodeVector, encodeString, unsignedLEB128, signedLEB128, unsignedLEB128_into, signedLEB128_into, ieee754_binary64, ieee754_binary64_into, unsignedLEB128_length } from './encoding.js';
2
+ import { unsignedLEB128_length, signedLEB128_length } from './encoding.js';
3
3
  import { importedFuncs } from './builtins.js';
4
4
  import { log } from './log.js';
5
5
  import './prefs.js';
6
6
 
7
- const createSection = (type, data) => [
8
- type,
9
- ...encodeVector(data)
10
- ];
11
-
12
- const customSection = (name, data) => createSection(
13
- Section.custom,
14
- [ ...encodeString(name), ...data ]
15
- );
16
-
17
- const encodeNames = funcs => {
18
- const encodeSection = (id, section) => [
19
- id,
20
- ...unsignedLEB128(section.length),
21
- ...section
22
- ];
23
-
24
- const moduleSection = encodeString('js'); // TODO: filename?
25
- const functionsSection = encodeVector(
26
- funcs.map(x => unsignedLEB128(x.asmIndex).concat(encodeString(x.name))),
27
- );
28
- const localsSection = encodeVector(
29
- funcs.map(x => unsignedLEB128(x.asmIndex).concat(encodeVector(
30
- Object.entries(x.locals).map(([name, local]) =>
31
- unsignedLEB128(local.idx).concat(encodeString(name))
32
- )
33
- )))
34
- );
35
-
36
- return [
37
- ...encodeSection(0, moduleSection),
38
- ...encodeSection(1, functionsSection),
39
- ...encodeSection(2, localsSection),
40
- ];
41
- };
42
-
43
7
  export default (funcs, globals, tags, pages, data, noTreeshake = false) => {
44
- const types = [], typeCache = {};
45
-
46
- const getType = (params, returns) => {
47
- const hash = `${params.join(',')}_${returns.join(',')}`;
48
- if (Prefs.optLog) log('assemble', `getType(${JSON.stringify(params)}, ${JSON.stringify(returns)}) -> ${hash} | cache: ${typeCache[hash]}`);
49
- if (typeCache[hash] !== undefined) return typeCache[hash];
50
-
51
- const type = [ FuncType, ...encodeVector(params), ...encodeVector(returns) ];
52
- const idx = types.length;
53
-
54
- types.push(type);
55
-
56
- return typeCache[hash] = idx;
57
- };
58
-
59
8
  let t = performance.now();
60
9
  const time = msg => {
61
10
  if (!Prefs.profileAssemble) return;
@@ -64,71 +13,189 @@ export default (funcs, globals, tags, pages, data, noTreeshake = false) => {
64
13
  t = performance.now();
65
14
  };
66
15
 
67
- let importFuncs = [], importDelta = 0;
16
+ let importFuncs = globalThis.importFuncs = [];
17
+ globalThis.importDelta = 0;
68
18
  if (!Prefs.treeshakeWasmImports || noTreeshake) {
69
19
  importFuncs = Array.from(importedFuncs);
70
20
  } else {
71
- let imports = new Map();
72
-
73
21
  // tree shake imports
74
- for (const f of funcs) {
75
- if (f.usesImports) for (const inst of f.wasm) {
76
- if (inst[0] === Opcodes.call && inst[1] < importedFuncs.length) {
77
- const idx = inst[1];
22
+ const remap = new WeakMap();
23
+ for (let i = 0; i < funcs.length; i++) {
24
+ const f = funcs[i];
25
+ if (f.usesImports) for (let j = 0; j < f.wasm.length; j++) {
26
+ const x = f.wasm[j];
27
+ if (x[0] === Opcodes.call && x[1] < importedFuncs.length) {
28
+ const idx = x[1];
78
29
  const func = importedFuncs[idx];
79
30
 
80
- if (!imports.has(func.name)) imports.set(func.name, { ...func, idx: imports.size });
81
- inst[1] = imports.get(func.name).idx;
31
+ if (!remap.has(func)) {
32
+ remap.set(func, importFuncs.length);
33
+ importFuncs.push(func);
34
+ }
35
+ x[1] = remap.get(func);
82
36
  }
83
37
  }
84
38
  }
85
39
 
86
- importFuncs = globalThis.importFuncs = [...imports.values()];
87
- importDelta = importedFuncs.length - importFuncs.length;
40
+ globalThis.importDelta = importedFuncs.length - importFuncs.length;
41
+ }
42
+
43
+ if (Prefs.optLog) log('assemble', `treeshake: using ${importFuncs.length}/${importedFuncs.length} imports`);
44
+ time('import treeshake');
45
+
46
+ // todo: this will just break if it is too small
47
+ let bufferSize = 1024 * 1024; // 1MB
48
+ let buffer = new Uint8Array(bufferSize);
49
+ let offset = 0;
50
+
51
+ const ensureBufferSize = added => {
52
+ if (offset + added >= bufferSize - 64) {
53
+ const newBuffer = new Uint8Array(bufferSize *= 2);
54
+ newBuffer.set(buffer);
55
+ buffer = null; // help gc
56
+ buffer = newBuffer;
57
+ }
58
+ };
59
+
60
+ const byte = byte => {
61
+ ensureBufferSize(1);
62
+ buffer[offset++] = byte;
63
+ };
64
+
65
+ const array = array => {
66
+ ensureBufferSize(array.length);
67
+ buffer.set(array, offset);
68
+ offset += array.length;
69
+ };
70
+
71
+ const unsigned = n => {
72
+ n |= 0;
73
+ if (n >= 0 && n <= 127) return byte(n);
74
+
75
+ do {
76
+ let x = n & 0x7f;
77
+ n >>>= 7;
78
+ if (n !== 0) {
79
+ x |= 0x80;
80
+ }
81
+
82
+ byte(x);
83
+ } while (n !== 0);
84
+ };
85
+
86
+ const signed = n => {
87
+ n |= 0;
88
+ if (n >= 0 && n <= 63) return byte(n);
89
+
90
+ while (true) {
91
+ let x = n & 0x7f;
92
+ n >>= 7;
93
+
94
+ if ((n === 0 && (x & 0x40) === 0) || (n === -1 && (x & 0x40) !== 0)) {
95
+ byte(x);
96
+ break;
97
+ } else {
98
+ x |= 0x80;
99
+ }
100
+
101
+ byte(x);
102
+ }
103
+ };
104
+
105
+ const ieee754 = n => {
106
+ ensureBufferSize(8);
107
+ array(new Uint8Array(new Float64Array([ n ]).buffer));
108
+ };
109
+
110
+ const string = str => {
111
+ unsigned(str.length);
112
+ for (let i = 0; i < str.length; i++) {
113
+ byte(str.charCodeAt(i));
114
+ }
115
+ };
116
+
117
+ const section = (id, bytes) => {
118
+ byte(id);
119
+ unsigned(bytes);
120
+ };
121
+
122
+ const unsignedPost = () => {
123
+ const o = offset;
124
+ offset += 5;
125
+ return n => {
126
+ const o2 = offset;
127
+ offset = o;
128
+ unsigned(n);
129
+
130
+ buffer.set(buffer.subarray(o + 5, o2), offset);
131
+ offset = o2 - (5 - (offset - o));
132
+ };
133
+ };
134
+
135
+ array(Magic, Magic.length);
136
+ time('setup');
137
+
138
+ const types = [], typeCache = new Map();
139
+ const getType = (params, returns) => {
140
+ const hash = `${params.join()}_${returns.join()}`;
141
+ if (typeCache.has(hash)) return typeCache.get(hash);
142
+
143
+ const type = [ FuncType, params.length, ...params, returns.length, ...returns ];
144
+ const idx = types.length;
145
+
146
+ types.push(type);
147
+ typeCache.set(hash, idx);
148
+ return idx;
149
+ };
150
+
151
+ // precache all types to be used
152
+ for (let i = 0; i < funcs.length; i++) {
153
+ const func = funcs[i];
154
+ getType(func.params, func.returns);
88
155
  }
89
156
 
90
- for (const f of funcs) {
91
- f.asmIndex = f.index - importDelta;
157
+ for (let i = 0; i < importFuncs.length; i++) {
158
+ const func = importFuncs[i];
159
+ getType(func.params, func.returns);
92
160
  }
93
161
 
94
- if (Prefs.optLog) log('assemble', `treeshake: using ${importFuncs.length}/${importedFuncs.length} imports`);
162
+ for (let i = 0; i < tags.length; i++) {
163
+ const tag = tags[i];
164
+ getType(tag.params, tag.results);
165
+ }
95
166
 
96
- const importSection = importFuncs.length === 0 ? [] : createSection(
97
- Section.import,
98
- encodeVector(importFuncs.map(x => {
99
- return [
100
- 0, 1, x.import.charCodeAt(0),
101
- ExportDesc.func,
102
- getType(x.params, x.returns)
103
- ];
104
- }))
105
- );
167
+ section(Section.type, unsignedLEB128_length(types.length) + types.reduce((acc, x) => acc + x.length, 0));
168
+ unsigned(types.length);
169
+ for (let i = 0; i < types.length; i++) {
170
+ array(types[i]);
171
+ }
172
+ time('type section');
173
+
174
+ if (importFuncs.length > 0) {
175
+ section(Section.import, unsignedLEB128_length(importFuncs.length) + importFuncs.length * 5);
176
+ unsigned(importFuncs.length);
177
+ for (let i = 0; i < importFuncs.length; i++) {
178
+ const x = importFuncs[i];
179
+ byte(0); byte(1);
180
+ byte(x.import.charCodeAt(0));
181
+ byte(ExportDesc.func);
182
+ byte(getType(x.params, x.returns));
183
+ }
184
+ }
106
185
  time('import section');
107
186
 
108
- const funcSection = createSection(
109
- Section.func,
110
- encodeVector(funcs.map(x => getType(x.params, x.returns))) // type indexes
111
- );
112
- time('func section');
187
+ const indirectFuncs = [], exportFuncs = [];
113
188
 
114
- const nameSection = Prefs.d ? customSection('name', encodeNames(funcs)) : [];
115
-
116
- const indirectFuncs = funcs.filter(x => x.indirect);
117
- const tableSection = !funcs.table ? [] : createSection(
118
- Section.table,
119
- encodeVector([ [ Reftype.funcref, 0x00, ...unsignedLEB128(indirectFuncs.length) ] ])
120
- );
121
- time('table section');
122
-
123
- const elementSection = !funcs.table ? [] : createSection(
124
- Section.element,
125
- encodeVector([ [
126
- 0x00,
127
- Opcodes.i32_const, 0, Opcodes.end,
128
- ...encodeVector(indirectFuncs.map(x => unsignedLEB128(x.asmIndex)))
129
- ] ])
130
- );
131
- time('element section');
189
+ section(Section.func, unsignedLEB128_length(funcs.length) + funcs.length);
190
+ unsigned(funcs.length);
191
+ for (let i = 0; i < funcs.length; i++) {
192
+ const x = funcs[i];
193
+ byte(getType(x.params, x.returns));
194
+
195
+ if (x.indirect) indirectFuncs.push(x);
196
+ if (x.export) exportFuncs.push(x);
197
+ }
198
+ time('func section');
132
199
 
133
200
  if (pages.has('func lut')) {
134
201
  if (data.addedFuncArgcLut) {
@@ -182,285 +249,276 @@ export default (funcs, globals, tags, pages, data, noTreeshake = false) => {
182
249
  }
183
250
  time('func lut');
184
251
 
185
- // specially optimized assembly for globals as this version is much (>5x) faster than traditional createSection()
186
- const globalsValues = Object.values(globals);
252
+ if (funcs.table) {
253
+ section(Section.table, unsignedLEB128_length(indirectFuncs.length) + 3);
254
+ byte(1); // table count
255
+ byte(Reftype.funcref); byte(0); // table type
256
+ unsigned(indirectFuncs.length); // table size
257
+ time('table section');
258
+ }
187
259
 
188
- let globalSection = [];
260
+ if (Prefs.alwaysMemory && pages.size === 0) pages.set('--always-memory', 0);
261
+ const usesMemory = pages.size > 0;
262
+ if (usesMemory) {
263
+ const pageCount = Math.ceil((pages.size * pageSize) / PageSize);
264
+ section(Section.memory, unsignedLEB128_length(pageCount) + 2);
265
+ byte(1); // memory count
266
+ byte(0); // memory type
267
+ unsigned(pageCount); // memory size
268
+ time('memory section');
269
+ }
270
+
271
+ const usesTags = tags.length > 0;
272
+ if (usesTags) {
273
+ section(Section.tag, unsignedLEB128_length(tags.length) + tags.length * 2);
274
+ unsigned(tags.length);
275
+ for (let i = 0; i < tags.length; i++) {
276
+ const x = tags[i];
277
+ byte(0); // tag type
278
+ byte(getType(x.params, x.results)); // tag signature
279
+ }
280
+ time('tag section');
281
+ }
282
+
283
+ const globalsValues = Object.values(globals);
189
284
  if (globalsValues.length > 0) {
190
- let data = unsignedLEB128(globalsValues.length);
285
+ section(Section.global, unsignedLEB128_length(globalsValues.length) + globalsValues.length * 4
286
+ + globalsValues.reduce((acc, x) => acc + (x.type === Valtype.f64 ? 8 : signedLEB128_length(x.init ?? 0)), 0));
287
+ unsigned(globalsValues.length);
191
288
  for (let i = 0; i < globalsValues.length; i++) {
192
- const global = globalsValues[i];
193
-
194
- switch (global.type) {
289
+ const x = globalsValues[i];
290
+ switch (x.type) {
195
291
  case Valtype.i32:
196
- if (i > 0) data.push(Opcodes.end, Valtype.i32, 0x01, Opcodes.i32_const);
197
- else data.push(Valtype.i32, 0x01, Opcodes.i32_const);
198
-
199
- signedLEB128_into(global.init ?? 0, data);
292
+ byte(Valtype.i32);
293
+ byte(0x01);
294
+ byte(Opcodes.i32_const);
295
+ signed(x.init ?? 0);
200
296
  break;
201
297
 
202
298
  case Valtype.i64:
203
- if (i > 0) data.push(Opcodes.end, Valtype.i64, 0x01, Opcodes.i64_const);
204
- else data.push(Valtype.i64, 0x01, Opcodes.i64_const);
205
-
206
- signedLEB128_into(global.init ?? 0, data);
299
+ byte(Valtype.i64);
300
+ byte(0x01);
301
+ byte(Opcodes.i64_const);
302
+ signed(x.init ?? 0);
207
303
  break;
208
304
 
209
305
  case Valtype.f64:
210
- if (i > 0) data.push(Opcodes.end, Valtype.f64, 0x01, Opcodes.f64_const);
211
- else data.push(Valtype.f64, 0x01, Opcodes.f64_const);
212
-
213
- ieee754_binary64_into(global.init ?? 0, data);
306
+ byte(Valtype.f64);
307
+ byte(0x01);
308
+ byte(Opcodes.f64_const);
309
+ ieee754(x.init ?? 0);
214
310
  break;
215
311
  }
312
+
313
+ byte(Opcodes.end);
216
314
  }
315
+ time('global section');
316
+ }
217
317
 
218
- data.push(Opcodes.end);
318
+ if (exportFuncs.length > 0 || usesMemory || usesTags) {
319
+ byte(Section.export);
320
+ const sizeOffset = offset, setSize = unsignedPost();
219
321
 
220
- globalSection.push(Section.global);
322
+ unsigned(exportFuncs.length + usesMemory + usesTags);
323
+ if (usesMemory) {
324
+ string('$');
325
+ byte(ExportDesc.mem); byte(0);
326
+ }
327
+ if (usesTags) {
328
+ string('0');
329
+ byte(ExportDesc.tag); byte(0);
330
+ }
221
331
 
222
- unsignedLEB128_into(data.length, globalSection);
223
- globalSection = globalSection.concat(data);
332
+ for (let i = 0; i < exportFuncs.length; i++) {
333
+ const x = exportFuncs[i];
334
+ string(x.name === '#main' ? 'm' : x.name);
335
+ byte(ExportDesc.func);
336
+ unsigned(x.index - importDelta);
337
+ }
338
+
339
+ setSize(offset - sizeOffset - 5);
340
+ time('export section');
224
341
  }
225
- time('global section');
226
342
 
227
- if (Prefs.alwaysMemory && pages.size === 0) pages.set('--always-memory', 0);
343
+ if (funcs.table) {
344
+ section(Section.element, unsignedLEB128_length(indirectFuncs.length) + 5 + indirectFuncs.reduce((acc, x) => acc + unsignedLEB128_length(x.index - importDelta), 0));
345
+ byte(1); // table index
346
+ byte(0); // element type
347
+ byte(Opcodes.i32_const); byte(0); byte(Opcodes.end); // offset
228
348
 
229
- const usesMemory = pages.size > 0;
230
- const memorySection = !usesMemory ? [] : createSection(
231
- Section.memory,
232
- encodeVector([ [ 0x00, ...unsignedLEB128(Math.ceil((pages.size * pageSize) / PageSize)) ] ])
233
- );
234
- time('memory section');
235
-
236
- const exports = funcs.filter(x => x.export).map((x, i) => [ ...encodeString(x.name === '#main' ? 'm' : x.name), ExportDesc.func, ...unsignedLEB128(x.asmIndex) ]);
237
-
238
- // export memory if used
239
- if (usesMemory) exports.unshift([ ...encodeString('$'), ExportDesc.mem, 0x00 ]);
240
- time('gen exports');
241
-
242
- const tagSection = tags.length === 0 ? [] : createSection(
243
- Section.tag,
244
- encodeVector(tags.map(x => [ 0x00, getType(x.params, x.results) ]))
245
- );
246
-
247
- // export first tag if used
248
- if (tags.length !== 0) exports.unshift([ ...encodeString('0'), ExportDesc.tag, 0x00 ]);
249
- time('tag section');
250
-
251
- const exportSection = createSection(
252
- Section.export,
253
- encodeVector(exports)
254
- );
255
- time('export section');
256
-
257
- let codeSection = [];
349
+ unsigned(indirectFuncs.length);
350
+ for (let i = 0; i < indirectFuncs.length; i++) {
351
+ const x = indirectFuncs[i];
352
+ unsigned(x.index - importDelta);
353
+ }
354
+ time('element section');
355
+ }
356
+
357
+ if (data.length > 0) {
358
+ section(Section.data_count, unsignedLEB128_length(data.length));
359
+ unsigned(data.length);
360
+ time('data count section');
361
+ }
362
+
363
+ byte(Section.code);
364
+ const codeSectionSizeOffset = offset, setCodeSectionSize = unsignedPost();
365
+
366
+ unsigned(funcs.length);
258
367
  for (let i = 0; i < funcs.length; i++) {
368
+ const funcSizeOffset = offset, setFuncSize = unsignedPost();
369
+
259
370
  const x = funcs[i];
260
371
  const locals = Object.values(x.locals).sort((a, b) => a.idx - b.idx);
261
372
 
262
373
  const paramCount = x.params.length;
263
- let localDecl = [], typeCount = 0, lastType, declCount = 0;
374
+ let declCount = 0, lastType, typeCount = 0;
264
375
  for (let i = paramCount; i <= locals.length; i++) {
265
376
  const local = locals[i];
266
- if (i !== paramCount && local?.type !== lastType) {
267
- unsignedLEB128_into(typeCount, localDecl);
268
- localDecl.push(lastType);
269
- typeCount = 0;
377
+ if (lastType && local?.type !== lastType) {
270
378
  declCount++;
271
379
  }
272
380
 
273
- typeCount++;
274
381
  lastType = local?.type;
275
382
  }
383
+ unsigned(declCount);
276
384
 
277
- let wasm = [], wasmNonFlat = [];
278
- if (Prefs.d) {
279
- for (let i = 0; i < x.wasm.length; i++) {
280
- let o = x.wasm[i];
281
-
282
- // encode local/global ops as unsigned leb128 from raw number
283
- if (
284
- (o[0] >= Opcodes.local_get && o[0] <= Opcodes.global_set) &&
285
- o[1] > 127
286
- ) {
287
- const n = o[1];
288
- o = [ o[0] ];
289
- unsignedLEB128_into(n, o);
290
- }
291
-
292
- // encode f64.const ops as ieee754 from raw number
293
- if (o[0] === Opcodes.f64_const) {
294
- const n = o[1];
295
- o = ieee754_binary64(n);
296
- if (o.length === 8) o.unshift(Opcodes.f64_const);
297
- }
298
-
299
- // encode call ops as unsigned leb128 from raw number
300
- if ((o[0] === Opcodes.call /* || o[0] === Opcodes.return_call */) && o[1] >= importedFuncs.length) {
301
- const n = o[1] - importDelta;
302
- o = [ Opcodes.call ];
303
- unsignedLEB128_into(n, o);
304
- }
305
-
306
- // encode call indirect ops as types from info
307
- if (o[0] === Opcodes.call_indirect) {
308
- o = [...o];
309
- const params = [];
310
- for (let i = 0; i < o[1]; i++) {
311
- params.push(valtypeBinary, Valtype.i32);
312
- }
313
-
314
- let returns = [ valtypeBinary, Valtype.i32 ];
315
- if (o.at(-1) === 'no_type_return') {
316
- o.pop();
317
- returns = [ valtypeBinary ];
318
- }
319
-
320
- o[1] = getType(params, returns);
321
- }
385
+ lastType = undefined;
386
+ for (let i = paramCount; i <= locals.length; i++) {
387
+ const local = locals[i];
388
+ if (lastType && local?.type !== lastType) {
389
+ unsigned(typeCount);
390
+ byte(lastType);
391
+ typeCount = 0;
392
+ }
322
393
 
323
- for (let j = 0; j < o.length; j++) {
324
- const x = o[j];
325
- if (x == null || !(x <= 0xff)) continue;
326
- wasm.push(x);
327
- }
394
+ typeCount++;
395
+ lastType = local?.type;
396
+ }
328
397
 
329
- wasmNonFlat.push(o);
398
+ for (let i = 0; i < x.wasm.length; i++) {
399
+ let o = x.wasm[i];
400
+ const op = o[0];
401
+
402
+ // encode local/global ops as unsigned leb128 from raw number
403
+ if (
404
+ (op >= Opcodes.local_get && op <= Opcodes.global_set) &&
405
+ o[1] > 127
406
+ ) {
407
+ byte(op);
408
+ unsigned(o[1]);
409
+ continue;
330
410
  }
331
411
 
332
- x.assembled = { localDecl, wasm, wasmNonFlat };
333
- } else {
334
- for (let i = 0; i < x.wasm.length; i++) {
335
- let o = x.wasm[i];
336
- const op = o[0];
337
-
338
- // encode local/global ops as unsigned leb128 from raw number
339
- if (
340
- (op >= Opcodes.local_get && op <= Opcodes.global_set) &&
341
- o[1] > 127
342
- ) {
343
- wasm.push(op);
344
- unsignedLEB128_into(o[1], wasm);
345
- continue;
346
- }
412
+ // encode f64.const ops as ieee754 from raw number
413
+ if (op === Opcodes.f64_const) {
414
+ byte(op);
415
+ ieee754(o[1]);
416
+ continue;
417
+ }
347
418
 
348
- // encode f64.const ops as ieee754 from raw number
349
- if (op === Opcodes.f64_const) {
350
- wasm.push(op);
351
- ieee754_binary64_into(o[1], wasm);
352
- continue;
353
- }
419
+ // encode call ops as unsigned leb128 from raw number
420
+ if ((op === Opcodes.call /* || o[0] === Opcodes.return_call */) && o[1] >= importedFuncs.length) {
421
+ byte(op);
422
+ unsigned(o[1] - importDelta);
423
+ continue;
424
+ }
354
425
 
355
- // encode call ops as unsigned leb128 from raw number
356
- if ((op === Opcodes.call /* || o[0] === Opcodes.return_call */) && o[1] >= importedFuncs.length) {
357
- wasm.push(op);
358
- unsignedLEB128_into(o[1] - importDelta, wasm);
359
- continue;
426
+ // encode call indirect ops as types from info
427
+ if (op === Opcodes.call_indirect) {
428
+ const params = [];
429
+ for (let i = 0; i < o[1]; i++) {
430
+ params.push(valtypeBinary, Valtype.i32);
360
431
  }
361
432
 
362
- // encode call indirect ops as types from info
363
- if (op === Opcodes.call_indirect) {
364
- const params = [];
365
- for (let i = 0; i < o[1]; i++) {
366
- params.push(valtypeBinary, Valtype.i32);
367
- }
368
-
369
- let returns = [ valtypeBinary, Valtype.i32 ];
370
- if (o.at(-1) === 'no_type_return') {
371
- returns = [ valtypeBinary ];
372
- }
373
-
374
- wasm.push(op, getType(params, returns), o[2]);
375
- continue;
376
- }
433
+ byte(op);
434
+ byte(getType(params, [ valtypeBinary, Valtype.i32 ]));
435
+ byte(o[2]);
436
+ continue;
437
+ }
377
438
 
378
- for (let j = 0; j < o.length; j++) {
379
- const x = o[j];
380
- if (x == null || !(x <= 0xff)) continue;
381
- wasm.push(x);
382
- }
439
+ for (let j = 0; j < o.length; j++) {
440
+ const x = o[j];
441
+ if (x == null || !(x <= 0xff)) continue;
442
+ buffer[offset++] = x;
383
443
  }
384
444
  }
385
445
 
386
- if (wasm.length > 100000) {
387
- // slow path for handling large arrays which break v8 due to running out of stack size
388
- const out = unsignedLEB128(declCount)
389
- .concat(localDecl, wasm, Opcodes.end);
390
- codeSection = codeSection.concat(unsignedLEB128(out.length), out);
391
- } else {
392
- codeSection.push(
393
- ...unsignedLEB128(unsignedLEB128_length(declCount) + localDecl.length + wasm.length + 1),
394
- ...unsignedLEB128(declCount),
395
- ...localDecl,
396
- ...wasm,
397
- Opcodes.end
398
- );
446
+ byte(Opcodes.end);
447
+ setFuncSize(offset - funcSizeOffset - 5);
448
+ }
449
+
450
+ setCodeSectionSize(offset - codeSectionSizeOffset - 5);
451
+ time('code section');
452
+
453
+ section(Section.data, unsignedLEB128_length(data.length) + data.reduce((acc, x) =>
454
+ acc + (x.page != null ? (3 + signedLEB128_length(pages.allocs.get(x.page) ?? (pages.get(x.page) * pageSize))) : 1)
455
+ + unsignedLEB128_length(x.bytes.length) + x.bytes.length, 0));
456
+
457
+ unsigned(data.length);
458
+ for (let i = 0; i < data.length; i++) {
459
+ const x = data[i];
460
+ if (x.page != null) { // active
461
+ let offset = pages.allocs.get(x.page) ?? (pages.get(x.page) * pageSize);
462
+ if (offset === 0) offset = 16;
463
+
464
+ byte(0x00);
465
+ byte(Opcodes.i32_const);
466
+ signed(offset);
467
+ byte(Opcodes.end);
468
+ } else { // passive
469
+ byte(0x01);
399
470
  }
400
471
 
401
- // globalThis.progress?.(`${i}/${funcs.length}`);
472
+ unsigned(x.bytes.length);
473
+ array(x.bytes);
402
474
  }
475
+ time('data section');
403
476
 
404
- codeSection.unshift(...unsignedLEB128(funcs.length, codeSection));
405
- codeSection.unshift(Section.code, ...unsignedLEB128(codeSection.length));
406
- time('code section');
477
+ if (Prefs.d) {
478
+ byte(Section.custom);
479
+ const totalSizeOffset = offset, setTotalSize = unsignedPost();
480
+ string('name');
407
481
 
408
- const typeSection = createSection(
409
- Section.type,
410
- encodeVector(types)
411
- );
412
- time('type section');
482
+ const section = (id, cb) => {
483
+ byte(id);
484
+ const sizeOffset = offset, setSize = unsignedPost();
485
+ cb();
486
+ setSize(offset - sizeOffset - 5);
487
+ };
413
488
 
414
- let dataSection = [];
415
- if (data.length > 0) {
416
- for (let i = 0; i < data.length; i++) {
417
- const x = data[i];
418
- if (Prefs.d && x.bytes.length > PageSize) log.warning('assemble', `data (${x.page}) has more bytes than Wasm page size! (${x.bytes.length})`);
419
-
420
- if (x.page != null) {
421
- // type: active
422
- let offset = pages.allocs.get(x.page) ?? (pages.get(x.page) * pageSize);
423
- if (offset === 0) offset = 16;
424
- dataSection.push(0x00, Opcodes.i32_const, ...signedLEB128(offset), Opcodes.end);
425
- } else {
426
- // type: passive
427
- dataSection.push(0x01);
489
+ section(0, () => { // module
490
+ string('js'); // todo: filename?
491
+ });
492
+
493
+ section(1, () => { // funcs
494
+ unsigned(funcs.length);
495
+ for (let i = 0; i < funcs.length; i++) {
496
+ const x = funcs[i];
497
+ unsigned(x.index - importDelta);
498
+ string(x.name);
428
499
  }
500
+ });
429
501
 
430
- if (x.bytes.length > 100000) {
431
- codeSection = codeSection.concat(unsignedLEB128(x.bytes.length), x.bytes);
432
- } else {
433
- dataSection.push(
434
- ...unsignedLEB128(x.bytes.length),
435
- ...x.bytes
436
- );
502
+ section(2, () => { // locals
503
+ unsigned(funcs.length);
504
+ for (let i = 0; i < funcs.length; i++) {
505
+ const x = funcs[i];
506
+ unsigned(x.index - importDelta);
507
+
508
+ const locals = Object.keys(x.locals);
509
+ unsigned(locals.length);
510
+ for (let j = 0; j < locals.length; j++) {
511
+ const name = locals[j];
512
+ unsigned(x.locals[name]);
513
+ string(name);
514
+ }
437
515
  }
438
- }
516
+ });
439
517
 
440
- dataSection.unshift(...unsignedLEB128(data.length, dataSection));
441
- dataSection.unshift(Section.data, ...unsignedLEB128(dataSection.length));
518
+ setTotalSize(offset - totalSizeOffset - 5);
519
+ time('name section');
442
520
  }
443
521
 
444
- const dataCountSection = data.length === 0 ? [] : createSection(
445
- Section.data_count,
446
- unsignedLEB128(data.length)
447
- );
448
- time('data section');
449
-
450
- return Uint8Array.from([
451
- ...Magic,
452
- ...typeSection,
453
- ...importSection,
454
- ...funcSection,
455
- ...tableSection,
456
- ...memorySection,
457
- ...tagSection,
458
- ...globalSection,
459
- ...exportSection,
460
- ...elementSection,
461
- ...dataCountSection,
462
- ...codeSection,
463
- ...dataSection,
464
- ...nameSection
465
- ]);
522
+ buffer = buffer.subarray(0, offset);
523
+ return buffer;
466
524
  };
@@ -2516,6 +2516,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
2516
2516
  const wrapperArgc = Prefs.indirectWrapperArgc ?? 10;
2517
2517
  const underflow = wrapperArgc - args.length;
2518
2518
  for (let i = 0; i < underflow; i++) args.push(DEFAULT_VALUE());
2519
+ if (args.length > wrapperArgc) args = args.slice(0, wrapperArgc);
2519
2520
 
2520
2521
  for (let i = 0; i < args.length; i++) {
2521
2522
  const arg = args[i];
@@ -2715,9 +2716,7 @@ const generateThis = (scope, decl) => {
2715
2716
 
2716
2717
  if (!scope.constr && !scope.method) {
2717
2718
  // this in a non-constructor context is a reference to globalThis
2718
- return [
2719
- ...generate(scope, { type: 'Identifier', name: 'globalThis' })
2720
- ];
2719
+ return generate(scope, { type: 'Identifier', name: 'globalThis' });
2721
2720
  }
2722
2721
 
2723
2722
  // opt: do not check for pure constructors or strict mode
@@ -84,6 +84,28 @@ export const unsignedLEB128_length = n => {
84
84
  return length;
85
85
  };
86
86
 
87
+ export const signedLEB128_length = n => {
88
+ if (n >= -64 && n <= 63) return 1;
89
+ if (n >= -8192 && n <= 8191) return 2;
90
+ if (n >= -1048576 && n <= 1048575) return 3;
91
+ if (n >= -134217728 && n <= 134217727) return 4;
92
+
93
+ let length = 0;
94
+ while (true) {
95
+ let byte = n & 0x7f;
96
+ n >>= 7;
97
+
98
+ if ((n === 0 && (byte & 0x40) === 0) || (n === -1 && (byte & 0x40) !== 0)) {
99
+ length++;
100
+ break;
101
+ } else {
102
+ length++;
103
+ }
104
+ }
105
+
106
+ return length;
107
+ };
108
+
87
109
  export const big_signedLEB128 = n => {
88
110
  // just input for small numbers (for perf as common)
89
111
  if (n >= 0n && n <= 63n) return [ Number(n) ];
package/compiler/wrap.js CHANGED
@@ -401,7 +401,7 @@ export default (source, module = undefined, customImports = {}, print = str => p
401
401
  const surrounding = Prefs.backtraceSurrounding ?? 10;
402
402
  let min = middleIndex - surrounding;
403
403
  let max = middleIndex + surrounding + 1;
404
- if (Prefs.backtraceFunc || middleIndex == -1) {
404
+ if (Prefs.backtraceFunc || middleIndex === -1) {
405
405
  min = 0;
406
406
  max = func.wasm.length;
407
407
  }
@@ -415,7 +415,7 @@ export default (source, module = undefined, customImports = {}, print = str => p
415
415
  longest = Math.max(longest, noAnsi(disasm[j])?.length ?? 0);
416
416
  }
417
417
 
418
- if (middleIndex != -1) {
418
+ if (middleIndex !== -1) {
419
419
  const middle = Math.floor(disasm.length / 2);
420
420
  disasm[middle] = `\x1B[47m\x1B[30m${noAnsi(disasm[middle])}${'\u00a0'.repeat(longest - noAnsi(disasm[middle]).length)}\x1B[0m`;
421
421
  }
@@ -429,47 +429,11 @@ export default (source, module = undefined, customImports = {}, print = str => p
429
429
  if (funcInd == null || blobOffset == null ||
430
430
  Number.isNaN(funcInd) || Number.isNaN(blobOffset) || Prefs.backtrace === false) return false;
431
431
 
432
- // convert blob offset -> function wasm offset
433
- const func = funcs.find(x => x.asmIndex === funcInd);
432
+ const func = funcs.find(x => (x.index - importDelta) === funcInd);
434
433
  if (!func) return false;
435
434
 
436
- const { wasm: assembledWasmFlat, wasmNonFlat: assembledWasmOps, localDecl } = func.assembled;
437
- const toFind = encodeVector(localDecl).concat(assembledWasmFlat.slice(0, 100));
438
-
439
- let i = 0;
440
- for (; i < wasm.length; i++) {
441
- let mismatch = false;
442
- for (let j = 0; j < toFind.length; j++) {
443
- if (wasm[i + j] !== toFind[j]) {
444
- mismatch = true;
445
- break;
446
- }
447
- }
448
-
449
- if (!mismatch) break;
450
- }
451
-
452
- if (i === wasm.length) {
453
- printBacktrace(-1, func, funcs, globals, exceptions);
454
- return false;
455
- }
456
-
457
- const offset = (blobOffset - i) - encodeVector(localDecl).length;
458
-
459
- let cumLen = 0;
460
- i = 0;
461
- for (; i < assembledWasmOps.length; i++) {
462
- cumLen += assembledWasmOps[i].filter(x => x != null && x <= 0xff).length;
463
- if (cumLen === offset) break;
464
- }
465
-
466
- if (cumLen !== offset) {
467
- printBacktrace(-1, func, funcs, globals, exceptions);
468
- return false;
469
- }
470
-
471
- printBacktrace(i + 1, func, funcs, globals, exceptions);
472
- return true;
435
+ printBacktrace(-1, func, funcs, globals, exceptions);
436
+ return false;
473
437
  };
474
438
 
475
439
  const t2 = performance.now();
package/foo.js CHANGED
@@ -1 +1,17 @@
1
- console.log(process.argv);
1
+ import compile from './compiler/wrap.js';
2
+ import { Worker, isMainThread } from 'node:worker_threads';
3
+
4
+ import { join } from 'node:path';
5
+ const __dirname = import.meta.dirname;
6
+ const __filename = join(__dirname, 'foo.js');
7
+
8
+ if (isMainThread) {
9
+ const worker = new Worker(__filename);
10
+ } else {
11
+ for (let i = 0; i < 500; i++) {
12
+ const js = `console.log(globalThis);`;
13
+ const out = compile(js);
14
+ out.exports.main();
15
+ console.log(i);
16
+ }
17
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "porffor",
3
3
  "description": "An ahead-of-time JavaScript compiler",
4
- "version": "0.57.19",
4
+ "version": "0.57.21",
5
5
  "author": "Oliver Medhurst <honk@goose.icu>",
6
6
  "license": "MIT",
7
7
  "scripts": {},
package/r.js ADDED
@@ -0,0 +1,243 @@
1
+ function isConfigurable(obj, name) {
2
+ if (Object.hasOwn(obj, name)) return Object.getOwnPropertyDescriptor(obj, name).configurable;
3
+ return true;
4
+ }
5
+
6
+ function isEnumerable(obj, name) {
7
+ return Object.hasOwn(obj, name) && Object.getOwnPropertyDescriptor(obj, name).enumerable;
8
+ }
9
+
10
+ function isSameValue(a, b) {
11
+ if (a === 0 && b === 0) return 1 / a === 1 / b;
12
+ if (a !== a && b !== b) return true;
13
+
14
+ return a === b;
15
+ }
16
+
17
+ function isWritable(obj, name, verifyProp, value) {
18
+ if (Object.hasOwn(obj, name) && Object.getOwnPropertyDescriptor(obj, name).writable != null) return Object.getOwnPropertyDescriptor(obj, name).writable;
19
+ if (!Object.hasOwn(obj, name) && Object.isExtensible(obj)) return true;
20
+
21
+ var unlikelyValue = Array.isArray(obj) && name === "length" ?
22
+ Math.pow(2, 32) - 1 :
23
+ "unlikelyValue";
24
+ var newValue = value || unlikelyValue;
25
+ var hadValue = Object.hasOwn(obj, name);
26
+ var oldValue = obj[name];
27
+ var writeSucceeded;
28
+
29
+ try {
30
+ obj[name] = newValue;
31
+ } catch {}
32
+
33
+ writeSucceeded = isSameValue(obj[verifyProp || name], newValue);
34
+
35
+ if (writeSucceeded) {
36
+ if (hadValue) {
37
+ obj[name] = oldValue;
38
+ } else {
39
+ delete obj[name];
40
+ }
41
+ }
42
+
43
+ return writeSucceeded;
44
+ }
45
+
46
+ function verifyProperty(obj, name, desc, options) {
47
+ var originalDesc = Object.getOwnPropertyDescriptor(obj, name);
48
+
49
+ if (desc === undefined) {
50
+ if (originalDesc !== undefined) {
51
+ throw new Test262Error('verifyProperty: expected undefined descriptor');
52
+ }
53
+
54
+ return true;
55
+ }
56
+
57
+ if (!Object.hasOwn(obj, name)) throw new Test262Error('verifyProperty: obj should have own property');
58
+
59
+ if (Object.hasOwn(desc, 'value')) {
60
+ const v = desc.value;
61
+ if (!isSameValue(originalDesc.value, v)) throw new Test262Error('verifyProperty: descriptor value mismatch');
62
+ // if (!isSameValue(obj[name], v)) throw new Test262Error('verifyProperty: object value mismatch');
63
+ }
64
+
65
+ if (Object.hasOwn(desc, 'enumerable')) {
66
+ if (desc.enumerable !== originalDesc.enumerable ||
67
+ desc.enumerable !== isEnumerable(obj, name)) {
68
+ throw new Test262Error('enumerable fail');
69
+ }
70
+ }
71
+
72
+ if (Object.hasOwn(desc, 'writable')) {
73
+ if (desc.writable !== originalDesc.writable ||
74
+ desc.writable !== isWritable(obj, name)) {
75
+ throw new Test262Error('writable fail');
76
+ }
77
+ }
78
+
79
+ if (Object.hasOwn(desc, 'configurable')) {
80
+ if (desc.configurable !== originalDesc.configurable ||
81
+ desc.configurable !== isConfigurable(obj, name)) {
82
+ throw new Test262Error('configurable fail');
83
+ }
84
+ }
85
+
86
+ if (options && options.restore) {
87
+ Object.defineProperty(obj, name, originalDesc);
88
+ }
89
+
90
+ return true;
91
+ }
92
+
93
+ function verifyEqualTo(obj, name, value) {
94
+ if (!isSameValue(obj[name], value)) {
95
+ throw new Test262Error('propertyHelper verifyEqualTo failed');
96
+ }
97
+ }
98
+
99
+ function verifyWritable(obj, name, verifyProp, value) {
100
+ if (!verifyProp) {
101
+ if (!Object.getOwnPropertyDescriptor(obj, name).writable)
102
+ throw new Test262Error('propertyHelper verifyWritable failed');
103
+ }
104
+
105
+ if (!isWritable(obj, name, verifyProp, value)) {
106
+ throw new Test262Error('propertyHelper verifyWritable failed');
107
+ }
108
+ }
109
+
110
+ function verifyNotWritable(obj, name, verifyProp, value) {
111
+ if (!verifyProp) {
112
+ if (Object.getOwnPropertyDescriptor(obj, name).writable)
113
+ throw new Test262Error('propertyHelper verifyNotWritable failed');
114
+ }
115
+
116
+ if (isWritable(obj, name, verifyProp)) {
117
+ throw new Test262Error('propertyHelper verifyNotWritable failed');
118
+ }
119
+ }
120
+
121
+ function verifyEnumerable(obj, name) {
122
+ if (!isEnumerable(obj, name)) {
123
+ throw new Test262Error('propertyHelper verifyEnumerable failed');
124
+ }
125
+ }
126
+
127
+ function verifyNotEnumerable(obj, name) {
128
+ if (isEnumerable(obj, name)) {
129
+ throw new Test262Error('propertyHelper verifyNotEnumerable failed');
130
+ }
131
+ }
132
+
133
+ function verifyConfigurable(obj, name) {
134
+ if (!isConfigurable(obj, name)) {
135
+ throw new Test262Error('propertyHelper verifyConfigurable failed');
136
+ }
137
+ }
138
+
139
+ function verifyNotConfigurable(obj, name) {
140
+ if (isConfigurable(obj, name)) {
141
+ throw new Test262Error('propertyHelper verifyNotConfigurable failed');
142
+ }
143
+ }
144
+ var assert = mustBeTrue => {
145
+ if (mustBeTrue === true) {
146
+ return;
147
+ }
148
+
149
+ throw new Test262Error('assert failed');
150
+ };
151
+ assert; // idk why exactly but this fixes many tests by forcing indirect ref
152
+
153
+ var __assert_throws = (expectedErrorConstructor, func) => {
154
+ if (typeof func !== 'function') {
155
+ throw new Test262Error('assert.throws invoked with a non-function value');
156
+ }
157
+
158
+ try {
159
+ func();
160
+ } catch {
161
+ return;
162
+ }
163
+
164
+ throw new Test262Error('assert.throws failed');
165
+ };
166
+
167
+ var __assert__isSameValue = (a, b) => {
168
+ if (a === b) {
169
+ // Handle +/-0 vs. -/+0
170
+ return a !== 0 || 1 / a === 1 / b;
171
+ }
172
+
173
+ // Handle NaN vs. NaN
174
+ return a !== a && b !== b;
175
+ };
176
+
177
+ var __assert_sameValue = (actual, expected) => {
178
+ if (assert._isSameValue(actual, expected)) {
179
+ return;
180
+ }
181
+
182
+ throw new Test262Error('assert.sameValue failed');
183
+ };
184
+
185
+ var __assert_notSameValue = (actual, unexpected) => {
186
+ if (!assert._isSameValue(actual, unexpected)) {
187
+ return;
188
+ }
189
+
190
+ throw new Test262Error('assert.notSameValue failed');
191
+ };
192
+ // define our $262 here too
193
+ // var $262 = {
194
+ // global: globalThis,
195
+ // gc() { /* noop */ },
196
+ // detachArrayBuffer(buffer) {
197
+ // return Porffor.arraybuffer.detach(buffer);
198
+ // },
199
+ // getGlobal(name) {
200
+ // return globalThis[name];
201
+ // },
202
+ // // todo: setGlobal
203
+ // destroy() { /* noop */ },
204
+ // agent: {}
205
+ // };
206
+
207
+ // function Test262Error(message) {
208
+ // this.message = message;
209
+ // this.name = 'Test262Error';
210
+ // }
211
+
212
+ // var __Test262Error_thrower = message => {
213
+ // throw new Test262Error(message);
214
+ // };
215
+
216
+ var $DONOTEVALUATE = () => {
217
+ throw 'Test262: This statement should not be evaluated.';
218
+ };
219
+ // Copyright (C) 2019 Aleksey Shvayka. All rights reserved.
220
+ // This code is governed by the BSD license found in the LICENSE file.
221
+ /*---
222
+ esid: sec-array-constructor
223
+ description: >
224
+ Property descriptor of Array
225
+ info: |
226
+ 22.1.1 The Array Constructor
227
+
228
+ * is the initial value of the Array property of the global object.
229
+
230
+ 17 ECMAScript Standard Built-in Objects
231
+
232
+ Every other data property described in clauses 18 through 26 and in Annex B.2
233
+ has the attributes { [[Writable]]: true, [[Enumerable]]: false,
234
+ [[Configurable]]: true } unless otherwise specified.
235
+ includes: [propertyHelper.js]
236
+ ---*/
237
+
238
+ verifyProperty(this, 'Array', {
239
+ value: Array,
240
+ writable: true,
241
+ enumerable: false,
242
+ configurable: true,
243
+ });
package/runtime/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from 'node:fs';
3
- globalThis.version = '0.57.19';
3
+ globalThis.version = '0.57.21';
4
4
 
5
5
  // deno compat
6
6
  if (typeof process === 'undefined' && typeof Deno !== 'undefined') {