porffor 0.16.0-fe07da0f4 → 0.17.0-05070e1f0

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})`;
@@ -130,7 +182,15 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
130
182
  }, {});
131
183
  const invGlobals = inv(globals, x => x.idx);
132
184
 
133
- const sanitize = str => str.replace(/[^0-9a-zA-Z_]/g, _ => String.fromCharCode(97 + _.charCodeAt(0) % 32));
185
+ const codeToSanitizedStr = code => {
186
+ let out = '';
187
+ while (code > 0) {
188
+ out += String.fromCharCode(97 + code % 26);
189
+ code -= 26;
190
+ }
191
+ return out;
192
+ };
193
+ const sanitize = str => str.replace(/[^0-9a-zA-Z_]/g, _ => codeToSanitizedStr(_.charCodeAt(0)));
134
194
 
135
195
  for (const x in invGlobals) {
136
196
  invGlobals[x] = sanitize(invGlobals[x]);
@@ -152,11 +212,16 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
152
212
 
153
213
  if (pages.size > 0) {
154
214
  prepend.set('_memory', `char _memory[${pages.size * pageSize}];\n`);
155
- includes.set('string.h', true);
215
+ if (Prefs['2cMemcpy']) includes.set('string.h', true);
156
216
  }
157
217
 
158
218
  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'));
219
+ if (Prefs['2cMemcpy']) {
220
+ prependMain.set('_data', data.map(x => `memcpy(_memory + ${x.offset}, (unsigned char[]){${x.bytes.join(',')}}, ${x.bytes.length});`).join('\n '));
221
+ includes.set('string.h', true);
222
+ } else {
223
+ prependMain.set('_data', data.map(x => x.bytes.reduce((acc, y, i) => acc + `_memory[${x.offset + i}]=(u8)${y};`, '')).join('\n '));
224
+ }
160
225
  }
161
226
 
162
227
  if (importFuncs.find(x => x.name === '__Porffor_readArgv')) {
@@ -168,10 +233,10 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
168
233
 
169
234
  let depth = 1;
170
235
  let brDepth = 0;
171
- const line = (str, semi = true) => out += `${' '.repeat(depth * 2 + brDepth * 2)}${str}${semi ? ';' : ''}\n`;
236
+ const line = (str, semi = true) => out += `${' '.repeat((depth + brDepth) * 2)}${str}${semi ? ';' : ''}\n`;
172
237
  const lines = lines => {
173
238
  for (const x of lines) {
174
- out += `${' '.repeat(depth * 2)}${x}\n`;
239
+ out += `${' '.repeat((depth + brDepth) * 2)}${x}\n`;
175
240
  }
176
241
  };
177
242
 
@@ -216,13 +281,14 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
216
281
  }
217
282
 
218
283
  const returns = f.returns.length > 0;
284
+ const typedReturns = f.returnType == null;
219
285
 
220
286
  const shouldInline = false; // f.internal;
221
287
  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`;
288
+ else out += `${!typedReturns ? (returns ? CValtype[f.returns[0]] : 'void') : 'struct ReturnValue'} ${shouldInline ? 'inline ' : ''}${sanitize(f.name)}(${f.params.map((x, i) => `${CValtype[x]} ${invLocals[i]}`).join(', ')}) {\n`;
223
289
 
224
290
  if (f.name === 'main') {
225
- out += [...prependMain.values()].join('\n');
291
+ out += ' ' + [...prependMain.values()].join('\n ');
226
292
  if (prependMain.size > 0) out += '\n\n';
227
293
  }
228
294
 
@@ -283,12 +349,12 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
283
349
  switch (i[1]) {
284
350
  // i32_trunc_sat_f64_s
285
351
  case 0x02:
286
- vals.push(`(${CValtype.i32})(${vals.pop()})`);
352
+ vals.push(`(i32)(${vals.pop()})`);
287
353
  break;
288
354
 
289
355
  // i32_trunc_sat_f64_u
290
356
  case 0x03:
291
- vals.push(`(${CValtype.u32})(${vals.pop()})`);
357
+ vals.push(`(u32)(${vals.pop()})`);
292
358
  break;
293
359
  }
294
360
 
@@ -337,7 +403,7 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
337
403
 
338
404
  case Opcodes.f64_trunc:
339
405
  // vals.push(`trunc(${vals.pop()})`);
340
- vals.push(`(${CValtype.i32})(${removeBrackets(vals.pop())})`); // this is ~10x faster with clang??
406
+ vals.push(`(i32)(${removeBrackets(vals.pop())})`); // this is ~10x faster with clang??
341
407
  break;
