porffor 0.2.0-fde989a → 0.14.0-032e4ad08

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.
Files changed (64) hide show
  1. package/CONTRIBUTING.md +262 -0
  2. package/LICENSE +20 -20
  3. package/README.md +135 -94
  4. package/asur/README.md +2 -0
  5. package/asur/index.js +1262 -0
  6. package/byg/index.js +216 -0
  7. package/compiler/2c.js +66 -54
  8. package/compiler/{sections.js → assemble.js} +109 -21
  9. package/compiler/builtins/annexb_string.js +72 -0
  10. package/compiler/builtins/annexb_string.ts +19 -0
  11. package/compiler/builtins/array.ts +225 -0
  12. package/compiler/builtins/base64.ts +77 -0
  13. package/compiler/builtins/boolean.ts +20 -0
  14. package/compiler/builtins/crypto.ts +121 -0
  15. package/compiler/builtins/date.ts +2069 -0
  16. package/compiler/builtins/error.js +22 -0
  17. package/compiler/builtins/escape.ts +140 -0
  18. package/compiler/builtins/function.ts +7 -0
  19. package/compiler/builtins/int.ts +147 -0
  20. package/compiler/builtins/math.ts +410 -0
  21. package/compiler/builtins/number.ts +531 -0
  22. package/compiler/builtins/object.ts +6 -0
  23. package/compiler/builtins/porffor.d.ts +60 -0
  24. package/compiler/builtins/set.ts +199 -0
  25. package/compiler/builtins/string.ts +1081 -0
  26. package/compiler/builtins/symbol.ts +62 -0
  27. package/compiler/builtins.js +466 -284
  28. package/compiler/{codeGen.js → codegen.js} +1573 -656
  29. package/compiler/decompile.js +3 -4
  30. package/compiler/embedding.js +22 -22
  31. package/compiler/encoding.js +94 -10
  32. package/compiler/expression.js +1 -1
  33. package/compiler/generated_builtins.js +2110 -0
  34. package/compiler/index.js +29 -44
  35. package/compiler/log.js +6 -3
  36. package/compiler/opt.js +55 -41
  37. package/compiler/parse.js +38 -30
  38. package/compiler/precompile.js +121 -0
  39. package/compiler/prefs.js +31 -0
  40. package/compiler/prototype.js +209 -201
  41. package/compiler/types.js +38 -0
  42. package/compiler/wasmSpec.js +33 -8
  43. package/compiler/wrap.js +154 -89
  44. package/package.json +9 -5
  45. package/porf +2 -0
  46. package/porffor_tmp.c +202 -0
  47. package/rhemyn/compile.js +46 -27
  48. package/rhemyn/parse.js +322 -320
  49. package/rhemyn/test/parse.js +58 -58
  50. package/runner/compare.js +33 -34
  51. package/runner/debug.js +117 -0
  52. package/runner/index.js +80 -12
  53. package/runner/profiler.js +75 -0
  54. package/runner/repl.js +58 -15
  55. package/runner/sizes.js +37 -37
  56. package/runner/version.js +10 -8
  57. package/compiler/builtins/base64.js +0 -92
  58. package/filesize.cmd +0 -2
  59. package/runner/info.js +0 -89
  60. package/runner/profile.js +0 -46
  61. package/runner/results.json +0 -1
  62. package/runner/transform.js +0 -15
  63. package/tmp.c +0 -661
  64. package/util/enum.js +0 -20
