porffor 0.2.0-5e33105 → 0.2.0-623cdf0

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/README.md CHANGED
@@ -121,7 +121,7 @@ No particular order and no guarentees, just what could happen soon™
121
121
  - *Basic* Wasm engine (interpreter) in JS
122
122
  - More math operators (`**`, etc)
123
123
  - `do { ... } while (...)`
124
- - Rewrite `console.log` to work with strings/arrays
124
+ - Typed export inputs (array)
125
125
  - Exceptions
126
126
  - Rewrite to use actual strings (optional?)
127
127
  - `try { } finally { }`
@@ -130,7 +130,7 @@ No particular order and no guarentees, just what could happen soon™
130
130
  - Rewrite local indexes per func for smallest local header and remove unused idxs
131
131
  - Smarter inline selection (snapshots?)
132
132
  - Remove const ifs (`if (true)`, etc)
133
- - Experiment with byte strings?
133
+ - Memory alignment
134
134
  - Runtime
135
135
  - WASI target
136
136
  - Run precompiled Wasm file if given
@@ -143,11 +143,11 @@ No particular order and no guarentees, just what could happen soon™
143
143
  - [`do` expressions](https://github.com/tc39/proposal-do-expressions)
144
144
  - [String Trim Characters](https://github.com/Kingwl/proposal-string-trim-characters)
145
145
  - Posts
146
- - Type annotations for performance
147
146
  - Inlining investigation
147
+ - Self hosted testing?
148
148
 
149
149
  ## Performance
150
- *For the things it supports most of the time*, Porffor is blazingly fast compared to most interpreters, and common engines running without JIT. For those with JIT, it is not that much slower like a traditional interpreter would be; mostly the same or a bit faster/slower depending on what.
150
+ *For the things it supports most of the time*, Porffor is *blazingly fast* compared to most interpreters, and common engines running without JIT. For those with JIT, it is usually slower by default, but can catch up with compiler arguments and typed input.
151
151
 
152
152
  ![Screenshot of comparison chart](https://github.com/CanadaHonk/porffor/assets/19228318/76c75264-cc68-4be1-8891-c06dc389d97a)
153
153
 
@@ -209,10 +209,13 @@ Porffor can run Test262 via some hacks/transforms which remove unsupported featu
209
209
  - `test262`: test262 runner and utils
210
210
 
211
211
  ## Usecases
212
- Basically none (other than giving people headaches). Potential ideas to come?
212
+ Basically none right now (other than giving people headaches). Potential ideas:
213
+ - Safety. As Porffor is written in JS, a memory-safe language\*, and compiles JS to Wasm, a fully sandboxed environment\*, it is quite safe. (\* These rely on the underlying implementations being secure. You could also run Wasm, or even Porffor itself, with an interpreter instead of a JIT for bonus security points too.)
214
+ - Compiling JS to native binaries. This is still very early, [`2c`](#2c) is not that good yet :(
215
+ - More in future probably?
213
216
 
214
217
  ## Usage
215
- Basically nothing will work :). See files in `test` for examples.
218
+ Basically nothing will work :). See files in `test` and `bench` for examples.
216
219
 
217
220
  1. Clone repo
218
221
  2. `npm install`
package/compiler/2c.js CHANGED
@@ -1,24 +1,107 @@
1
- import { read_ieee754_binary64, read_signedLEB128 } from './encoding.js';
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
4
  import { log } from "./log.js";
5
5
 
6
6
  const CValtype = {
7
- i8: 'char',
8
- i16: 'unsigned short', // presume all i16 stuff is unsigned
9
- i32: 'long',
10
- i32_u: 'unsigned long',
11
- i64: 'long long',
12
- i64_u: 'unsigned long long',
7
+ i8: 'i8',
8
+ i16: 'i16',
9
+ i32: 'i32',
10
+ u32: 'u32',
11
+ i64: 'i64',
12
+ u64: 'u64',
13
13
 
14
- f32: 'float',
15
- f64: 'double',
14
+ f32: 'f32',
15
+ f64: 'f64',
16
16
 
17
17
  undefined: 'void'
18
18
  };
19
19
 
20
+ const alwaysPreface = `typedef unsigned char i8;
21
+ typedef unsigned short i16;
22
+ typedef long i32;
23
+ typedef unsigned long u32;
24
+ typedef long long i64;
25
+ typedef unsigned long long u64;
26
+ typedef float f32;
27
+ typedef double f64;
28
+
29
+ f64 NAN = 0e+0/0e+0;
30
+
31
+ struct ReturnValue {
32
+ ${CValtype.f64} value;
33
+ ${CValtype.i32} type;
34
+ };
35
+ \n`;
36
+
37
+ // todo: is memcpy/etc safe with host endianness?
38
+
39
+ // all:
40
+ // immediates: ['align', 'offset']
41
+ const CMemFuncs = {
42
+ [Opcodes.i32_store]: {
43
+ c: `memcpy(_memory + offset + pointer, &value, sizeof(value));`,
44
+ args: ['pointer', 'value'],
45
+ argTypes: [CValtype.i32, CValtype.i32],
46
+ returns: false
47
+ },
48
+ [Opcodes.i32_store16]: {
49
+ c: `memcpy(_memory + offset + pointer, &value, sizeof(value));`,
50
+ args: ['pointer', 'value'],
51
+ argTypes: [CValtype.i32, CValtype.i16],
52
+ returns: false
53
+ },
54
+ [Opcodes.i32_store8]: {
55
+ c: `memcpy(_memory + offset + pointer, &value, sizeof(value));`,
56
+ args: ['pointer', 'value'],
57
+ argTypes: [CValtype.i32, CValtype.i8],
58
+ returns: false
59
+ },
60
+
61
+ [Opcodes.i32_load]: {
62
+ c: `${CValtype.i32} out;
63
+ memcpy(&out, _memory + offset + pointer, sizeof(out));
64
+ return out;`,
65
+ args: ['pointer'],
66
+ argTypes: [CValtype.i32],
67
+ returns: CValtype.i32
68
+ },
69
+ [Opcodes.i32_load16_u]: {
70
+ c: `${CValtype.i16} out;
71
+ memcpy(&out, _memory + offset + pointer, sizeof(out));
72
+ return out;`,
73
+ args: ['pointer'],
74
+ argTypes: [CValtype.i32],
75
+ returns: CValtype.i32
76
+ },
77
+ [Opcodes.i32_load8_u]: {
78
+ c: `${CValtype.i8} out;
79
+ memcpy(&out, _memory + offset + pointer, sizeof(out));
80
+ return out;`,
81
+ args: ['pointer'],
82
+ argTypes: [CValtype.i32],
83
+ returns: CValtype.i32
84
+ },
85
+
86
+ [Opcodes.f64_store]: {
87
+ c: `memcpy(_memory + offset + pointer, &value, sizeof(value));`,
88
+ args: ['pointer', 'value'],
89
+ argTypes: [CValtype.i32, CValtype.f64],
90
+ returns: false
91
+ },
92
+ [Opcodes.f64_load]: {
93
+ c: `${CValtype.f64} out;
94
+ memcpy(&out, _memory + offset + pointer, sizeof(out));
95
+ return out;`,
96
+ args: ['pointer'],
97
+ argTypes: [CValtype.i32],
98
+ returns: CValtype.f64
99
+ },
100
+ };
101
+
20
102
  const inv = (obj, keyMap = x => x) => Object.keys(obj).reduce((acc, x) => { acc[keyMap(obj[x])] = x; return acc; }, {});
21
103
  const invOpcodes = inv(Opcodes);
104
+ const invValtype = inv(Valtype);
22
105
 
23
106
  for (const x in CValtype) {
24
107
  if (Valtype[x]) CValtype[Valtype[x]] = CValtype[x];
@@ -36,11 +119,18 @@ const todo = msg => {
36
119
  };
37
120
 
38
121
  const removeBrackets = str => {
39
- if (str.startsWith('(long)(unsigned long)')) return '(long)(unsigned long)(' + removeBrackets(str.slice(22, -1)) + ')';
122
+ // return str;
123
+ // if (str.startsWith(`(${CValtype.i32})(${CValtype.u32})`)) return `(${CValtype.i32})(${CValtype.u32})(` + removeBrackets(str.slice(22, -1)) + ')';
124
+
125
+ for (const x in CValtype) {
126
+ const p = `(${x})`;
127
+ if (str.startsWith(p)) return p + removeBrackets(str.slice(p.length));
128
+ }
129
+
40
130
  return str.startsWith('(') && str.endsWith(')') ? str.slice(1, -1) : str;
41
131
  };
42
132
 
43
- export default ({ funcs, globals, tags, exceptions, pages }) => {
133
+ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
44
134
  const invOperatorOpcode = Object.values(operatorOpcode).reduce((acc, x) => {
45
135
  for (const k in x) {
46
136
  acc[x[k]] = k;
@@ -56,12 +146,10 @@ export default ({ funcs, globals, tags, exceptions, pages }) => {
56
146
  }
57
147
 
58
148
  const includes = new Map(), unixIncludes = new Map(), winIncludes = new Map();
149
+ const prepend = new Map(), prependMain = new Map();
59
150
 
60
- // TODO: make type i16
61
- let out = `struct ReturnValue {
62
- ${CValtype.f64} value;
63
- ${CValtype.i32} type;
64
- };\n\n`;
151
+ // presume all <i32 work is unsigned
152
+ let out = ``;
65
153
 
66
154
  for (const x in globals) {
67
155
  const g = globals[x];
@@ -70,6 +158,15 @@ export default ({ funcs, globals, tags, exceptions, pages }) => {
70
158
  out += ';\n';
71
159
  }
72
160
 
161
+ if (pages.size > 0) {
162
+ prepend.set('_memory', `char _memory[${pages.size * pageSize}];\n`);
163
+ includes.set('string.h', true);
164
+ }
165
+
166
+ if (data.length > 0) {
167
+ prependMain.set('_data', data.map(x => `memcpy(_memory + ${x.offset}, (char[]){${x.bytes.join(',')}}, ${x.bytes.length});`).join('\n'));
168
+ }
169
+
73
170
  // for (const [ x, p ] of pages) {
74
171
  // out += `${CValtype[p.type]} ${x.replace(': ', '_').replace(/[^0-9a-zA-Z_]/g, '')}[100]`;
75
172
  // out += ';\n';
@@ -78,7 +175,8 @@ export default ({ funcs, globals, tags, exceptions, pages }) => {
78
175
  if (out) out += '\n';
79
176
 
80
177
  let depth = 1;
81
- const line = (str, semi = true) => out += `${' '.repeat(depth * 2)}${str}${semi ? ';' : ''}\n`;
178
+ let brDepth = 0;
179
+ const line = (str, semi = true) => out += `${' '.repeat(depth * 2 + brDepth * 2)}${str}${semi ? ';' : ''}\n`;
82
180
  const lines = lines => {
83
181
  for (const x of lines) {
84
182
  out += `${' '.repeat(depth * 2)}${x}\n`;
@@ -111,6 +209,8 @@ export default ({ funcs, globals, tags, exceptions, pages }) => {
111
209
  return tmp;
112
210
  };
113
211
 
212
+ let brId = 0;
213
+
114
214
  for (const f of funcs) {
115
215
  depth = 1;
116
216
 
@@ -124,7 +224,12 @@ export default ({ funcs, globals, tags, exceptions, pages }) => {
124
224
  const returns = f.returns.length > 0;
125
225
 
126
226
  const shouldInline = f.internal;
127
- out += `${f.name === 'main' ? 'int' : (f.internal ? (returns ? 'double' : 'void') : 'struct ReturnValue')} ${shouldInline ? 'inline ' : ''}${sanitize(f.name)}(${f.params.map((x, i) => `${CValtype[x]} ${invLocals[i]}`).join(', ')}) {\n`;
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`;
228
+
229
+ if (f.name === 'main') {
230
+ out += [...prependMain.values()].join('\n');
231
+ if (prependMain.size > 0) out += '\n\n';
232
+ }
128
233
 
129
234
  const localKeys = Object.keys(f.locals).sort((a, b) => f.locals[a].idx - f.locals[b].idx).slice(f.params.length).sort((a, b) => f.locals[a].idx - f.locals[b].idx);
130
235
  for (const x of localKeys) {
@@ -134,11 +239,58 @@ export default ({ funcs, globals, tags, exceptions, pages }) => {
134
239
 
135
240
  if (localKeys.length !== 0) out += '\n';
136
241
 
242
+ const rets = [];
243
+ const runOnEnd = [];
244
+
137
245
  let vals = [];
138
- const endNeedsCurly = [], ignoreEnd = [];
139
- let beginLoop = false, lastCond = false, ifTernary = false;
246
+ const endNeedsCurly = [];
247
+ const brs = [];
248
+ let lastCond = false;
249
+
250
+ // let brDepth = 0;
251
+
252
+ const blockStart = (i, loop) => {
253
+ // reset "stack"
254
+ // vals = [];
255
+
256
+ rets.push(i[1]);
257
+
258
+ const br = brId++;
259
+ brs.push(br);
260
+ if (loop) {
261
+ line(`j${br}:;`, false);
262
+ runOnEnd.push(null);
263
+ } else {
264
+ runOnEnd.push(() => line(`j${br}:;`, false));
265
+ }
266
+
267
+ if (i[1] !== Blocktype.void) line(`${CValtype[i[1]]} _r${br}`);
268
+
269
+ brDepth++;
270
+ };
271
+
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
+
140
291
  for (let _ = 0; _ < f.wasm.length; _++) {
141
292
  const i = f.wasm[_];
293
+ if (!i || !i[0]) continue;
142
294
 
143
295
  if (invOperatorOpcode[i[0]]) {
144
296
  const b = vals.pop();
@@ -160,12 +312,12 @@ export default ({ funcs, globals, tags, exceptions, pages }) => {
160
312
  switch (i[1]) {
161
313
  // i32_trunc_sat_f64_s
162
314
  case 0x02:
163
- vals.push(`(${CValtype.i32})${vals.pop()}`);
315
+ vals.push(`(${CValtype.i32})(${vals.pop()})`);
164
316
  break;
165
317
 
166
318
  // i32_trunc_sat_f64_u
167
319
  case 0x03:
168
- vals.push(`(${CValtype.i32})(${CValtype.i32_u})${vals.pop()}`);
320
+ vals.push(`(${CValtype.u32})(${vals.pop()})`);
169
321
  break;
170
322
  }
171
323
 
@@ -176,12 +328,19 @@ export default ({ funcs, globals, tags, exceptions, pages }) => {
176
328
  switch (i[0]) {
177
329
  case Opcodes.i32_const:
178
330
  case Opcodes.i64_const:
179
- vals.push(read_signedLEB128(i.slice(1)).toString());
331
+ // vals.push(read_signedLEB128(i.slice(1)).toString());
332
+ vals.push(new String(read_signedLEB128(i.slice(1)).toString()));
333
+ vals.at(-1).offset = _;
180
334
  break;
181
335
 
182
- case Opcodes.f64_const:
183
- vals.push(read_ieee754_binary64(i.slice(1)).toExponential());
336
+ case Opcodes.f64_const: {
337
+ // const val = read_ieee754_binary64(i.slice(1)).toExponential();
338
+ const val = new String(read_ieee754_binary64(i.slice(1)).toExponential());
339
+ // vals.push(val == 'NaN' ? 'NAN' : val);
340
+ vals.push(val == 'NaN' ? new String('NAN') : val);
341
+ vals.at(-1).offset = _;
184
342
  break;
343
+ }
185
344
 
186
345
  case Opcodes.local_get:
187
346
  vals.push(`${invLocals[i[1]]}`);
@@ -192,9 +351,9 @@ export default ({ funcs, globals, tags, exceptions, pages }) => {
192
351
  break;
193
352
 
194
353
  case Opcodes.local_tee:
195
- // line(`${invLocals[i[1]]} = ${removeBrackets(vals.pop())}`);
196
- // vals.push(`${invLocals[i[1]]}`);
197
- vals.push(`((${invLocals[i[1]]} = ${vals.pop()}))`);
354
+ line(`${invLocals[i[1]]} = ${removeBrackets(vals.pop())}`);
355
+ vals.push(`${invLocals[i[1]]}`);
356
+ // vals.push(`((${invLocals[i[1]]} = ${vals.pop()}))`);
198
357
  break;
199
358
 
200
359
  case Opcodes.global_get:
@@ -207,78 +366,102 @@ export default ({ funcs, globals, tags, exceptions, pages }) => {
207
366
 
208
367
  case Opcodes.f64_trunc:
209
368
  // vals.push(`trunc(${vals.pop()})`);
210
- vals.push(`(int)(${removeBrackets(vals.pop())})`); // this is ~10x faster with clang??
369
+ vals.push(`(${CValtype.i32})(${removeBrackets(vals.pop())})`); // this is ~10x faster with clang??
211
370
  break;
212
371
 
213
372
  case Opcodes.f64_convert_i32_u:
214
373
  case Opcodes.f64_convert_i32_s:
215
374
  case Opcodes.f64_convert_i64_u:
216
375
  case Opcodes.f64_convert_i64_s:
217
- // int to double
218
- vals.push(`(double)${vals.pop()}`);
376
+ // int to f64
377
+ vals.push(`(${CValtype.f64})(${removeBrackets(vals.pop())})`);
219
378
  break;
220
379
 
380
+ case Opcodes.i32_eqz:
381
+ if (lastCond) {
382
+ vals.push(`!(${removeBrackets(vals.pop())})`);
383
+ } else {
384
+ let cond = '(' + removeBrackets(vals.pop());
385
+ if (cond.startsWith(`(${CValtype.i32})`)) cond = `${cond.slice(`(${CValtype.i32})`.length)}) == 0e+0`;
386
+ else cond += ') == 0';
387
+ vals.push(cond);
388
+ }
389
+ lastCond = true;
390
+ continue;
391
+
221
392
  case Opcodes.return:
222
393
  // line(`return${returns ? ` ${removeBrackets(vals.pop())}` : ''}`);
223
394
  line(`return${returns ? ` (struct ReturnValue){ ${removeBrackets(vals.pop())}, ${removeBrackets(vals.pop())} }` : ''}`);
224
395
  break;
225
396
 
226
- case Opcodes.if:
397
+ case Opcodes.if: {
227
398
  let cond = removeBrackets(vals.pop());
228
399
  if (!lastCond) {
229
- if (cond.startsWith('(long)')) cond = `${cond.slice(6)} == 1e+0`;
230
- else cond += ' == 1';
400
+ if (cond.startsWith(`(${CValtype.i32})`)) cond = `(${cond.slice(`(${CValtype.i32})`.length)}) != 0e+0`;
401
+ else cond = `(${cond}) != 0`;
231
402
  }
232
403
 
233
- ifTernary = i[1] !== Blocktype.void;
234
- if (ifTernary) {
235
- ifTernary = cond;
236
- break;
237
- }
238
-
239
- if (beginLoop) {
240
- beginLoop = false;
241
- line(`while (${cond}) {`, false);
242
-
243
- depth++;
244
- endNeedsCurly.push(true);
245
- ignoreEnd.push(false, true);
246
- break;
247
- }
404
+ line(`// if ${invValtype[i[1]] ?? ''}`, false);
405
+ blockStart(i, false);
248
406
 
249
407
  line(`if (${cond}) {`, false);
250
408
 
251
409
  depth++;
252
410
  endNeedsCurly.push(true);
253
- ignoreEnd.push(false);
254
411
  break;
412
+ }
255
413
 
256
- case Opcodes.else:
257
- if (ifTernary) break;
414
+ case Opcodes.else: {
415
+ const br = brs.at(-1);
416
+ const ret = rets.at(-1);
417
+ if (ret && ret !== Blocktype.void) {
418
+ // console.log(vals, ret);
419
+ // console.log(decompile(f.wasm.slice(_ - 5, _ + 1)));
420
+ if (vals.length > 0) line(`_r${br} = ${removeBrackets(vals.pop())}`);
421
+ // vals.push(`_r${br}`);
422
+ }
258
423
 
259
424
  depth--;
260
425
  line(`} else {`, false);
261
426
  depth++;
427
+
428
+ // reset "stack"
429
+ // vals = [];
262
430
  break;
431
+ }
263
432
 
264
- case Opcodes.loop:
265
- // not doing properly, fake a while loop
266
- beginLoop = true;
433
+ case Opcodes.loop: {
434
+ line(`// loop ${invValtype[i[1]] ?? ''}`, false);
435
+ blockStart(i, true);
436
+ endNeedsCurly.push(false);
267
437
  break;
438
+ }
268
439
 
269
- case Opcodes.end:
270
- if (ignoreEnd.pop()) break;
440
+ case Opcodes.end: {
441
+ const br = brs.pop();
442
+ const ret = rets.pop();
443
+ if (ret && ret !== Blocktype.void) {
444
+ // console.log(vals, ret);
445
+ // console.log(decompile(f.wasm.slice(_ - 5, _ + 1)));
446
+ if (vals.length > 0) line(`_r${br} = ${removeBrackets(vals.pop())}`);
447
+ vals.push(`_r${br}`);
448
+ }
271
449
 
272
- if (ifTernary) {
273
- const b = vals.pop();
274
- const a = vals.pop();
275
- vals.push(`${ifTernary} ? ${a} : ${b}`);
276
- break;
450
+ const enc = endNeedsCurly.pop() === true;
451
+ if (enc) {
452
+ depth--;
453
+ line('}', false);
277
454
  }
278
455
 
279
- depth--;
280
- if (endNeedsCurly.pop() === true) line('}', false);
456
+ brDepth--;
457
+
458
+ line(`// end`, false);
459
+
460
+ const roe = runOnEnd.pop();
461
+ if (roe) roe();
462
+
281
463
  break;
464
+ }
282
465
 
283
466
  case Opcodes.call:
284
467
  let func = funcs.find(x => x.index === i[1]);
@@ -286,7 +469,8 @@ export default ({ funcs, globals, tags, exceptions, pages }) => {
286
469
  const importFunc = importFuncs[i[1]];
287
470
  switch (importFunc.name) {
288
471
  case 'print':
289
- line(`printf("%f\\n", ${vals.pop()})`);
472
+ // line(`printf("%f\\n", ${vals.pop()})`);
473
+ line(`printf("${valtype === 'f64' ? '%g' : '%i'}\\n", ${vals.pop()})`);
290
474
  includes.set('stdio.h', true);
291
475
  break;
292
476
  case 'printChar':
@@ -349,13 +533,74 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
349
533
  vals.pop();
350
534
  break;
351
535
 
352
- case Opcodes.br:
353
- // ignore
354
- // reset "stack"
355
- vals = [];
536
+ case Opcodes.block:
537
+ line(`// block ${invValtype[i[1]] ?? ''}`, false);
538
+ blockStart(i, false);
539
+ endNeedsCurly.push(false);
540
+ break;
541
+
542
+ case Opcodes.br: {
543
+ const ret = rets[brDepth - i[1] - 1];
544
+ // console.log(rets, brDepth, i[1], brDepth - i[1] - 1, ret, vals);
545
+ if (ret !== Blocktype.void) line(`_r${brs[brDepth - i[1] - 1]} = ${removeBrackets(vals.pop())}`);
546
+ line(`goto j${brs[brDepth - i[1] - 1]}`);
547
+
548
+ // // reset "stack"
549
+ // vals = [];
550
+ break;
551
+ }
552
+
553
+ case Opcodes.br_if: {
554
+ const ret = rets[brDepth - i[1] - 1];
555
+ // console.log(rets, brDepth, i[1], brDepth - i[1] - 1, ret, vals);
556
+
557
+ let cond = removeBrackets(vals.pop());
558
+ if (!lastCond) {
559
+ if (cond.startsWith(`(${CValtype.i32})`)) cond = `(${cond.slice(`(${CValtype.i32})`.length)}) != 0e+0`;
560
+ else cond = `(${cond}) != 0`;
561
+ }
562
+
563
+ line(`if (${cond}) {`, false);
564
+ depth++;
565
+ if (ret !== Blocktype.void) line(`_r${brs[brDepth - i[1] - 1]} = ${removeBrackets(vals.at(-1))}`);
566
+ line(`goto j${brs[brDepth - i[1] - 1]}`);
567
+ depth--;
568
+ line(`}`, false);
569
+
570
+ break;
571
+ }
572
+
573
+ case Opcodes.throw: {
574
+ const id = vals.pop();
575
+
576
+ line(`printf("Uncaught ${exceptions[id].constructor}: ${exceptions[id].message}\\n")`);
577
+ line(`exit(1)`);
578
+
579
+ includes.set('stdlib.h', true);
580
+
356
581
  break;
582
+ }
357
583
 
358
584
  default:
585
+ if (CMemFuncs[i[0]]) {
586
+ const name = invOpcodes[i[0]];
587
+ const func = CMemFuncs[i[0]];
588
+ if (!prepend.has(name)) {
589
+ 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`);
590
+ // generate func c and prepend
591
+ }
592
+
593
+ const immediates = [ i[1], read_unsignedLEB128(i.slice(2)) ];
594
+
595
+ let args = [];
596
+ for (let j = 0; j < func.args.length; j++) args.unshift(removeBrackets(vals.pop()));
597
+
598
+ if (func.returns !== false) {
599
+ vals.push(`${name}(${immediates[0]}, ${immediates[1]}, ${args.join(', ')})`);
600
+ } else line(`${name}(${immediates[0]}, ${immediates[1]}, ${args.join(', ')})`);
601
+ break;
602
+ }
603
+
359
604
  log.warning('2c', `unimplemented op: ${invOpcodes[i[0]]}`);
360
605
  // todo(`unimplemented op: ${invOpcodes[i[0]]}`);
361
606
  }
@@ -379,7 +624,7 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
379
624
 
380
625
  const makeIncludes = includes => [...includes.keys()].map(x => `#include <${x}>\n`).join('');
381
626
 
382
- out = platformSpecific(makeIncludes(winIncludes), makeIncludes(unixIncludes), false) + '\n' + makeIncludes(includes) + '\n' + out;
627
+ out = alwaysPreface + platformSpecific(makeIncludes(winIncludes), makeIncludes(unixIncludes), false) + '\n' + makeIncludes(includes) + '\n' + [...prepend.values()].join('\n') + '\n\n' + out;
383
628
 
384
- return out;
629
+ return out.trim();
385
630
  };