342
408
 
343
409
  case Opcodes.f64_convert_i32_u:
@@ -345,7 +411,7 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
345
411
  case Opcodes.f64_convert_i64_u:
346
412
  case Opcodes.f64_convert_i64_s:
347
413
  // int to f64
348
- vals.push(`(${CValtype.f64})(${removeBrackets(vals.pop())})`);
414
+ vals.push(`(f64)(${removeBrackets(vals.pop())})`);
349
415
  break;
350
416
 
351
417
  case Opcodes.i32_eqz:
@@ -353,7 +419,7 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
353
419
  vals.push(`!(${removeBrackets(vals.pop())})`);
354
420
  } else {
355
421
  let cond = '(' + removeBrackets(vals.pop());
356
- if (cond.startsWith(`(${CValtype.i32})`)) cond = `${cond.slice(`(${CValtype.i32})`.length)}) == 0e+0`;
422
+ if (cond.startsWith(`(i32)`)) cond = `${cond.slice(5)} == 0e+0`;
357
423
  else cond += ') == 0';
358
424
  vals.push(cond);
359
425
  }
@@ -361,14 +427,20 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
361
427
  continue;
362
428
 
363
429
  case Opcodes.return:
364
- // line(`return${returns ? ` ${removeBrackets(vals.pop())}` : ''}`);
365
- line(`return${returns ? ` (struct ReturnValue){ ${removeBrackets(vals.pop())}, ${removeBrackets(vals.pop())} }` : ''}`);
430
+ if (!typedReturns) {
431
+ line(`return${returns ? ` ${removeBrackets(vals.pop())}` : ''}`);
432
+ break;
433
+ }
434
+
435
+ const b = returns ? vals.pop() : -1;
436
+ const a = returns ? vals.pop() : -1;
437
+ line(`return${returns ? ` (struct ReturnValue){ ${removeBrackets(a)}, ${removeBrackets(b)} }` : ''}`);
366
438
  break;
367
439
 
