porffor 0.16.0-fe07da0f4 → 0.17.0-418ce1445

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.
package/CONTRIBUTING.md CHANGED
@@ -26,7 +26,7 @@ You can also swap out `node` in the alias to use another runtime like Deno (`den
26
26
 
27
27
  ### Precompile
28
28
 
29
- **If you update any file inside `compiler/builtins` you will need to do this for it to update inside Porffor otherwise your changes will have no effect.** Run `node compiler/precompile.js` to precompile. It may error during this, if so, you might have an error in your code or there could be a compiler error with Porffor (feel free to ask for help as soon as you encounter any errors with it).
29
+ **If you update any file inside `compiler/builtins` you will need to do this for it to update inside Porffor otherwise your changes will have no effect.** Run `./porf precompile` to precompile. It may error during this, if so, you might have an error in your code or there could be a compiler error with Porffor (feel free to ask for help as soon as you encounter any errors with it).
30
30
 
31
31
  <br>
32
32
 
@@ -233,7 +233,7 @@ builtins/tostring_number: impl radix
233
233
 
234
234
  ## Test262
235
235
 
236
- Make sure you have Test262 cloned already **inside of `test262/`** (`git clone https://github.com/tc39/test262.git test262/test262`) and run `npm install` inside `test262/` too.
236
+ For the first time, ensure you run `./test262/setup.sh`.
237
237
 
238
238
  Run `node test262` to run all the tests and get an output of total overall test results.
239
239
 
package/README.md CHANGED
@@ -29,7 +29,7 @@ Expect nothing to work! Only very limited JS is currently supported. See files i
29
29
  > [!WARNING]
30
30
  > Compiling to native binaries uses [2c](#2c), Porffor's own Wasm -> C compiler, which is experimental.
31
31
 
32
- **`porf native path/to/script.js out(.exe)`**. You can specify the compiler with `--compiler=clang/zig/gcc`, and which opt level to use with `--cO=O3` (`Ofast` by default). Output binaries are also stripped by default.
32
+ **`porf native path/to/script.js out(.exe)`**. You can specify the compiler with `--compiler=clang|gcc|zig` (`clang` by default), and which optimization level to use with `--cO=Ofast|O3|O2|O1|O0` (`Ofast` by default). Output binaries are also stripped by default.
33
33
 
34
34
  ### Compiling to C
35
35
  > [!WARNING]
@@ -49,7 +49,7 @@ Expect nothing to work! Only very limited JS is currently supported. See files i
49
49
 
50
50
  **`porf debug path/to/script.js`**
51
51
 
52
- ### Profiling the generated Wasm of a JS file
52
+ ### Debugging the compiled Wasm of a JS file
53
53
  > [!WARNING]
54
54
  > Very experimental WIP feature!
55
55
 
@@ -63,26 +63,14 @@ Expect nothing to work! Only very limited JS is currently supported. See files i
63
63
  - `--valtype=i32|i64|f64` (default: `f64`) to set valtype
64
64
  - `-O0` to disable opt
65
65
  - `-O1` (default) to enable basic opt (simplify insts, treeshake wasm imports)
66
- - `-O2` to enable advanced opt (inlining). unstable
67
- - `-O3` to enable advanceder opt (precompute const math). unstable
68
- - `--no-run` to not run wasm output, just compile
69
- - `--opt-log` to log some opts
70
- - `--code-log` to log some codegen (you probably want `-funcs`)
71
- - `--regex-log` to log some regex
72
- - `--funcs` to log funcs
73
- - `--ast-log` to log AST
74
- - `--opt-funcs` to log funcs after opt
75
- - `--sections` to log sections as hex
76
- - `--opt-no-inline` to not inline any funcs
77
- - `--tail-call` to enable tail calls (experimental + not widely implemented)
78
- - `--compile-hints` to enable V8 compilation hints (experimental + doesn't seem to do much?)
66
+ - `-O2` to enable advanced opt (inlining). unstable!
67
+ - `-O3` to enable advanceder opt (precompute const math). unstable!
79
68
 
80
69
  ## Limitations
81
70
  - No full object support yet
82
71
  - Little built-ins/prototype
83
72
  - No async/promise/await
84
73
  - No variables between scopes (except args and globals)
85
- - Literal callees only in calls (eg `print()` works, `a = print; a()` does not)
86
74
  - No `eval()` etc (since it is AOT)
87
75
 
88
76
  ## Sub-engines
@@ -112,7 +100,7 @@ These include some early (stage 1/0) and/or dead (last commit years ago) proposa
112
100
 
113
101
  - Number literals
114
102
  - Declaring functions
115
- - Calling functions *literal callees only*
103
+ - Calling functions
116
104
  - `return`
117
105
  - `let`/`const`/`var` basic declarations
118
106
  - Some basic integer operators (`+-/*%`)
package/compiler/2c.js CHANGED
@@ -2,10 +2,11 @@ import { read_ieee754_binary64, read_signedLEB128, read_unsignedLEB128 } from '.
2
2
  import { Blocktype, Opcodes, Valtype } from './wasmSpec.js';
3
3
  import { operatorOpcode } from './expression.js';
4
4
  import { log } from './log.js';
5
+ import Prefs from './prefs.js';
5
6
 
6
7
  const CValtype = {
7
- i8: 'i8',
8
- i16: 'i16',
8
+ i8: 'u8',
9
+ i16: 'u16',
9
10
  i32: 'i32',
10
11
  u32: 'u32',
11
12
  i64: 'i64',
@@ -17,8 +18,8 @@ const CValtype = {
17
18
  undefined: 'void'
18
19
  };
19
20
 
20
- const alwaysPreface = `typedef uint8_t i8;
21
- typedef uint16_t i16;
21
+ const alwaysPreface = `typedef uint8_t u8;
22
+ typedef uint16_t u16;
22
23
  typedef int32_t i32;
23
24
  typedef uint32_t u32;
24
25
  typedef int64_t i64;
@@ -29,72 +30,123 @@ typedef double f64;
29
30
  f64 NAN = 0e+0/0e+0;
30
31
 
31
32
  struct ReturnValue {
32
- ${CValtype.f64} value;
33
- ${CValtype.i32} type;
33
+ f64 value;
34
+ i32 type;
34
35
  };\n\n`;
35
36
 
36
- // todo: is memcpy/etc safe with host endianness?
37
+ // todo: review whether 2cMemcpy should be default or not
37
38
 
38
39
  // all:
39
40
  // immediates: ['align', 'offset']
40
- const CMemFuncs = {
41
+ const CMemFuncs = Prefs['2cMemcpy'] ? {
41
42
  [Opcodes.i32_store]: {
42
43
  c: `memcpy(_memory + offset + pointer, &value, sizeof(value));`,
43
44
  args: ['pointer', 'value'],
44
- argTypes: [CValtype.i32, CValtype.i32],
45
+ argTypes: ['i32', 'i32'],
45
46
  returns: false
46
47
  },
47
48
  [Opcodes.i32_store16]: {
48
49
  c: `memcpy(_memory + offset + pointer, &value, sizeof(value));`,
49
50
  args: ['pointer', 'value'],
50
- argTypes: [CValtype.i32, CValtype.i16],
51
+ argTypes: ['i32', 'u16'],
51
52
  returns: false
52
53
  },
53
54
  [Opcodes.i32_store8]: {
54
55
  c: `memcpy(_memory + offset + pointer, &value, sizeof(value));`,
55
56
  args: ['pointer', 'value'],
56
- argTypes: [CValtype.i32, CValtype.i8],
57
+ argTypes: ['i32', 'u8'],
57
58
  returns: false
58
59
  },
59
60
 
60
61
  [Opcodes.i32_load]: {
61
- c: `${CValtype.i32} out;
62
+ c: `i32 out;
62
63
  memcpy(&out, _memory + offset + pointer, sizeof(out));
63
64
  return out;`,
64
65
  args: ['pointer'],
65
- argTypes: [CValtype.i32],
66
- returns: CValtype.i32
66
+ argTypes: ['i32'],
67
+ returns: 'i32'
67
68
  },
68
69
  [Opcodes.i32_load16_u]: {
69
- c: `${CValtype.i16} out;
70
+ c: `u16 out;
70
71
  memcpy(&out, _memory + offset + pointer, sizeof(out));
71
72
  return out;`,
72
73
  args: ['pointer'],
73
- argTypes: [CValtype.i32],
74
- returns: CValtype.i32
74
+ argTypes: ['i32'],
75
+ returns: 'i32'
75
76
  },
76
77
  [Opcodes.i32_load8_u]: {
77
- c: `${CValtype.i8} out;
78
+ c: `u8 out;
78
79
  memcpy(&out, _memory + offset + pointer, sizeof(out));
79
80
  return out;`,
80
81
  args: ['pointer'],
81
- argTypes: [CValtype.i32],
82
- returns: CValtype.i32
82
+ argTypes: ['i32'],
83
+ returns: 'i32'
83
84
  },
84
85
 
85
86
  [Opcodes.f64_store]: {
86
87
  c: `memcpy(_memory + offset + pointer, &value, sizeof(value));`,
87
88
  args: ['pointer', 'value'],
88
- argTypes: [CValtype.i32, CValtype.f64],
89
+ argTypes: ['i32', 'f64'],
89
90
  returns: false
90
91
  },
91
92
  [Opcodes.f64_load]: {
92
- c: `${CValtype.f64} out;
93
+ c: `f64 out;
93
94
  memcpy(&out, _memory + offset + pointer, sizeof(out));
94
95
  return out;`,
95
96
  args: ['pointer'],
96
- argTypes: [CValtype.i32],
97
- returns: CValtype.f64
97
+ argTypes: ['i32'],
98
+ returns: 'f64'
99
+ },
100
+ } : {
101
+ [Opcodes.i32_store]: {
102
+ c: `*((i32*)(_memory + offset + pointer)) = value;`,
103
+ args: ['pointer', 'value'],
104
+ argTypes: ['i32', 'i32'],
105
+ returns: false
106
+ },
107
+ [Opcodes.i32_store16]: {
108
+ c: `*((u16*)(_memory + offset + pointer)) = value;`,
109
+ args: ['pointer', 'value'],
110
+ argTypes: ['i32', 'u16'],
111
+ returns: false
112
+ },
113
+ [Opcodes.i32_store8]: {
114
+ c: `*((u8*)(_memory + offset + pointer)) = value;`,
115
+ args: ['pointer', 'value'],
116
+ argTypes: ['i32', 'u8'],
117
+ returns: false
118
+ },
119
+
120
+ [Opcodes.i32_load]: {
121
+ c: `return *((i32*)(_memory + offset + pointer));`,
122
+ args: ['pointer'],
123
+ argTypes: ['i32'],
124
+ returns: 'i32'
125
+ },
126
+ [Opcodes.i32_load16_u]: {
127
+ c: `return *((u16*)(_memory + offset + pointer));`,
128
+ args: ['pointer'],
129
+ argTypes: ['i32'],
130
+ returns: 'i32'
131
+ },
132
+ [Opcodes.i32_load8_u]: {
133
+ c: `return *((u8*)(_memory + offset + pointer));`,
134
+ args: ['pointer'],
135
+ argTypes: ['i32'],
136
+ returns: 'i32'
137
+ },
138
+
139
+ [Opcodes.f64_store]: {
140
+ c: `*((f64*)(_memory + offset + pointer)) = value;`,
141
+ args: ['pointer', 'value'],
142
+ argTypes: ['i32', 'f64'],
143
+ returns: false
144
+ },
145
+ [Opcodes.f64_load]: {
146
+ c: `return *((f64*)(_memory + offset + pointer));`,
147
+ args: ['pointer'],
148
+ argTypes: ['i32'],
149
+ returns: 'f64'
98
150
  },
99
151
  };
100
152
 
@@ -108,7 +160,7 @@ for (const x in CValtype) {
108
160
 
109
161
  const removeBrackets = str => {
110
162
  // return str;
111
- // if (str.startsWith(`(${CValtype.i32})(${CValtype.u32})`)) return `(${CValtype.i32})(${CValtype.u32})(` + removeBrackets(str.slice(22, -1)) + ')';
163
+ // if (str.startsWith('(i32)(u32)')) return '(i32)(u32)(' + removeBrackets(str.slice(22, -1)) + ')';
112
164
 
113
165
  for (const x in CValtype) {
114
166
  const p = `(${x})`;
@@ -152,11 +204,16 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
152
204
 
153
205
  if (pages.size > 0) {
154
206
  prepend.set('_memory', `char _memory[${pages.size * pageSize}];\n`);
155
- includes.set('string.h', true);
207
+ if (Prefs['2cMemcpy']) includes.set('string.h', true);
156
208
  }
157
209
 
158
210
  if (data.length > 0) {
159
- prependMain.set('_data', data.map(x => `memcpy(_memory + ${x.offset}, (unsigned char[]){${x.bytes.join(',')}}, ${x.bytes.length});`).join('\n'));
211
+ if (Prefs['2cMemcpy']) {
212
+ prependMain.set('_data', data.map(x => `memcpy(_memory + ${x.offset}, (unsigned char[]){${x.bytes.join(',')}}, ${x.bytes.length});`).join('\n '));
213
+ includes.set('string.h', true);
214
+ } else {
215
+ prependMain.set('_data', data.map(x => x.bytes.reduce((acc, y, i) => acc + `_memory[${x.offset + i}]=(u8)${y};`, '')).join('\n '));
216
+ }
160
217
  }
161
218
 
162
219
  if (importFuncs.find(x => x.name === '__Porffor_readArgv')) {
@@ -168,10 +225,10 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
168
225
 
169
226
  let depth = 1;
170
227
  let brDepth = 0;
171
- const line = (str, semi = true) => out += `${' '.repeat(depth * 2 + brDepth * 2)}${str}${semi ? ';' : ''}\n`;
228
+ const line = (str, semi = true) => out += `${' '.repeat((depth + brDepth) * 2)}${str}${semi ? ';' : ''}\n`;
172
229
  const lines = lines => {
173
230
  for (const x of lines) {
174
- out += `${' '.repeat(depth * 2)}${x}\n`;
231
+ out += `${' '.repeat((depth + brDepth) * 2)}${x}\n`;
175
232
  }
176
233
  };
177
234
 
@@ -219,10 +276,10 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
219
276
 
220
277
  const shouldInline = false; // f.internal;
221
278
  if (f.name === 'main') out += `int main(${prependMain.has('argv') ? 'int argc, char* argv[]' : ''}) {\n`;
222
- 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`;
279
+ else out += `${f.internal ? (returns ? 'f64' : 'void') : 'struct ReturnValue'} ${shouldInline ? 'inline ' : ''}${sanitize(f.name)}(${f.params.map((x, i) => `${CValtype[x]} ${invLocals[i]}`).join(', ')}) {\n`;
223
280
 
224
281
  if (f.name === 'main') {
225
- out += [...prependMain.values()].join('\n');
282
+ out += ' ' + [...prependMain.values()].join('\n ');
226
283
  if (prependMain.size > 0) out += '\n\n';
227
284
  }
228
285
 
@@ -283,12 +340,12 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
283
340
  switch (i[1]) {
284
341
  // i32_trunc_sat_f64_s
285
342
  case 0x02:
286
- vals.push(`(${CValtype.i32})(${vals.pop()})`);
343
+ vals.push(`(i32)(${vals.pop()})`);
287
344
  break;
288
345
 
289
346
  // i32_trunc_sat_f64_u
290
347
  case 0x03:
291
- vals.push(`(${CValtype.u32})(${vals.pop()})`);
348
+ vals.push(`(u32)(${vals.pop()})`);
292
349
  break;
293
350
  }
294
351
 
@@ -337,7 +394,7 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
337
394
 
338
395
  case Opcodes.f64_trunc:
339
396
  // vals.push(`trunc(${vals.pop()})`);
340
- vals.push(`(${CValtype.i32})(${removeBrackets(vals.pop())})`); // this is ~10x faster with clang??
397
+ vals.push(`(i32)(${removeBrackets(vals.pop())})`); // this is ~10x faster with clang??
341
398
  break;
342
399
 
343
400
  case Opcodes.f64_convert_i32_u:
@@ -345,7 +402,7 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
345
402
  case Opcodes.f64_convert_i64_u:
346
403
  case Opcodes.f64_convert_i64_s:
347
404
  // int to f64
348
- vals.push(`(${CValtype.f64})(${removeBrackets(vals.pop())})`);
405
+ vals.push(`(f64)(${removeBrackets(vals.pop())})`);
349
406
  break;
350
407
 
351
408
  case Opcodes.i32_eqz:
@@ -353,7 +410,7 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
353
410
  vals.push(`!(${removeBrackets(vals.pop())})`);
354
411
  } else {
355
412
  let cond = '(' + removeBrackets(vals.pop());
356
- if (cond.startsWith(`(${CValtype.i32})`)) cond = `${cond.slice(`(${CValtype.i32})`.length)}) == 0e+0`;
413
+ if (cond.startsWith(`(i32)`)) cond = `${cond.slice(5)} == 0e+0`;
357
414
  else cond += ') == 0';
358
415
  vals.push(cond);
359
416
  }
@@ -362,13 +419,16 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
362
419
 
363
420
  case Opcodes.return:
364
421
  // line(`return${returns ? ` ${removeBrackets(vals.pop())}` : ''}`);
365
- line(`return${returns ? ` (struct ReturnValue){ ${removeBrackets(vals.pop())}, ${removeBrackets(vals.pop())} }` : ''}`);
422
+ const b = vals.pop();
423
+ const a = vals.pop();
424
+ line(`return${returns ? ` (struct ReturnValue){ ${removeBrackets(a)}, ${removeBrackets(b)} }` : ''}`);
425
+ // line(`return${returns ? ` (struct ReturnValue){ ${removeBrackets(vals.pop())}, ${removeBrackets(vals.pop())} }` : ''}`);
366
426
  break;
367
427
 
368
428
  case Opcodes.if: {
369
429
  let cond = removeBrackets(vals.pop());
370
430
  if (!lastCond) {
371
- if (cond.startsWith(`(${CValtype.i32})`)) cond = `(${cond.slice(`(${CValtype.i32})`.length)}) != 0e+0`;
431
+ if (cond.startsWith(`(i32)`)) cond = `${cond.slice(5)} != 0e+0`;
372
432
  else cond = `(${cond}) != 0`;
373
433
  }
374
434
 
@@ -434,28 +494,16 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
434
494
  const importFunc = importFuncs[i[1]];
435
495
  switch (importFunc.name) {
436
496
  case 'print':
437
- // line(`printf("%f\\n", ${vals.pop()})`);
438
497
  line(`printf("${valtype === 'f64' ? '%g' : '%i'}\\n", ${vals.pop()})`);
439
498
  includes.set('stdio.h', true);
440
499
  break;
441
500
  case 'printChar':
442
- line(`printf("%c", (int)(${vals.pop()}))`);
501
+ line(`putchar((int)(${vals.pop()}))`);
443
502
  includes.set('stdio.h', true);
444
503
  break;
445
504
 
446
505
  case 'time':
447
506
  line(`double _time_out`);
448
- /* platformSpecific(
449
- `FILETIME _time_filetime;
450
- GetSystemTimeAsFileTime(&_time_filetime);
451
-
452
- ULARGE_INTEGER _time_ularge;
453
- _time_ularge.LowPart = _time_filetime.dwLowDateTime;
454
- _time_ularge.HighPart = _time_filetime.dwHighDateTime;
455
- _time_out = (_time_ularge.QuadPart - 116444736000000000i64) / 10000.;`,
456
- `struct timespec _time;
457
- clock_gettime(CLOCK_MONOTONIC, &_time);
458
- _time_out = _time.tv_nsec / 1000000.;`); */
459
507
  platformSpecific(
460
508
  `LARGE_INTEGER _time_freq, _time_t;
461
509
  QueryPerformanceFrequency(&_time_freq);
@@ -470,14 +518,11 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
470
518
  winIncludes.set('windows.h', true);
471
519
  break;
472
520
 
473
- case '__Porffor_readArgv':
474
- includes.set('stdlib.h', true);
475
-
521
+ case '__Porffor_readArgv': {
476
522
  prepend.set('__Porffor_readArgv',
477
- `void __Porffor_readArgv(u32 index, u32 outPtr) {
523
+ `i32 __Porffor_readArgv(u32 index, u32 outPtr) {
478
524
  if (index >= _argc) {
479
- printf("expected %d arguments\\n", index);
480
- exit(1);
525
+ return -1;
481
526
  }
482
527
 
483
528
  char* arg = _argv[index];
@@ -489,24 +534,25 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
489
534
  out[read++] = ch;
490
535
  }
491
536
 
492
- memcpy(_memory + outPtr, &read, sizeof(read));
537
+ *((i32*)(_memory + outPtr)) = (i32)read;
538
+ return read;
493
539
  }`);
494
540
 
495
- line(`__Porffor_readArgv((u32)(${vals.at(-2)}), (u32)(${vals.pop()}))`);
496
- vals.pop();
541
+ const outPtr = vals.pop();
542
+ const index = vals.pop();
543
+ vals.push(`(f64)__Porffor_readArgv((u32)(${index}), (u32)(${outPtr}))`);
497
544
  break;
545
+ }
498
546
 
499
- case '__Porffor_readFile':
547
+ case '__Porffor_readFile': {
500
548
  includes.set('stdio.h', true);
501
- includes.set('stdlib.h', true);
502
549
 
503
550
  prepend.set('__Porffor_readFile',
504
- `void __Porffor_readFile(u32 pathPtr, u32 outPtr) {
551
+ `i32 __Porffor_readFile(u32 pathPtr, u32 outPtr) {
505
552
  char* path = _memory + pathPtr + 4;
506
553
  FILE* fp = fopen(path, "r");
507
554
  if (fp == NULL) {
508
- printf("failed to open file: %s\\n", path);
509
- exit(1);
555
+ return -1;
510
556
  }
511
557
 
512
558
  u32 read = 0;
@@ -518,11 +564,14 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
518
564
 
519
565
  fclose(fp);
520
566
 
521
- memcpy(_memory + outPtr, &read, sizeof(read));
567
+ *((i32*)(_memory + outPtr)) = (i32)read;
568
+ return read;
522
569
  }`);
523
- line(`__Porffor_readFile((u32)(${vals.at(-2)}), (u32)(${vals.pop()}))`);
524
- vals.pop();
570
+ const outPtr = vals.pop();
571
+ const pathPtr = vals.pop();
572
+ vals.push(`(f64)__Porffor_readFile((u32)(${pathPtr}), (u32)(${outPtr}))`);
525
573
  break;
574
+ }
526
575
 
527
576
  default:
528
577
  log.warning('2c', `unimplemented import: ${importFunc.name}`);
@@ -539,9 +588,10 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
539
588
  if (func.internal) {
540
589
  vals.push(`${sanitize(func.name)}(${args.join(', ')})`);
541
590
  } else {
542
- line(`const struct ReturnValue _${retTmpId++} = ${sanitize(func.name)}(${args.join(', ')})`);
543
- vals.push(`_.value`);
544
- vals.push(`_.type`);
591
+ const id = retTmpId++;
592
+ line(`const struct ReturnValue _${id} = ${sanitize(func.name)}(${args.join(', ')})`);
593
+ vals.push(`_${id}.value`);
594
+ vals.push(`_${id}.type`);
545
595
  }
546
596
  } else line(`${sanitize(func.name)}(${args.join(', ')})`);
547
597
 
@@ -571,7 +621,7 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
571
621
 
572
622
  let cond = removeBrackets(vals.pop());
573
623
  if (!lastCond) {
574
- if (cond.startsWith(`(${CValtype.i32})`)) cond = `(${cond.slice(`(${CValtype.i32})`.length)}) != 0e+0`;
624
+ if (cond.startsWith(`(i32)`)) cond = `${cond.slice(5)} != 0e+0`;
575
625
  else cond = `(${cond}) != 0`;
576
626
  }
577
627
 
@@ -601,8 +651,7 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
601
651
  const name = invOpcodes[i[0]];
602
652
  const func = CMemFuncs[i[0]];
603
653
  if (!prepend.has(name)) {
604
- prepend.set(name, `${func.returns || 'void'} ${name}(${CValtype.i32} align, ${CValtype.i32} offset, ${func.args.map((x, i) => `${func.argTypes[i]} ${x}`).join(', ')}) {\n ${func.c.replaceAll('\n', '\n ')}\n}\n`);
605
- // generate func c and prepend
654
+ prepend.set(name, `${func.returns || 'void'} ${name}(i32 align, i32 offset, ${func.args.map((x, i) => `${func.argTypes[i]} ${x}`).join(', ')}) {\n ${func.c.replaceAll('\n', '\n ')}\n}\n`);
606
655
  }
607
656
 
608
657
  const immediates = [ i[1], read_unsignedLEB128(i.slice(2)) ];
@@ -637,7 +686,6 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
637
686
  depth = 0;
638
687
 
639
688
  const makeIncludes = includes => [...includes.keys()].map(x => `#include <${x}>\n`).join('');
640
-
641
689
  out = platformSpecific(makeIncludes(winIncludes), makeIncludes(unixIncludes), false) + '\n' + makeIncludes(includes) + '\n' + alwaysPreface + [...prepend.values()].join('\n') + '\n\n' + out;
642
690
 
643
691
  return out.trim();
@@ -0,0 +1,128 @@
1
+ import { Opcodes, PageSize, Valtype } from './wasmSpec.js';
2
+ import { number } from './embedding.js';
3
+ import Prefs from './prefs.js';
4
+
5
+ // we currently have 3 allocators:
6
+ // - static (default): a static/compile-time allocator. fast (no grow/run-time alloc needed) but can break some code
7
+ // - grow: perform a memory.grow every allocation. simple but maybe slow?
8
+ // - chunk: perform large memory.grow's in chunks when needed. needs investigation
9
+
10
+ export default name => {
11
+ switch (name) {
12
+ case 'static': return new StaticAllocator();
13
+ case 'grow': return new GrowAllocator();
14
+ case 'chunk': return new ChunkAllocator();
15
+ default: throw new Error(`unknown allocator: ${name}`);
16
+ }
17
+ };
18
+
19
+ export class StaticAllocator {
20
+ constructor() {
21
+ }
22
+
23
+ allocType(itemType) {
24
+ switch (itemType) {
25
+ case 'i8': return 'bytestring';
26
+ case 'i16': return 'string';
27
+
28
+ default: return 'array';
29
+ }
30
+ }
31
+
32
+ ptr(ind) {
33
+ if (ind === 0) return 4;
34
+ return ind * PageSize;
35
+ }
36
+
37
+ alloc({ scope, pages }, name, { itemType }) {
38
+ const reason = `${this.allocType(itemType)}: ${Prefs.scopedPageNames ? (scope.name + '/') : ''}${name}`;
39
+
40
+ if (pages.has(reason)) return number(this.ptr(pages.get(reason).ind), Valtype.i32);
41
+
42
+ if (reason.startsWith('array:')) pages.hasArray = true;
43
+ if (reason.startsWith('string:')) pages.hasString = true;
44
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
45
+ if (reason.includes('string:')) pages.hasAnyString = true;
46
+
47
+ let ind = pages.size;
48
+ pages.set(reason, { ind, type: itemType });
49
+
50
+ scope.pages ??= new Map();
51
+ scope.pages.set(reason, { ind, type: itemType });
52
+
53
+ return number(this.ptr(ind), Valtype.i32);
54
+ }
55
+ }
56
+
57
+ export class GrowAllocator {
58
+ constructor() {
59
+ Prefs.rmUnusedTypes = false;
60
+ }
61
+
62
+ alloc() {
63
+ return [
64
+ // grow by 1 page
65
+ [ Opcodes.i32_const, 1 ],
66
+ [ Opcodes.memory_grow, 0 ], // returns old page count
67
+
68
+ // get ptr (page count * page size)
69
+ number(65536, Valtype.i32)[0],
70
+ [ Opcodes.i32_mul ]
71
+ ];
72
+ }
73
+ }
74
+
75
+ export class ChunkAllocator {
76
+ constructor(chunkSize) {
77
+ Prefs.rmUnusedTypes = false;
78
+
79
+ // 64KiB * chunk size each growth
80
+ // 16: 1MiB chunks
81
+ this.chunkSize = chunkSize ?? Prefs.chunkAllocatorSize ?? 16;
82
+ }
83
+
84
+ alloc({ asmFunc, funcIndex }) {
85
+ const func = funcIndex['#chunkallocator_alloc'] ?? asmFunc('#chunkallocator_alloc', {
86
+ wasm: [
87
+ [ Opcodes.global_get, 0 ],
88
+ [ Opcodes.global_get, 1 ],
89
+ [ Opcodes.i32_ge_s ],
90
+ [ Opcodes.if, Valtype.i32 ], // ptr >= next
91
+ // grow by chunk size pages
92
+ [ Opcodes.i32_const, this.chunkSize ],
93
+ [ Opcodes.memory_grow, 0 ],
94
+
95
+ // ptr = prev memory size * PageSize
96
+ number(65536, Valtype.i32)[0],
97
+ [ Opcodes.i32_mul ],
98
+ [ Opcodes.global_set, 0 ],
99
+
100
+ // next = ptr + ((chunkSize - 1) * PageSize)
101
+ [ Opcodes.global_get, 0 ],
102
+ number(65536 * (this.chunkSize - 1), Valtype.i32)[0],
103
+ [ Opcodes.i32_add ],
104
+ [ Opcodes.global_set, 1 ],
105
+
106
+ // return ptr
107
+ [ Opcodes.global_get, 0 ],
108
+ [ Opcodes.else ],
109
+ // return ptr = ptr + PageSize
110
+ [ Opcodes.global_get, 0 ],
111
+ number(65536, Valtype.i32)[0],
112
+ [ Opcodes.i32_add ],
113
+ [ Opcodes.global_set, 0 ],
114
+ [ Opcodes.global_get, 0 ],
115
+ [ Opcodes.end ],
116
+ ],
117
+ params: [],
118
+ locals: [],
119
+ globals: [ Valtype.i32, Valtype.i32 ],
120
+ globalNames: ['#chunkallocator_ptr', '#chunkallocator_next'],
121
+ returns: [ Valtype.i32 ],
122
+ }).index;
123
+
124
+ return [
125
+ [ Opcodes.call, func ]
126
+ ];
127
+ }
128
+ }
@@ -21,7 +21,7 @@ const chHint = (topTier, baselineTier, strategy) => {
21
21
  return (strategy | (baselineTier << 2) | (topTier << 4));
22
22
  };
23
23
 
24
- export default (funcs, globals, tags, pages, data, flags) => {
24
+ export default (funcs, globals, tags, pages, data, flags, noTreeshake = false) => {
25
25
  const types = [], typeCache = {};
26
26
 
27
27
  const optLevel = parseInt(process.argv.find(x => x.startsWith('-O'))?.[2] ?? 1);
@@ -44,7 +44,7 @@ export default (funcs, globals, tags, pages, data, flags) => {
44
44
 
45
45
  let importFuncs = [];
46
46
 
47
- if (optLevel < 1 || !Prefs.treeshakeWasmImports) {
47
+ if (optLevel < 1 || !Prefs.treeshakeWasmImports || noTreeshake) {
48
48
  importFuncs = importedFuncs;
49
49
  } else {
50
50
  let imports = new Map();
@@ -94,7 +94,7 @@ export default (funcs, globals, tags, pages, data, flags) => {
94
94
 
95
95
  const importSection = importFuncs.length === 0 ? [] : createSection(
96
96
  Section.import,
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)) ]))
97
+ encodeVector(importFuncs.map(x => [ 0, ...encodeString(x.import), ExportDesc.func, getType(typeof x.params === 'object' ? x.params : new Array(x.params).fill(valtypeBinary), new Array(x.returns).fill(valtypeBinary)) ]))
98
98
  );
99
99
 
100
100
  const funcSection = createSection(
@@ -116,7 +116,7 @@ export default (funcs, globals, tags, pages, data, flags) => {
116
116
  ] ])
117
117
  );
118
118
 
119
- if (pages.has('func argc lut')) {
119
+ if (pages.has('func argc lut') && !data.addedFuncArgcLut) {
120
120
  // generate func argc lut data
121
121
  const bytes = [];
122
122
  for (let i = 0; i < funcs.length; i++) {
@@ -128,6 +128,7 @@ export default (funcs, globals, tags, pages, data, flags) => {
128
128
  offset: pages.get('func argc lut').ind * pageSize,
129
129
  bytes
130
130
  });
131
+ data.addedFuncArgcLut = true;
131
132
  }
132
133
 
133
134
  // const t0 = performance.now();
@@ -239,7 +240,13 @@ export default (funcs, globals, tags, pages, data, flags) => {
239
240
 
240
241
  const dataSection = data.length === 0 ? [] : createSection(
241
242
  Section.data,
242
- encodeVector(data.map(x => [ 0x00, Opcodes.i32_const, ...signedLEB128(x.offset), Opcodes.end, ...encodeVector(x.bytes) ]))
243
+ encodeVector(data.map(x => {
244
+ // type: active
245
+ if (x.offset != null) return [ 0x00, Opcodes.i32_const, ...signedLEB128(x.offset), Opcodes.end, ...encodeVector(x.bytes) ];
246
+
247
+ // type: passive
248
+ return [ 0x01, ...encodeVector(x.bytes) ];
249
+ }))
243
250
  );
244
251
 
245
252
  const dataCountSection = data.length === 0 ? [] : createSection(