porffor 0.2.0-a759814 → 0.2.0-af678f0

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
@@ -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,7 +130,10 @@ 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
134
137
  - Cool proposals
135
138
  - [Optional Chaining Assignment](https://github.com/tc39/proposal-optional-chaining-assignment)
136
139
  - [Modulus and Additional Integer Math](https://github.com/tc39/proposal-integer-and-modulus-math)
@@ -139,9 +142,12 @@ No particular order and no guarentees, just what could happen soon™
139
142
  - [Seeded Pseudo-Random Numbers](https://github.com/tc39/proposal-seeded-random)
140
143
  - [`do` expressions](https://github.com/tc39/proposal-do-expressions)
141
144
  - [String Trim Characters](https://github.com/Kingwl/proposal-string-trim-characters)
145
+ - Posts
146
+ - Inlining investigation
147
+ - Self hosted testing?
142
148
 
143
149
  ## Performance
144
- *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.
145
151
 
146
152
  ![Screenshot of comparison chart](https://github.com/CanadaHonk/porffor/assets/19228318/76c75264-cc68-4be1-8891-c06dc389d97a)
147
153
 
@@ -165,10 +171,12 @@ Mostly for reducing size. I do not really care about compiler perf/time as long
165
171
  - Remove unneeded blocks (no `br`s inside)
166
172
  - Remove unused imports
167
173
  - Use data segments for initing arrays/strings
174
+ - (Likely more not documented yet, todo)
168
175
 
169
176
  ### Wasm module
170
177
  - Type cache/index (no repeated types)
171
178
  - No main func if empty (and other exports)
179
+ - No tags if unused/optimized out
172
180
 
173
181
  ## Test262
174
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.
@@ -188,7 +196,7 @@ Porffor can run Test262 via some hacks/transforms which remove unsupported featu
188
196
  - `wasmSpec.js`: "enums"/info from wasm spec
189
197
  - `wrap.js`: wrapper for compiler which instantiates and produces nice exports
190
198
 
191
- - `runner`: contains utils for running js with the compiler
199
+ - `runner`: contains utils for running JS with the compiler
192
200
  - `index.js`: the main file, you probably want to use this
193
201
  - `info.js`: runs with extra info printed
194
202
  - `repl.js`: basic repl (uses `node:repl`)
@@ -201,17 +209,42 @@ Porffor can run Test262 via some hacks/transforms which remove unsupported featu
201
209
  - `test262`: test262 runner and utils
202
210
 
203
211
  ## Usecases
204
- 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!
215
+ - More in future probably?
205
216
 
206
217
  ## Usage
207
- Basically nothing will work :). See files in `test` for examples.
218
+ Basically nothing will work :). See files in `test` and `bench` for examples.
208
219
 
209
- 1. Clone repo
210
- 2. `npm install`
211
- 3. `node test` to run tests (some will fail)
212
- 4. `node runner path/to/code.js` to run a file (or `node runner` to use wip repl)
220
+ ### Setup
221
+ 1. Clone this repo
222
+ 2. `npm install` - for parser(s)
213
223
 
214
- You can also use Deno (`deno run -A ...` instead of `node ...`), or Bun (`bun ...` instead of `node ...`).
224
+ ### Running a file
225
+ The repos comes with easy alias files for Unix and Windows, which you can use like so:
226
+ - Unix: `./porf path/to/script.js`
227
+ - Windows: `.\porf path/to/script.js`
228
+
229
+ Please note that further examples below will just use `./porf`, you need to use `.\porf` on Windows. You can also swap out `node` in the alias to use another runtime like Deno (`deno run -A`) or Bun (`bun ...`), or just use it yourself (eg `node runner/index.js ...`, `bun runner/index.js ...`). Node and Bun should work great, Deno support is WIP.
230
+
231
+ ### Trying a REPL
232
+ **`./porf`**. Just run it with no script file argument.
233
+
234
+ ### Compiling to native binaries
235
+ > [!WARNING]
236
+ > Compiling to native binaries uses [2c](#2c), Porffor's own Wasm->C compiler, which is experimental.
237
+
238
+ **`./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.
239
+
240
+ ### Compiling to C
241
+ > [!WARNING]
242
+ > Compiling to C uses [2c](#2c), Porffor's own Wasm->C compiler, which is experimental.
243
+
244
+ **`./porf c path/to/script.js (out.c)`**. When not including an output file, it will be printed to stdout instead.
245
+
246
+ ### Compiling to a Wasm binary
247
+ **`./porf compile path/to/script.js out.wasm`**. Currently it does not use an import standard like WASI, so it is mostly unusable.
215
248
 
216
249
  ### Options
217
250
  - `-target=wasm|c|native` (default: `wasm`) to set target output (native compiles c output to binary, see args below)
@@ -241,20 +274,20 @@ You can also use Deno (`deno run -A ...` instead of `node ...`), or Bun (`bun ..
241
274
  - `-compile-hints` to enable V8 compilation hints (experimental + doesn't seem to do much?)
242
275
 
243
276
  ## VSCode extension
244
- 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).
277
+ 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).
245
278
 
246
279
  ## Isn't this the same as AssemblyScript/other Wasm langs?
247
280
  No. they are not alike at all internally and have very different goals/ideals:
248
281
  - Porffor is made as a generic JS engine, not for Wasm stuff specifically
249
- - Porffor takes in JS, not a different language or typescript
250
- - Porffor is made in pure JS and compiles itself, not using Binaryen/etc
282
+ - Porffor primarily consumes JS
283
+ - Porffor is written in pure JS and compiles itself, not using Binaryen/etc
251
284
  - (Also I didn't know it existed when I started this, lol)
252
285
 
253
286
  ## FAQ
254
287
 
255
288
  ### 1. Why the name?
256
289
  `purple` in Welsh is `porffor`. Why purple?
257
- - No other js engine is purple colored
290
+ - No other JS engine is purple colored
258
291
  - Purple is pretty cool
259
292
  - Purple apparently represents "ambition", which is.. one word to describe this project
260
293
  - 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,106 @@
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 uint8_t i8;
21
+ typedef uint16_t i16;
22
+ typedef int32_t i32;
23
+ typedef uint32_t u32;
24
+ typedef int64_t i64;
25
+ typedef uint64_t 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
+ };\n\n`;
35
+
36
+ // todo: is memcpy/etc safe with host endianness?
37
+
38
+ // all:
39
+ // immediates: ['align', 'offset']
40
+ const CMemFuncs = {
41
+ [Opcodes.i32_store]: {
42
+ c: `memcpy(_memory + offset + pointer, &value, sizeof(value));`,
43
+ args: ['pointer', 'value'],
44
+ argTypes: [CValtype.i32, CValtype.i32],
45
+ returns: false
46
+ },
47
+ [Opcodes.i32_store16]: {
48
+ c: `memcpy(_memory + offset + pointer, &value, sizeof(value));`,
49
+ args: ['pointer', 'value'],
50
+ argTypes: [CValtype.i32, CValtype.i16],
51
+ returns: false
52
+ },
53
+ [Opcodes.i32_store8]: {
54
+ c: `memcpy(_memory + offset + pointer, &value, sizeof(value));`,
55
+ args: ['pointer', 'value'],
56
+ argTypes: [CValtype.i32, CValtype.i8],
57
+ returns: false
58
+ },
59
+
60
+ [Opcodes.i32_load]: {
61
+ c: `${CValtype.i32} out;
62
+ memcpy(&out, _memory + offset + pointer, sizeof(out));
63
+ return out;`,
64
+ args: ['pointer'],
65
+ argTypes: [CValtype.i32],
66
+ returns: CValtype.i32
67
+ },
68
+ [Opcodes.i32_load16_u]: {
69
+ c: `${CValtype.i16} out;
70
+ memcpy(&out, _memory + offset + pointer, sizeof(out));
71
+ return out;`,
72
+ args: ['pointer'],
73
+ argTypes: [CValtype.i32],
74
+ returns: CValtype.i32
75
+ },
76
+ [Opcodes.i32_load8_u]: {
77
+ c: `${CValtype.i8} out;
78
+ memcpy(&out, _memory + offset + pointer, sizeof(out));
79
+ return out;`,
80
+ args: ['pointer'],
81
+ argTypes: [CValtype.i32],
82
+ returns: CValtype.i32
83
+ },
84
+
85
+ [Opcodes.f64_store]: {
86
+ c: `memcpy(_memory + offset + pointer, &value, sizeof(value));`,
87
+ args: ['pointer', 'value'],
88
+ argTypes: [CValtype.i32, CValtype.f64],
89
+ returns: false
90
+ },
91
+ [Opcodes.f64_load]: {
92
+ c: `${CValtype.f64} out;
93
+ memcpy(&out, _memory + offset + pointer, sizeof(out));
94
+ return out;`,
95
+ args: ['pointer'],
96
+ argTypes: [CValtype.i32],
97
+ returns: CValtype.f64
98
+ },
99
+ };
100
+
20
101
  const inv = (obj, keyMap = x => x) => Object.keys(obj).reduce((acc, x) => { acc[keyMap(obj[x])] = x; return acc; }, {});
21
102
  const invOpcodes = inv(Opcodes);
103
+ const invValtype = inv(Valtype);
22
104
 
23
105
  for (const x in CValtype) {
24
106
  if (Valtype[x]) CValtype[Valtype[x]] = CValtype[x];
@@ -36,11 +118,18 @@ const todo = msg => {
36
118
  };
37
119
 
38
120
  const removeBrackets = str => {
39
- if (str.startsWith('(long)(unsigned long)')) return '(long)(unsigned long)(' + removeBrackets(str.slice(22, -1)) + ')';
121
+ // return str;
122
+ // if (str.startsWith(`(${CValtype.i32})(${CValtype.u32})`)) return `(${CValtype.i32})(${CValtype.u32})(` + removeBrackets(str.slice(22, -1)) + ')';
123
+
124
+ for (const x in CValtype) {
125
+ const p = `(${x})`;
126
+ if (str.startsWith(p)) return p + removeBrackets(str.slice(p.length));
127
+ }
128
+
40
129
  return str.startsWith('(') && str.endsWith(')') ? str.slice(1, -1) : str;
41
130
  };
42
131
 
43
- export default ({ funcs, globals, tags, exceptions, pages }) => {
132
+ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
44
133
  const invOperatorOpcode = Object.values(operatorOpcode).reduce((acc, x) => {
45
134
  for (const k in x) {
46
135
  acc[x[k]] = k;
@@ -56,12 +145,11 @@ export default ({ funcs, globals, tags, exceptions, pages }) => {
56
145
  }
57
146
 
58
147
  const includes = new Map(), unixIncludes = new Map(), winIncludes = new Map();
148
+ const prepend = new Map(), prependMain = new Map();
59
149
 
60
- // TODO: make type i16
61
- let out = `struct ReturnValue {
62
- ${CValtype.f64} value;
63
- ${CValtype.i32} type;
64
- };\n\n`;
150
+ includes.set('stdint.h', true);
151
+
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}, (unsigned 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';
231
- }
232
-
233
- ifTernary = i[1] !== Blocktype.void;
234
- if (ifTernary) {
235
- ifTernary = cond;
236
- break;
400
+ if (cond.startsWith(`(${CValtype.i32})`)) cond = `(${cond.slice(`(${CValtype.i32})`.length)}) != 0e+0`;
401
+ else cond = `(${cond}) != 0`;
237
402
  }
238
403
 
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
+
356
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
  }
@@ -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 = platformSpecific(makeIncludes(winIncludes), makeIncludes(unixIncludes), false) + '\n' + makeIncludes(includes) + '\n' + alwaysPreface + [...prepend.values()].join('\n') + '\n\n' + out;
383
628
 
384
- return out;
629
+ return out.trim();
385
630
  };