368
440
  case Opcodes.if: {
369
441
  let cond = removeBrackets(vals.pop());
370
442
  if (!lastCond) {
371
- if (cond.startsWith(`(${CValtype.i32})`)) cond = `(${cond.slice(`(${CValtype.i32})`.length)}) != 0e+0`;
443
+ if (cond.startsWith(`(i32)`)) cond = `${cond.slice(5)} != 0e+0`;
372
444
  else cond = `(${cond}) != 0`;
373
445
  }
374
446
 
@@ -434,28 +506,16 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
434
506
  const importFunc = importFuncs[i[1]];
435
507
  switch (importFunc.name) {
436
508
  case 'print':
437
- // line(`printf("%f\\n", ${vals.pop()})`);
438
509
  line(`printf("${valtype === 'f64' ? '%g' : '%i'}\\n", ${vals.pop()})`);
439
510
  includes.set('stdio.h', true);
440
511
  break;
441
512
  case 'printChar':
442
- line(`printf("%c", (int)(${vals.pop()}))`);
513
+ line(`putchar((int)(${vals.pop()}))`);
443
514
  includes.set('stdio.h', true);
444
515
  break;
445
516
 
446
517
  case 'time':
447
518
  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
519
  platformSpecific(
460
520
  `LARGE_INTEGER _time_freq, _time_t;
461
521
  QueryPerformanceFrequency(&_time_freq);
@@ -470,14 +530,11 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
470
530
  winIncludes.set('windows.h', true);
471
531
  break;
472
532
 
473
- case '__Porffor_readArgv':
474
- includes.set('stdlib.h', true);
475
-
533
+ case '__Porffor_readArgv': {
476
534
  prepend.set('__Porffor_readArgv',
477
- `void __Porffor_readArgv(u32 index, u32 outPtr) {
535
+ `i32 __Porffor_readArgv(u32 index, u32 outPtr) {
478
536
  if (index >= _argc) {
479
- printf("expected %d arguments\\n", index);
480
- exit(1);
537
+ return -1;
481
538
  }
482
539
 
483
540
  char* arg = _argv[index];
@@ -489,24 +546,30 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
489
546
  out[read++] = ch;
490
547
  }
491
548
 
492
- memcpy(_memory + outPtr, &read, sizeof(read));
549
+ *((i32*)(_memory + outPtr)) = (i32)read;
550
+ return read;
493
551
  }`);
494
552
 
495
- line(`__Porffor_readArgv((u32)(${vals.at(-2)}), (u32)(${vals.pop()}))`);
496
- vals.pop();
553
+ const outPtr = vals.pop();
554
+ const index = vals.pop();
555
+ vals.push(`(f64)__Porffor_readArgv((u32)(${index}), (u32)(${outPtr}))`);
497
556
  break;
557
+ }
498
558
 
499
- case '__Porffor_readFile':
559
+ case '__Porffor_readFile': {
500
560
  includes.set('stdio.h', true);
501
- includes.set('stdlib.h', true);
502
561
 
503
562
  prepend.set('__Porffor_readFile',
504
- `void __Porffor_readFile(u32 pathPtr, u32 outPtr) {
505
- char* path = _memory + pathPtr + 4;
506
- FILE* fp = fopen(path, "r");
507
- if (fp == NULL) {
508
- printf("failed to open file: %s\\n", path);
509
- exit(1);
563
+ `i32 __Porffor_readFile(u32 pathPtr, u32 outPtr) {
564
+ FILE* fp;
565
+ if (pathPtr == 0) {
566
+ fp = stdin;
567
+ } else {
568
+ char* path = _memory + pathPtr + 4;
569
+ fp = fopen(path, "r");
570
+ if (fp == NULL) {
571
+ return -1;
572
+ }
510
573
  }
511
574
 
512
575
  u32 read = 0;
@@ -518,11 +581,14 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
518
581
 
519
582
  fclose(fp);
520
583
 
521
- memcpy(_memory + outPtr, &read, sizeof(read));
584
+ *((i32*)(_memory + outPtr)) = (i32)read;
585
+ return read;
522
586
  }`);
523
- line(`__Porffor_readFile((u32)(${vals.at(-2)}), (u32)(${vals.pop()}))`);
524
- vals.pop();
587
+ const outPtr = vals.pop();
588
+ const pathPtr = vals.pop();
589
+ vals.push(`(f64)__Porffor_readFile((u32)(${pathPtr}), (u32)(${outPtr}))`);
525
590
  break;
591
+ }
526
592
 
527
593
  default:
528
594
  log.warning('2c', `unimplemented import: ${importFunc.name}`);
@@ -536,12 +602,13 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
536
602
  for (let j = 0; j < func.params.length; j++) args.unshift(removeBrackets(vals.pop()));
537
603
 
538
604
  if (func.returns.length > 0) {
539
- if (func.internal) {
605
+ if (func.returnType != null) {
540
606
  vals.push(`${sanitize(func.name)}(${args.join(', ')})`);
541
607
  } else {
542
- line(`const struct ReturnValue _${retTmpId++} = ${sanitize(func.name)}(${args.join(', ')})`);
543
- vals.push(`_.value`);
544
- vals.push(`_.type`);
608
+ const id = retTmpId++;
609
+ line(`const struct ReturnValue _${id} = ${sanitize(func.name)}(${args.join(', ')})`);
610
+ vals.push(`_${id}.value`);
611
+ vals.push(`_${id}.type`);
545
612
  }
546
613
  } else line(`${sanitize(func.name)}(${args.join(', ')})`);
547
614
 
@@ -571,7 +638,7 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
571
638
 
572
639
  let cond = removeBrackets(vals.pop());
573
640
  if (!lastCond) {
574
- if (cond.startsWith(`(${CValtype.i32})`)) cond = `(${cond.slice(`(${CValtype.i32})`.length)}) != 0e+0`;
641
+ if (cond.startsWith(`(i32)`)) cond = `${cond.slice(5)} != 0e+0`;
575
642
  else cond = `(${cond}) != 0`;
576
643
  }
577
644
 
@@ -601,8 +668,7 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
601
668
  const name = invOpcodes[i[0]];
602
669
  const func = CMemFuncs[i[0]];
603
670
  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
671
+ 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
672
  }
607
673
 
608
674
  const immediates = [ i[1], read_unsignedLEB128(i.slice(2)) ];
@@ -637,7 +703,6 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
637
703
  depth = 0;
638
704
 
639
705
  const makeIncludes = includes => [...includes.keys()].map(x => `#include <${x}>\n`).join('');
640
-
641
706
  out = platformSpecific(makeIncludes(winIncludes), makeIncludes(unixIncludes), false) + '\n' + makeIncludes(includes) + '\n' + alwaysPreface + [...prepend.values()].join('\n') + '\n\n' + out;
642
707
 
643
708
  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
+ }