porffor 0.2.0-536e463 → 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
@@ -1,5 +1,5 @@
1
1
  # Porffor &nbsp;<sup><sub>/ˈpɔrfɔr/ &nbsp;*(poor-for)*</sup></sub>
2
- A from-scratch experimental **AOT** optimizing JS -> Wasm/C engine/compiler/runtime in JS. Not serious/intended for (real) use. (this is a straight forward, honest readme)<br>
2
+ A from-scratch experimental **AOT** optimizing JS/TS -> Wasm/C engine/compiler/runtime in JS. Not serious/intended for (real) use. (this is a straight forward, honest readme)<br>
3
3
  Age: ~6 months (very on and off)
4
4
 
5
5
  ## Design
@@ -118,10 +118,10 @@ No particular order and no guarentees, just what could happen soon™
118
118
  - Objects
119
119
  - Basic object expressions (eg `{}`, `{ a: 0 }`)
120
120
  - Wasm
121
- - *Basic* Wasm engine (interpreter) in js
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,10 +130,24 @@ 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
- - Use type(script) information to remove unneeded typechecker code
133
+ - Memory alignment
134
+ - Runtime
135
+ - WASI target
136
+ - Run precompiled Wasm file if given
137
+ - Cool proposals
138
+ - [Optional Chaining Assignment](https://github.com/tc39/proposal-optional-chaining-assignment)
139
+ - [Modulus and Additional Integer Math](https://github.com/tc39/proposal-integer-and-modulus-math)
140
+ - [Array Equality](https://github.com/tc39/proposal-array-equality)
141
+ - [Declarations in Conditionals](https://github.com/tc39/proposal-Declarations-in-Conditionals)
142
+ - [Seeded Pseudo-Random Numbers](https://github.com/tc39/proposal-seeded-random)
143
+ - [`do` expressions](https://github.com/tc39/proposal-do-expressions)
144
+ - [String Trim Characters](https://github.com/Kingwl/proposal-string-trim-characters)
145
+ - Posts
146
+ - Inlining investigation
147
+ - Self hosted testing?
134
148
 
135
149
  ## Performance
136
- *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.
137
151
 
138
152
  ![Screenshot of comparison chart](https://github.com/CanadaHonk/porffor/assets/19228318/76c75264-cc68-4be1-8891-c06dc389d97a)
139
153
 
@@ -157,10 +171,12 @@ Mostly for reducing size. I do not really care about compiler perf/time as long
157
171
  - Remove unneeded blocks (no `br`s inside)
158
172
  - Remove unused imports
159
173
  - Use data segments for initing arrays/strings
174
+ - (Likely more not documented yet, todo)
160
175
 
161
176
  ### Wasm module
162
177
  - Type cache/index (no repeated types)
163
178
  - No main func if empty (and other exports)
179
+ - No tags if unused/optimized out
164
180
 
165
181
  ## Test262
166
182
  Porffor can run Test262 via some hacks/transforms which remove unsupported features whilst still doing the same asserts (eg simpler error messages using literals only). It currently passes >10% (see latest commit desc for latest and details). Use `node test262` to test, it will also show a difference of overall results between the last commit and current results.
@@ -180,7 +196,7 @@ Porffor can run Test262 via some hacks/transforms which remove unsupported featu
180
196
  - `wasmSpec.js`: "enums"/info from wasm spec
181
197
  - `wrap.js`: wrapper for compiler which instantiates and produces nice exports
182
198
 
183
- - `runner`: contains utils for running js with the compiler
199
+ - `runner`: contains utils for running JS with the compiler
184
200
  - `index.js`: the main file, you probably want to use this
185
201
  - `info.js`: runs with extra info printed
186
202
  - `repl.js`: basic repl (uses `node:repl`)
@@ -193,10 +209,13 @@ Porffor can run Test262 via some hacks/transforms which remove unsupported featu
193
209
  - `test262`: test262 runner and utils
194
210
 
195
211
  ## Usecases
196
- 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?
197
216
 
198
217
  ## Usage
199
- Basically nothing will work :). See files in `test` for examples.
218
+ Basically nothing will work :). See files in `test` and `bench` for examples.
200
219
 
201
220
  1. Clone repo
202
221
  2. `npm install`
@@ -212,11 +231,14 @@ You can also use Deno (`deno run -A ...` instead of `node ...`), or Bun (`bun ..
212
231
  - `-target=native` only:
213
232
  - `-compiler=clang` to set compiler binary (path/name) to use to compile
214
233
  - `-cO=O3` to set compiler opt argument
234
+ - `-parser=acorn|@babel/parser|meriyah|hermes-parser` (default: `acorn`) to set which parser to use
235
+ - `-parse-types` to enable parsing type annotations/typescript. if `-parser` is unset, changes default to `@babel/parser`. does not type check
236
+ - `-opt-types` to perform optimizations using type annotations as compiler hints. does not type check
215
237
  - `-valtype=i32|i64|f64` (default: `f64`) to set valtype
216
238
  - `-O0` to disable opt
217
239
  - `-O1` (default) to enable basic opt (simplify insts, treeshake wasm imports)
218
- - `-O2` to enable advanced opt (inlining)
219
- - `-O3` to enable advanceder opt (precompute const math)
240
+ - `-O2` to enable advanced opt (inlining). unstable
241
+ - `-O3` to enable advanceder opt (precompute const math). unstable
220
242
  - `-no-run` to not run wasm output, just compile
221
243
  - `-opt-log` to log some opts
222
244
  - `-code-log` to log some codegen (you probably want `-funcs`)
@@ -230,20 +252,20 @@ You can also use Deno (`deno run -A ...` instead of `node ...`), or Bun (`bun ..
230
252
  - `-compile-hints` to enable V8 compilation hints (experimental + doesn't seem to do much?)
231
253
 
232
254
  ## VSCode extension
233
- There is a vscode extension in `porffor-for-vscode` which tweaks js syntax highlighting to be nicer with porffor features (eg highlighting wasm inside of inline asm).
255
+ There is a vscode extension in `porffor-for-vscode` which tweaks JS syntax highlighting to be nicer with porffor features (eg highlighting wasm inside of inline asm).
234
256
 
235
257
  ## Isn't this the same as AssemblyScript/other Wasm langs?
236
258
  No. they are not alike at all internally and have very different goals/ideals:
237
259
  - Porffor is made as a generic JS engine, not for Wasm stuff specifically
238
- - Porffor takes in JS, not a different language or typescript
239
- - Porffor is made in pure JS and compiles itself, not using Binaryen/etc
260
+ - Porffor primarily consumes JS
261
+ - Porffor is written in pure JS and compiles itself, not using Binaryen/etc
240
262
  - (Also I didn't know it existed when I started this, lol)
241
263
 
242
264
  ## FAQ
243
265
 
244
266
  ### 1. Why the name?
245
267
  `purple` in Welsh is `porffor`. Why purple?
246
- - No other js engine is purple colored
268
+ - No other JS engine is purple colored
247
269
  - Purple is pretty cool
248
270
  - Purple apparently represents "ambition", which is.. one word to describe this project
249
271
  - The hard to speak name is also the noise your brain makes in reaction to this idea!
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 ? 'double' : '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 = [];
356
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
+
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
  }
@@ -367,6 +612,11 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
367
612
  line(`return ${vals.pop()}`);
368
613
  }
369
614
 
615
+ if (f.name === 'main') {
616
+ out += '\n';
617
+ line(`return 0`);
618
+ }
619
+
370
620
  out += '}\n\n';
371
621
  }
372
622
 
@@ -374,7 +624,7 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
374
624
 
375
625
  const makeIncludes = includes => [...includes.keys()].map(x => `#include <${x}>\n`).join('');
376
626
 
377
- 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;
378
628
 
379
- return out;
629
+ return out.trim();
380
630
  };