package/byg/index.js ADDED
@@ -0,0 +1,216 @@
1
+ import fs from 'node:fs';
2
+
3
+ const noAnsi = s => s.replace(/\u001b\[[0-9]+m/g, '');
4
+ const printLine = (line, number, breakpoint = false, current = false, selected = false) => {
5
+ console.log(`\x1b[${breakpoint ? (selected ? '43' : '103') : (selected ? '47' : '100')}m\x1b[${selected || breakpoint ? '30' : '97'}m${number.toFixed(0).padStart(4, ' ')}\x1b[${breakpoint ? (selected ? '33' : '93') : (selected ? '37' : '90')}m\x1b[${current ? '47' : '40'}m▌ \x1b[${current ? '47' : '40'}m\x1b[${current ? '30' : '37'}m${current ? noAnsi(line) : line}\x1b[0K`);
6
+ };
7
+
8
+ const box = (x, y, width, height, title = '', content = [], color = ['90', '100', '37', '35', '45'], padding = true) => {
9
+ if (padding) {
10
+ width += 1;
11
+ height += 1;
12
+
13
+ // top
14
+ process.stdout.write(`\x1b[48m\x1b[${y + 1};${x + 1}H\x1b[${color[0]}m` + '▄'.repeat(width));
15
+ // bottom
16
+ process.stdout.write(`\x1b[${y + height + 1};${x + 1}H▝` + '▀'.repeat(width - 1) + '▘');
17
+ // left
18
+ process.stdout.write(`\x1b[${y + 1};${x + 1}H▗` + '\x1b[1B\x1b[1D▐'.repeat(height - 1));
19
+ // right
20
+ process.stdout.write(`\x1b[${y + 1};${x + width + 1}H▖` + '\x1b[1B\x1b[1D▌'.repeat(height - 1));
21
+
22
+ x += 1;
23
+ y += 1;
24
+ width -= 1;
25
+ height -= 1;
26
+ }
27
+
28
+ // bg
29
+ process.stdout.write(`\x1b[${y + 1};${x + 1}H\x1b[${color[1]}m` + ' '.repeat(width) + (`\x1b[1B\x1b[${width}D` + ' '.repeat(width)).repeat(Math.max(0, height - 1)));
30
+
31
+ // title
32
+ if (title) process.stdout.write(`\x1b[${y};${x}H\x1b[0m\x1b[${color[3]}m▐\x1b[${color[4]}m\x1b[${color[2]}m\x1b[1m${' '.repeat((width - title.length) / 2 | 0)}${title}${' '.repeat(width - (((width - title.length) / 2 | 0)) - title.length)}\x1b[0m\x1b[${color[3]}m▌`);
33
+
34
+ // content
35
+ process.stdout.write(`\x1b[${y + (title ? 1 : 1)};${x + 1}H\x1b[${color[1]}m\x1b[${color[2]}m${content.join(`\x1b[1B\x1b[${x + 1}G`)}`);
36
+ };
37
+
38
+ const controls = {
39
+ 'ret': 'resume ',
40
+ 'b': 'breakpoint ',
41
+ 's': 'step over',
42
+ 'i': 'step in',
43
+ 'o': 'step out ',
44
+ };
45
+
46
+ const controlInfo = Object.keys(controls).reduce((acc, x, i) => acc + `\x1B[45m\x1B[97m${x}\x1b[105m\x1b[37m ${controls[x]} `, '');
47
+ const plainControlInfo = noAnsi(controlInfo);
48
+
49
+ globalThis.termWidth = process.stdout.columns || 80;
50
+ globalThis.termHeight = process.stdout.rows || 24;
51
+
52
+ export default ({ lines, pause, breakpoint }) => {
53
+ process.stdin.setRawMode(true);
54
+ process.stdin.resume();
55
+ process.stdin.setEncoding('utf8');
56
+
57
+ process.stdin.on('data', s => {
58
+ // ctrl c
59
+ if (s === '\u0003') {
60
+ process.exit();
61
+ }
62
+
63
+ if (!paused) pause();
64
+ });
65
+
66
+ const stdin = fs.openSync('/dev/stdin', 'r+');
67
+ const readCharSync = () => {
68
+ const buffer = Buffer.alloc(1);
69
+ fs.readSync(stdin, buffer, 0, 1);
70
+ return buffer.toString('utf8');
71
+ };
72
+
73
+ const tooManyLines = lines.length > (termHeight - 1);
74
+ const breakpoints = {};
75
+
76
+ process.on('exit', () => {
77
+ process.stdout.write('\x1b[0m');
78
+ });
79
+
80
+ console.clear();
81
+
82
+ let paused = true;
83
+ return (_paused, currentLine, text, boxes = []) => {
84
+ paused = _paused;
85
+
86
+ let scrollOffset = 0;
87
+ let currentLinePos = currentLine;
88
+
89
+ const draw = () => {
90
+ console.clear();
91
+ process.stdout.write(`\x1b[1;1H`);
92
+
93
+ if (tooManyLines) {
94
+ const edgePadding = (termHeight / 2) - 1;
95
+ let beforePadding = currentLine - edgePadding;
96
+ let afterPadding = currentLine + edgePadding + 1;
97
+
98
+ if (beforePadding < 0) {
99
+ afterPadding += Math.abs(beforePadding);
100
+ beforePadding = 0;
101
+ }
102
+
103
+ beforePadding += scrollOffset;
104
+ afterPadding += scrollOffset;
105
+
106
+ if (afterPadding > lines.length) {
107
+ beforePadding -= afterPadding - lines.length;
108
+ afterPadding = lines.length;
109
+ }
110
+
111
+ for (let i = Math.max(0, beforePadding); i < Math.max(0, beforePadding) + (termHeight - 1); i++) {
112
+ printLine(lines[i], i + 1, !!breakpoints[i], currentLine === i, currentLine + scrollOffset === i);
113
+ }
114
+
115
+ currentLinePos = currentLine - beforePadding;
116
+ } else {
117
+ for (let i = 0; i < lines.length; i++) {
118
+ printLine(lines[i], i + 1, !!breakpoints[i], currentLine === i, currentLine + scrollOffset === i);
119
+ }
120
+ }
121
+
122
+ for (const x of boxes) {
123
+ const y = x.y({ currentLinePos });
124
+ if (y < 0 || y >= termHeight) continue;
125
+
126
+ box(x.x, y, x.width, x.height, x.title, x.content);
127
+ }
128
+
129
+ // text += ` | rss: ${(process.memoryUsage.rss() / 1024 / 1024).toFixed(2)}mb`;
130
+
131
+ process.stdout.write(`\x1b[${termHeight};1H\x1b[105m\x1b[37m${text}${' '.repeat(termWidth - plainControlInfo.length - noAnsi(text).length - 1)}${controlInfo} \x1b[0m`);
132
+ };
133
+
134
+ draw();
135
+
136
+ let lastSpecial = false;
137
+ while (true) {
138
+ const char = readCharSync();
139
+
140
+ if (char === '[') {
141
+ lastSpecial = true;
142
+ continue;
143
+ }
144
+
145
+ switch (char.toLowerCase()) {
146
+ case '\r': {
147
+ paused = false;
148
+ return 'resume';
149
+ }
150
+
151
+ case 's': {
152
+ return 'stepOver';
153
+ }
154
+
155
+ case 'i': {
156
+ return 'stepIn';
157
+ }
158
+
159
+ case 'o': {
160
+ return 'stepOut';
161
+ }
162
+
163
+ case 'b': {
164
+ if (!lastSpecial) {
165
+ // b pressed normally
166
+ breakpoints[currentLine + scrollOffset] = !breakpoints[currentLine + scrollOffset];
167
+ draw();
168
+
169
+ breakpoint(currentLine + scrollOffset, breakpoints[currentLine + scrollOffset]);
170
+ break;
171
+ }
172
+
173
+ // arrow down
174
+ if (scrollOffset < lines.length - currentLine - 1) scrollOffset++;
175
+ draw();
176
+ break;
177
+ }
178
+
179
+ case 'a': {
180
+ if (!lastSpecial) break;
181
+
182
+ // arrow up
183
+ if (scrollOffset > -currentLine) scrollOffset--;
184
+ draw();
185
+
186
+ break;
187
+ }
188
+
189
+ case '5': {
190
+ if (!lastSpecial) break;
191
+
192
+ // page up
193
+ scrollOffset -= Math.min(scrollOffset + currentLine, termHeight - 1);
194
+ draw();
195
+ break;
196
+ }
197
+
198
+ case '6': {
199
+ if (!lastSpecial) break;
200
+
201
+ // page down
202
+ scrollOffset += Math.min(lines.length - (scrollOffset + currentLine) - 1, termHeight - 1);
203
+ draw();
204
+ break;
205
+ }
206
+
207
+ case 'q':
208
+ case '\u0003': {
209
+ process.exit();
210
+ }
211
+ }
212
+
213
+ lastSpecial = false;
214
+ }
215
+ };
216
+ };
package/compiler/2c.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { read_ieee754_binary64, read_signedLEB128, read_unsignedLEB128 } from './encoding.js';
2
2
  import { Blocktype, Opcodes, Valtype } from './wasmSpec.js';
3
3
  import { operatorOpcode } from './expression.js';
4
- import { log } from "./log.js";
4
+ import { log } from './log.js';
5
5
 
6
6
  const CValtype = {
7
7
  i8: 'i8',
@@ -106,17 +106,6 @@ for (const x in CValtype) {
106
106
  if (Valtype[x]) CValtype[Valtype[x]] = CValtype[x];
107
107
  }
108
108
 
109
- const todo = msg => {
110
- class TodoError extends Error {
111
- constructor(message) {
112
- super(message);
113
- this.name = 'TodoError';
114
- }
115
- }
116
-
117
- throw new TodoError(`todo: ${msg}`);
118
- };
119
-
120
109
  const removeBrackets = str => {
121
110
  // return str;
122
111
  // if (str.startsWith(`(${CValtype.i32})(${CValtype.u32})`)) return `(${CValtype.i32})(${CValtype.u32})(` + removeBrackets(str.slice(22, -1)) + ')';
@@ -130,6 +119,9 @@ const removeBrackets = str => {
130
119
  };
131
120
 
132
121
  export default ({ funcs, globals, tags, data, exceptions, pages }) => {
122
+ // fix declaring order for c
123
+ funcs.reverse();
124
+
133
125
  const invOperatorOpcode = Object.values(operatorOpcode).reduce((acc, x) => {
134
126
  for (const k in x) {
135
127
  acc[x[k]] = k;
@@ -167,10 +159,10 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
167
159
  prependMain.set('_data', data.map(x => `memcpy(_memory + ${x.offset}, (unsigned char[]){${x.bytes.join(',')}}, ${x.bytes.length});`).join('\n'));
168
160
  }
169
161
 
170
- // for (const [ x, p ] of pages) {
171
- // out += `${CValtype[p.type]} ${x.replace(': ', '_').replace(/[^0-9a-zA-Z_]/g, '')}[100]`;
172
- // out += ';\n';
173
- // }
162
+ if (importFuncs.find(x => x.name === '__Porffor_readArgv')) {
163
+ prepend.set('argv', `int _argc; char** _argv;`);
164
+ prependMain.set('argv', `_argc = argc; _argv = argv;`);
165
+ }
174
166
 
175
167
  if (out) out += '\n';
176
168
 
@@ -213,9 +205,9 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
213
205
 
214
206
  for (const f of funcs) {
215
207
  depth = 1;
208
+ brDepth = 0;
216
209
 
217
210
  const invLocals = inv(f.locals, x => x.idx);
218
- // if (f.returns.length > 1) todo('funcs returning >1 value unsupported');
219
211
 
220
212
  for (const x in invLocals) {
221
213
  invLocals[x] = sanitize(invLocals[x]);
@@ -223,8 +215,9 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
223
215
 
224
216
  const returns = f.returns.length > 0;
225
217
 
226
- const shouldInline = f.internal;
227
- out += `${f.name === 'main' ? 'int' : (f.internal ? (returns ? CValtype.f64 : 'void') : 'struct ReturnValue')} ${shouldInline ? 'inline ' : ''}${sanitize(f.name)}(${f.params.map((x, i) => `${CValtype[x]} ${invLocals[i]}`).join(', ')}) {\n`;
218
+ const shouldInline = false; // f.internal;
219
+ if (f.name === 'main') out += `int main(${prependMain.has('argv') ? 'int argc, char* argv[]' : ''}) {\n`;
220
+ else out += `${f.internal ? (returns ? CValtype.f64 : 'void') : 'struct ReturnValue'} ${shouldInline ? 'inline ' : ''}${sanitize(f.name)}(${f.params.map((x, i) => `${CValtype[x]} ${invLocals[i]}`).join(', ')}) {\n`;
228
221
 
229
222
  if (f.name === 'main') {
230
223
  out += [...prependMain.values()].join('\n');
@@ -247,12 +240,7 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
247
240
  const brs = [];
248
241
  let lastCond = false;
249
242
 
250
- // let brDepth = 0;
251
-
252
243
  const blockStart = (i, loop) => {
253
- // reset "stack"
254
- // vals = [];
255
-
256
244
  rets.push(i[1]);
257
245
 
258
246
  const br = brId++;
@@ -269,25 +257,6 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
269
257
  brDepth++;
270
258
  };
271
259
 
272
- const highlight = i => {
273
- const surrounding = 6;
274
-
275
- const decomp = decompile(f.wasm.slice(i - surrounding, i + surrounding + 1), '', 0, f.locals, f.params, f.returns, funcs, globals, exceptions).slice(0, -1).split('\n');
276
-
277
- const noAnsi = s => s.replace(/\u001b\[[0-9]+m/g, '');
278
- let longest = 0;
279
- for (let j = 0; j < decomp.length; j++) {
280
- longest = Math.max(longest, noAnsi(decomp[j]).length);
281
- }
282
-
283
- const middle = Math.floor(decomp.length / 2);
284
- decomp[middle] = `\x1B[47m\x1B[30m${noAnsi(decomp[middle])}${'\u00a0'.repeat(longest - noAnsi(decomp[middle]).length)}\x1B[0m`;
285
-
286
- console.log('\x1B[90m...\x1B[0m');
287
- console.log(decomp.join('\n'));
288
- console.log('\x1B[90m...\x1B[0m\n');
289
- };
290
-
291
260
  for (let _ = 0; _ < f.wasm.length; _++) {
292
261
  const i = f.wasm[_];
293
262
  if (!i || !i[0]) continue;
@@ -415,8 +384,6 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
415
384
  const br = brs.at(-1);
416
385
  const ret = rets.at(-1);
417
386
  if (ret && ret !== Blocktype.void) {
418
- // console.log(vals, ret);
419
- // console.log(decompile(f.wasm.slice(_ - 5, _ + 1)));
420
387
  if (vals.length > 0) line(`_r${br} = ${removeBrackets(vals.pop())}`);
421
388
  // vals.push(`_r${br}`);
422
389
  }
@@ -425,8 +392,6 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
425
392
  line(`} else {`, false);
426
393
  depth++;
427
394
 
428
- // reset "stack"
429
- // vals = [];
430
395
  break;
431
396
  }
432
397
 
@@ -441,8 +406,6 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
441
406
  const br = brs.pop();
442
407
  const ret = rets.pop();
443
408
  if (ret && ret !== Blocktype.void) {
444
- // console.log(vals, ret);
445
- // console.log(decompile(f.wasm.slice(_ - 5, _ + 1)));
446
409
  if (vals.length > 0) line(`_r${br} = ${removeBrackets(vals.pop())}`);
447
410
  vals.push(`_r${br}`);
448
411
  }
@@ -505,6 +468,60 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
505
468
  winIncludes.set('windows.h', true);
506
469
  break;
507
470
 
471
+ case '__Porffor_readArgv':
472
+ includes.set('stdlib.h', true);
473
+
474
+ prepend.set('__Porffor_readArgv',
475
+ `void __Porffor_readArgv(u32 index, u32 outPtr) {
476
+ if (index >= _argc) {
477
+ printf("expected %d arguments\\n", index);
478
+ exit(1);
479
+ }
480
+
481
+ char* arg = _argv[index];
482
+
483
+ u32 read = 0;
484
+ char* out = _memory + outPtr + 4;
485
+ char ch;
486
+ while ((ch = *(arg++)) != 0) {
487
+ out[read++] = ch;
488
+ }
489
+
490
+ memcpy(_memory + outPtr, &read, sizeof(read));
491
+ }`);
492
+
493
+ line(`__Porffor_readArgv((u32)(${vals.at(-2)}), (u32)(${vals.pop()}))`);
494
+ vals.pop();
495
+ break;
496
+
497
+ case '__Porffor_readFile':
498
+ includes.set('stdio.h', true);
499
+ includes.set('stdlib.h', true);
500
+
501
+ prepend.set('__Porffor_readFile',
502
+ `void __Porffor_readFile(u32 pathPtr, u32 outPtr) {
503
+ char* path = _memory + pathPtr + 4;
504
+ FILE* fp = fopen(path, "r");
505
+ if (fp == NULL) {
506
+ printf("failed to open file: %s\\n", path);
507
+ exit(1);
508
+ }
509
+
510
+ u32 read = 0;
511
+ char* out = _memory + outPtr + 4;
512
+ char ch;
513
+ while ((ch = fgetc(fp)) != EOF) {
514
+ out[read++] = ch;
515
+ }
516
+
517
+ fclose(fp);
518
+
519
+ memcpy(_memory + outPtr, &read, sizeof(read));
520
+ }`);
521
+ line(`__Porffor_readFile((u32)(${vals.at(-2)}), (u32)(${vals.pop()}))`);
522
+ vals.pop();
523
+ break;
524
+
508
525
  default:
509
526
  log.warning('2c', `unimplemented import: ${importFunc.name}`);
510
527
  break;
@@ -541,18 +558,14 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
541
558
 
542
559
  case Opcodes.br: {
543
560
  const ret = rets[brDepth - i[1] - 1];
544
- // console.log(rets, brDepth, i[1], brDepth - i[1] - 1, ret, vals);
545
561
  if (ret !== Blocktype.void) line(`_r${brs[brDepth - i[1] - 1]} = ${removeBrackets(vals.pop())}`);
546
562
  line(`goto j${brs[brDepth - i[1] - 1]}`);
547
563
 
548
- // // reset "stack"
549
- // vals = [];
550
564
  break;
551
565
  }
552
566
 
553
567
  case Opcodes.br_if: {
554
568
  const ret = rets[brDepth - i[1] - 1];
555
- // console.log(rets, brDepth, i[1], brDepth - i[1] - 1, ret, vals);
556
569
 
557
570
  let cond = removeBrackets(vals.pop());
558
571
  if (!lastCond) {
@@ -602,7 +615,6 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
602
615
  }
603
616
 
604
617
  log.warning('2c', `unimplemented op: ${invOpcodes[i[0]]}`);
605
- // todo(`unimplemented op: ${invOpcodes[i[0]]}`);
606
618
  }
607
619
 
608
620
  lastCond = false;
@@ -1,8 +1,8 @@
1
- import { Valtype, FuncType, Empty, ExportDesc, Section, Magic, ModuleVersion, Opcodes, PageSize } from './wasmSpec.js';
2
- import { encodeVector, encodeString, encodeLocal, unsignedLEB128, signedLEB128 } from './encoding.js';
3
- import { number } from './embedding.js';
1
+ import { Valtype, FuncType, ExportDesc, Section, Magic, ModuleVersion, Opcodes, PageSize, Reftype } from './wasmSpec.js';
2
+ import { encodeVector, encodeString, encodeLocal, unsignedLEB128, signedLEB128, unsignedLEB128_into, signedLEB128_into, ieee754_binary64_into } from './encoding.js';
4
3
  import { importedFuncs } from './builtins.js';
5
- import { log } from "./log.js";
4
+ import { log } from './log.js';
5
+ import Prefs from './prefs.js';
6
6
 
7
7
  const createSection = (type, data) => [
8
8
  type,
@@ -26,12 +26,12 @@ export default (funcs, globals, tags, pages, data, flags) => {
26
26
 
27
27
  const optLevel = parseInt(process.argv.find(x => x.startsWith('-O'))?.[2] ?? 1);
28
28
 
29
- const compileHints = process.argv.includes('-compile-hints');
30
- if (compileHints) log.warning('sections', 'compile hints is V8 only w/ experimental arg! (you used -compile-hints)');
29
+ const compileHints = Prefs.compileHints;
30
+ if (compileHints) log.warning('assemble', 'compile hints is V8 only w/ experimental arg! (you used -compile-hints)');
31
31
 
32
32
  const getType = (params, returns) => {
33
33
  const hash = `${params.join(',')}_${returns.join(',')}`;
34
- if (optLog) log('sections', `getType(${JSON.stringify(params)}, ${JSON.stringify(returns)}) -> ${hash} | cache: ${typeCache[hash]}`);
34
+ if (Prefs.optLog) log('assemble', `getType(${JSON.stringify(params)}, ${JSON.stringify(returns)}) -> ${hash} | cache: ${typeCache[hash]}`);
35
35
  if (optLevel >= 1 && typeCache[hash] !== undefined) return typeCache[hash];
36
36
 
37
37
  const type = [ FuncType, ...encodeVector(params), ...encodeVector(returns) ];
@@ -44,7 +44,7 @@ export default (funcs, globals, tags, pages, data, flags) => {
44
44
 
45
45
  let importFuncs = [];
46
46
 
47
- if (optLevel < 1) {
47
+ if (optLevel < 1 || !Prefs.treeshakeWasmImports) {
48
48
  importFuncs = importedFuncs;
49
49
  } else {
50
50
  let imports = new Map();
@@ -65,6 +65,7 @@ export default (funcs, globals, tags, pages, data, flags) => {
65
65
  importFuncs = [...imports.values()];
66
66
 
67
67
  // fix call indexes for non-imports
68
+ // also fix call_indirect types
68
69
  const delta = importedFuncs.length - importFuncs.length;
69
70
  for (const f of funcs) {
70
71
  f.originalIndex = f.index;
@@ -74,16 +75,26 @@ export default (funcs, globals, tags, pages, data, flags) => {
74
75
  if ((inst[0] === Opcodes.call || inst[0] === Opcodes.return_call) && inst[1] >= importedFuncs.length) {
75
76
  inst[1] -= delta;
76
77
  }
78
+
79
+ if (inst[0] === Opcodes.call_indirect) {
80
+ const params = [];
81
+ for (let i = 0; i < inst[1]; i++) {
82
+ params.push(valtypeBinary, Valtype.i32);
83
+ }
84
+
85
+ const returns = [ valtypeBinary, Valtype.i32 ];
86
+ inst[1] = getType(params, returns);
87
+ }
77
88
  }
78
89
  }
79
90
  }
80
91
  globalThis.importFuncs = importFuncs;
81
92
 
82
- if (optLog) log('sections', `treeshake: using ${importFuncs.length}/${importedFuncs.length} imports`);
93
+ if (Prefs.optLog) log('assemble', `treeshake: using ${importFuncs.length}/${importedFuncs.length} imports`);
83
94
 
84
95
  const importSection = importFuncs.length === 0 ? [] : createSection(
85
96
  Section.import,
86
- encodeVector(importFuncs.map(x => [ 0, ...encodeString(x.import), ExportDesc.func, getType(new Array(x.params).fill(valtypeBinary), new Array(x.returns).fill(valtypeBinary)) ]))
97
+ encodeVector(importFuncs.map(x => [ 0, ...encodeString(x.import), ExportDesc.func, getType(new Array(x.params).fill(x.name.startsWith('profile') ? Valtype.i32 : valtypeBinary), new Array(x.returns).fill(valtypeBinary)) ]))
87
98
  );
88
99
 
89
100
  const funcSection = createSection(
@@ -91,22 +102,89 @@ export default (funcs, globals, tags, pages, data, flags) => {
91
102
  encodeVector(funcs.map(x => getType(x.params, x.returns))) // type indexes
92
103
  );
93
104
 
94
- // compilation hints section - unspecd, v8 only
95
- // https://github.com/WebAssembly/design/issues/1473#issuecomment-1431274746
96
- const chSection = !compileHints ? [] : customSection(
97
- 'compilationHints',
98
- // for now just do everything as optimise eager
99
- encodeVector(funcs.map(_ => chHint(0x02, 0x02, 0x02)))
105
+ const tableSection = !funcs.table ? [] : createSection(
106
+ Section.table,
107
+ encodeVector([ [ Reftype.funcref, 0x00, funcs.length ] ])
100
108
  );
101
109
 
102
- const globalSection = Object.keys(globals).length === 0 ? [] : createSection(
103
- Section.global,
104
- encodeVector(Object.keys(globals).map(x => [ globals[x].type, 0x01, ...number(globals[x].init ?? 0, globals[x].type).flat(), Opcodes.end ]))
110
+ const elementSection = !funcs.table ? [] : createSection(
111
+ Section.element,
112
+ encodeVector([ [
113
+ 0x00,
114
+ Opcodes.i32_const, 0, Opcodes.end,
115
+ ...encodeVector(funcs.map(x => x.index))
116
+ ] ])
105
117
  );
106
118
 
119
+ if (pages.has('func argc lut')) {
120
+ // generate func argc lut data
121
+ const bytes = [];
122
+ for (let i = 0; i < funcs.length; i++) {
123
+ const argc = Math.floor(funcs[i].params.length / 2);
124
+ bytes.push(argc % 256, (argc / 256 | 0) % 256);
125
+ }
126
+
127
+ data.push({
128
+ offset: pages.get('func argc lut').ind * pageSize,
129
+ bytes
130
+ });
131
+ }
132
+
133
+ // const t0 = performance.now();
134
+
135
+ // specially optimized assembly for globals as this version is much (>5x) faster than traditional createSection()
136
+ const globalsValues = Object.values(globals);
137
+
138
+ let globalSection = [];
139
+ if (globalsValues.length > 0) {
140
+ let data = unsignedLEB128(globalsValues.length);
141
+ for (let i = 0; i < globalsValues.length; i++) {
142
+ const global = globalsValues[i];
143
+
144
+ switch (global.type) {
145
+ case Valtype.i32:
146
+ if (i > 0) data.push(Opcodes.end, Valtype.i32, 0x01, Opcodes.i32_const);
147
+ else data.push(Valtype.i32, 0x01, Opcodes.i32_const);
148
+
149
+ signedLEB128_into(global.init ?? 0, data);
150
+ break;
151
+
152
+ case Valtype.i64:
153
+ if (i > 0) data.push(Opcodes.end, Valtype.i64, 0x01, Opcodes.i64_const);
154
+ else data.push(Valtype.i64, 0x01, Opcodes.i64_const);
155
+
156
+ signedLEB128_into(global.init ?? 0, data);
157
+ break;
158
+
159
+ case Valtype.f64:
160
+ if (i > 0) data.push(Opcodes.end, Valtype.f64, 0x01, Opcodes.f64_const);
161
+ else data.push(Valtype.f64, 0x01, Opcodes.f64_const);
162
+
163
+ ieee754_binary64_into(global.init ?? 0, data);
164
+ break;
165
+ }
166
+ }
167
+
168
+ data.push(Opcodes.end);
169
+
170
+ globalSection.push(Section.global);
171
+
172
+ unsignedLEB128_into(data.length, globalSection);
173
+ globalSection = globalSection.concat(data);
174
+ }
175
+
176
+ // if (Prefs.profileCompiler) {
177
+ // const log = console.log;
178
+ // console.log = function () {
179
+ // log.apply(this, arguments);
180
+ // console.log = log;
181
+ // console.log(` a. assembled global section in ${(performance.now() - t0).toFixed(2)}ms\n`);
182
+ // };
183
+ // }
184
+
107
185
  const exports = funcs.filter(x => x.export).map((x, i) => [ ...encodeString(x.name === 'main' ? 'm' : x.name), ExportDesc.func, x.index ]);
108
186
 
109
- if (process.argv.includes('-always-memory') && pages.size === 0) pages.set('-always-memory', 0);
187
+ if (Prefs.alwaysMemory && pages.size === 0) pages.set('--always-memory', 0);
110
188
  if (optLevel === 0) pages.set('O0 precaution', 0);
111
189
 
112
190
  const usesMemory = pages.size > 0;
@@ -169,7 +247,7 @@ export default (funcs, globals, tags, pages, data, flags) => {
169
247
  unsignedLEB128(data.length)
170
248
  );
171
249
 
172
- if (process.argv.includes('-sections')) console.log({
250
+ if (Prefs.sections) console.log({
173
251
  typeSection: typeSection.map(x => x.toString(16)),
174
252
  importSection: importSection.map(x => x.toString(16)),
175
253
  funcSection: funcSection.map(x => x.toString(16)),
@@ -179,6 +257,14 @@ export default (funcs, globals, tags, pages, data, flags) => {
179
257
  dataSection: dataSection.map(x => x.toString(16)),
180
258
  });
181
259
 
260
+ // compilation hints section - unspecd, v8 only
261
+ // https://github.com/WebAssembly/design/issues/1473#issuecomment-1431274746
262
+ const chSection = !compileHints ? [] : customSection(
263
+ 'compilationHints',
264
+ // for now just do everything as optimize eager
265
+ encodeVector(funcs.map(_ => chHint(0x02, 0x02, 0x02)))
266
+ );
267
+
182
268
  return Uint8Array.from([
183
269
  ...Magic,
184
270
  ...ModuleVersion,
@@ -186,10 +272,12 @@ export default (funcs, globals, tags, pages, data, flags) => {
186
272
  ...importSection,
187
273
  ...funcSection,
188
274
  ...chSection,
275
+ ...tableSection,
189
276
  ...memorySection,
190
277
  ...tagSection,
191
278
  ...globalSection,
192
279
  ...exportSection,
280
+ ...elementSection,
193
281
  ...dataCountSection,
194
282
  ...codeSection,
195
283
  ...dataSection