porffor 0.2.0-9ca9aed → 0.2.0-a6c01f5

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.
Files changed (54) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +169 -76
  3. package/asur/README.md +2 -0
  4. package/asur/index.js +1262 -0
  5. package/byg/index.js +237 -0
  6. package/compiler/2c.js +317 -72
  7. package/compiler/{sections.js → assemble.js} +64 -16
  8. package/compiler/builtins/annexb_string.js +72 -0
  9. package/compiler/builtins/annexb_string.ts +19 -0
  10. package/compiler/builtins/array.ts +145 -0
  11. package/compiler/builtins/base64.ts +151 -0
  12. package/compiler/builtins/crypto.ts +120 -0
  13. package/compiler/builtins/date.ts +1370 -0
  14. package/compiler/builtins/escape.ts +141 -0
  15. package/compiler/builtins/int.ts +147 -0
  16. package/compiler/builtins/number.ts +527 -0
  17. package/compiler/builtins/porffor.d.ts +42 -0
  18. package/compiler/builtins/string.ts +1055 -0
  19. package/compiler/builtins/tostring.ts +45 -0
  20. package/compiler/builtins.js +601 -272
  21. package/compiler/{codeGen.js → codegen.js} +1282 -484
  22. package/compiler/decompile.js +3 -3
  23. package/compiler/embedding.js +22 -22
  24. package/compiler/encoding.js +108 -10
  25. package/compiler/generated_builtins.js +1262 -0
  26. package/compiler/index.js +36 -34
  27. package/compiler/log.js +6 -3
  28. package/compiler/opt.js +65 -29
  29. package/compiler/parse.js +42 -35
  30. package/compiler/precompile.js +123 -0
  31. package/compiler/prefs.js +26 -0
  32. package/compiler/prototype.js +177 -37
  33. package/compiler/types.js +37 -0
  34. package/compiler/wasmSpec.js +31 -7
  35. package/compiler/wrap.js +141 -43
  36. package/fib.js +7 -0
  37. package/package.json +9 -5
  38. package/porf +4 -0
  39. package/rhemyn/compile.js +5 -3
  40. package/rhemyn/parse.js +323 -320
  41. package/rhemyn/test/parse.js +58 -58
  42. package/runner/compare.js +34 -34
  43. package/runner/debug.js +122 -0
  44. package/runner/index.js +74 -10
  45. package/runner/profiler.js +102 -0
  46. package/runner/repl.js +42 -9
  47. package/runner/sizes.js +37 -37
  48. package/compiler/builtins/base64.js +0 -92
  49. package/runner/info.js +0 -89
  50. package/runner/profile.js +0 -46
  51. package/runner/results.json +0 -1
  52. package/runner/transform.js +0 -15
  53. package/tmp.c +0 -71
  54. package/util/enum.js +0 -20
@@ -1,5 +1,5 @@
1
1
  import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
2
- import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
2
+ import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from "./encoding.js";
3
3
  import { operatorOpcode } from "./expression.js";
4
4
  import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
5
5
  import { PrototypeFuncs } from "./prototype.js";
@@ -7,6 +7,8 @@ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enfor
7
7
  import { log } from "./log.js";
8
8
  import parse from "./parse.js";
9
9
  import * as Rhemyn from "../rhemyn/compile.js";
10
+ import Prefs from './prefs.js';
11
+ import { TYPES, TYPE_NAMES } from './types.js';
10
12
 
11
13
  let globals = {};
12
14
  let globalInd = 0;
@@ -23,39 +25,41 @@ const debug = str => {
23
25
  const logChar = n => {
24
26
  code.push(...number(n));
25
27
 
26
- code.push(Opcodes.call);
27
- code.push(...unsignedLEB128(0));
28
+ code.push([ Opcodes.call, 0 ]);
28
29
  };
29
30
 
30
31
  for (let i = 0; i < str.length; i++) {
31
32
  logChar(str.charCodeAt(i));
32
33
  }
33
34
 
34
- logChar('\n'.charCodeAt(0));
35
+ logChar(10); // new line
35
36
 
36
37
  return code;
37
38
  };
38
39
 
39
- const todo = msg => {
40
- class TodoError extends Error {
41
- constructor(message) {
42
- super(message);
43
- this.name = 'TodoError';
44
- }
40
+ class TodoError extends Error {
41
+ constructor(message) {
42
+ super(message);
43
+ this.name = 'TodoError';
45
44
  }
45
+ }
46
+ const todo = (scope, msg, expectsValue = undefined) => {
47
+ switch (Prefs.todoTime ?? 'runtime') {
48
+ case 'compile':
49
+ throw new TodoError(msg);
46
50
 
47
- throw new TodoError(`todo: ${msg}`);
48
-
49
- const code = [];
50
-
51
- code.push(...debug(`todo! ` + msg));
52
- code.push(Opcodes.unreachable);
51
+ case 'runtime':
52
+ return internalThrow(scope, 'TodoError', msg, expectsValue);
53
53
 
54
- return code;
54
+ // return [
55
+ // ...debug(`todo! ${msg}`),
56
+ // [ Opcodes.unreachable ]
57
+ // ];
58
+ }
55
59
  };
56
60
 
57
61
  const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
58
- const generate = (scope, decl, global = false, name = undefined) => {
62
+ const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
59
63
  switch (decl.type) {
60
64
  case 'BinaryExpression':
61
65
  return generateBinaryExp(scope, decl, global, name);
@@ -68,7 +72,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
68
72
 
69
73
  case 'ArrowFunctionExpression':
70
74
  case 'FunctionDeclaration':
71
- generateFunc(scope, decl);
75
+ const func = generateFunc(scope, decl);
76
+
77
+ if (decl.type.endsWith('Expression')) {
78
+ return number(func.index);
79
+ }
80
+
72
81
  return [];
73
82
 
74
83
  case 'BlockStatement':
@@ -81,7 +90,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
81
90
  return generateExp(scope, decl);
82
91
 
83
92
  case 'CallExpression':
84
- return generateCall(scope, decl, global, name);
93
+ return generateCall(scope, decl, global, name, valueUnused);
85
94
 
86
95
  case 'NewExpression':
87
96
  return generateNew(scope, decl, global, name);
@@ -99,7 +108,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
99
108
  return generateUnary(scope, decl);
100
109
 
101
110
  case 'UpdateExpression':
102
- return generateUpdate(scope, decl);
111
+ return generateUpdate(scope, decl, global, name, valueUnused);
103
112
 
104
113
  case 'IfStatement':
105
114
  return generateIf(scope, decl);
@@ -110,6 +119,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
110
119
  case 'WhileStatement':
111
120
  return generateWhile(scope, decl);
112
121
 
122
+ case 'DoWhileStatement':
123
+ return generateDoWhile(scope, decl);
124
+
113
125
  case 'ForOfStatement':
114
126
  return generateForOf(scope, decl);
115
127
 
@@ -119,6 +131,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
119
131
  case 'ContinueStatement':
120
132
  return generateContinue(scope, decl);
121
133
 
134
+ case 'LabeledStatement':
135
+ return generateLabel(scope, decl);
136
+
122
137
  case 'EmptyStatement':
123
138
  return generateEmpty(scope, decl);
124
139
 
@@ -132,7 +147,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
132
147
  return generateTry(scope, decl);
133
148
 
134
149
  case 'DebuggerStatement':
135
- // todo: add fancy terminal debugger?
150
+ // todo: hook into terminal debugger
136
151
  return [];
137
152
 
138
153
  case 'ArrayExpression':
@@ -146,16 +161,22 @@ const generate = (scope, decl, global = false, name = undefined) => {
146
161
  const funcsBefore = funcs.length;
147
162
  generate(scope, decl.declaration);
148
163
 
149
- if (funcsBefore === funcs.length) throw new Error('no new func added in export');
164
+ if (funcsBefore !== funcs.length) {
165
+ // new func added
166
+ const newFunc = funcs[funcs.length - 1];
167
+ newFunc.export = true;
168
+ }
169
+
170
+ // if (funcsBefore === funcs.length) throw new Error('no new func added in export');
150
171
 
151
- const newFunc = funcs[funcs.length - 1];
152
- newFunc.export = true;
172
+ // const newFunc = funcs[funcs.length - 1];
173
+ // newFunc.export = true;
153
174
 
154
175
  return [];
155
176
 
156
177
  case 'TaggedTemplateExpression': {
157
178
  const funcs = {
158
- asm: str => {
179
+ __Porffor_wasm: str => {
159
180
  let out = [];
160
181
 
161
182
  for (const line of str.split('\n')) {
@@ -163,8 +184,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
163
184
  if (asm[0] === '') continue; // blank
164
185
 
165
186
  if (asm[0] === 'local') {
166
- const [ name, idx, type ] = asm.slice(1);
167
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
187
+ const [ name, type ] = asm.slice(1);
188
+ scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
168
189
  continue;
169
190
  }
170
191
 
@@ -174,7 +195,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
174
195
  }
175
196
 
176
197
  if (asm[0] === 'memory') {
177
- allocPage('asm instrinsic');
198
+ allocPage(scope, 'asm instrinsic');
178
199
  // todo: add to store/load offset insts
179
200
  continue;
180
201
  }
@@ -183,38 +204,65 @@ const generate = (scope, decl, global = false, name = undefined) => {
183
204
  if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
184
205
 
185
206
  if (!Array.isArray(inst)) inst = [ inst ];
186
- const immediates = asm.slice(1).map(x => parseInt(x));
207
+ const immediates = asm.slice(1).map(x => {
208
+ const int = parseInt(x);
209
+ if (Number.isNaN(int)) return scope.locals[x]?.idx;
210
+ return int;
211
+ });
187
212
 
188
- out.push([ ...inst, ...immediates ]);
213
+ out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
189
214
  }
190
215
 
191
216
  return out;
192
217
  },
193
218
 
194
- __internal_print_type: str => {
195
- const type = getType(scope, str) - TYPES.number;
219
+ __Porffor_bs: str => [
220
+ ...makeString(scope, str, global, name, true),
196
221
 
197
- return [
198
- ...number(type),
199
- [ Opcodes.call, importedFuncs.print ],
222
+ ...(name ? setType(scope, name, TYPES._bytestring) : [
223
+ ...number(TYPES._bytestring, Valtype.i32),
224
+ ...setLastType(scope)
225
+ ])
226
+ ],
227
+ __Porffor_s: str => [
228
+ ...makeString(scope, str, global, name, false),
200
229
 
201
- // newline
202
- ...number(10),
203
- [ Opcodes.call, importedFuncs.printChar ]
204
- ];
205
- }
206
- }
230
+ ...(name ? setType(scope, name, TYPES.string) : [
231
+ ...number(TYPES.string, Valtype.i32),
232
+ ...setLastType(scope)
233
+ ])
234
+ ],
235
+ };
207
236
 
208
- const name = decl.tag.name;
237
+ const func = decl.tag.name;
209
238
  // hack for inline asm
210
- if (!funcs[name]) return todo('tagged template expressions not implemented');
239
+ if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
240
+
241
+ const { quasis, expressions } = decl.quasi;
242
+ let str = quasis[0].value.raw;
211
243
 
212
- const str = decl.quasi.quasis[0].value.raw;
213
- return funcs[name](str);
244
+ for (let i = 0; i < expressions.length; i++) {
245
+ const e = expressions[i];
246
+ if (!e.name) {
247
+ if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
248
+ str += lookupName(scope, e.left.name)[0].idx + e.right.value;
249
+ } else todo(scope, 'unsupported expression in intrinsic');
250
+ } else str += lookupName(scope, e.name)[0].idx;
251
+
252
+ str += quasis[i + 1].value.raw;
253
+ }
254
+
255
+ return funcs[func](str);
214
256
  }
215
257
 
216
258
  default:
217
- return todo(`no generation for ${decl.type}!`);
259
+ // ignore typescript nodes
260
+ if (decl.type.startsWith('TS') ||
261
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
262
+ return [];
263
+ }
264
+
265
+ return todo(scope, `no generation for ${decl.type}!`);
218
266
  }
219
267
  };
220
268
 
@@ -242,7 +290,7 @@ const lookupName = (scope, _name) => {
242
290
  return [ undefined, undefined ];
243
291
  };
244
292
 
245
- const internalThrow = (scope, constructor, message, expectsValue = false) => [
293
+ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
246
294
  ...generateThrow(scope, {
247
295
  argument: {
248
296
  type: 'NewExpression',
@@ -264,25 +312,33 @@ const generateIdent = (scope, decl) => {
264
312
  const name = mapName(rawName);
265
313
  let local = scope.locals[rawName];
266
314
 
267
- if (builtinVars[name]) {
315
+ if (Object.hasOwn(builtinVars, name)) {
268
316
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
269
- return builtinVars[name];
317
+
318
+ let wasm = builtinVars[name];
319
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
320
+ return wasm.slice();
321
+ }
322
+
323
+ if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
324
+ // todo: return an actual something
325
+ return number(1);
270
326
  }
271
327
 
272
- if (builtinFuncs[name] || internalConstrs[name]) {
328
+ if (isExistingProtoFunc(name)) {
273
329
  // todo: return an actual something
274
330
  return number(1);
275
331
  }
276
332
 
277
- if (local === undefined) {
333
+ if (local?.idx === undefined) {
278
334
  // no local var with name
279
- if (importedFuncs.hasOwnProperty(name)) return number(importedFuncs[name]);
280
- if (funcIndex[name] !== undefined) return number(funcIndex[name]);
335
+ if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
336
+ if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
281
337
 
282
- if (globals[name] !== undefined) return [ [ Opcodes.global_get, globals[name].idx ] ];
338
+ if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
283
339
  }
284
340
 
285
- if (local === undefined && rawName.startsWith('__')) {
341
+ if (local?.idx === undefined && rawName.startsWith('__')) {
286
342
  // return undefined if unknown key in already known var
287
343
  let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
288
344
  if (parent.includes('_')) parent = '__' + parent;
@@ -291,7 +347,7 @@ const generateIdent = (scope, decl) => {
291
347
  if (!parentLookup[1]) return number(UNDEFINED);
292
348
  }
293
349
 
294
- if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
350
+ if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
295
351
 
296
352
  return [ [ Opcodes.local_get, local.idx ] ];
297
353
  };
@@ -304,14 +360,18 @@ const generateReturn = (scope, decl) => {
304
360
  // just bare "return"
305
361
  return [
306
362
  ...number(UNDEFINED), // "undefined" if func returns
307
- ...number(TYPES.undefined, Valtype.i32), // type undefined
363
+ ...(scope.returnType != null ? [] : [
364
+ ...number(TYPES.undefined, Valtype.i32) // type undefined
365
+ ]),
308
366
  [ Opcodes.return ]
309
367
  ];
310
368
  }
311
369
 
312
370
  return [
313
371
  ...generate(scope, decl.argument),
314
- ...getNodeType(scope, decl.argument),
372
+ ...(scope.returnType != null ? [] : [
373
+ ...getNodeType(scope, decl.argument)
374
+ ]),
315
375
  [ Opcodes.return ]
316
376
  ];
317
377
  };
@@ -325,7 +385,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
325
385
  return idx;
326
386
  };
327
387
 
328
- const isIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
388
+ const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
389
+ const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
329
390
 
330
391
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
331
392
  const checks = {
@@ -334,7 +395,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
334
395
  '??': nullish
335
396
  };
336
397
 
337
- if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
398
+ if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
338
399
 
339
400
  // generic structure for {a} OP {b}
340
401
  // -->
@@ -342,8 +403,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
342
403
 
343
404
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
344
405
  // (like if we are in an if condition - very common)
345
- const leftIsInt = isIntOp(left[left.length - 1]);
346
- const rightIsInt = isIntOp(right[right.length - 1]);
406
+ const leftIsInt = isFloatToIntOp(left[left.length - 1]);
407
+ const rightIsInt = isFloatToIntOp(right[right.length - 1]);
347
408
 
348
409
  const canInt = leftIsInt && rightIsInt;
349
410
 
@@ -360,12 +421,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
360
421
  ...right,
361
422
  // note type
362
423
  ...rightType,
363
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
424
+ ...setLastType(scope),
364
425
  [ Opcodes.else ],
365
426
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
366
427
  // note type
367
428
  ...leftType,
368
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
429
+ ...setLastType(scope),
369
430
  [ Opcodes.end ],
370
431
  Opcodes.i32_from
371
432
  ];
@@ -379,17 +440,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
379
440
  ...right,
380
441
  // note type
381
442
  ...rightType,
382
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
443
+ ...setLastType(scope),
383
444
  [ Opcodes.else ],
384
445
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
385
446
  // note type
386
447
  ...leftType,
387
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
448
+ ...setLastType(scope),
388
449
  [ Opcodes.end ]
389
450
  ];
390
451
  };
391
452
 
392
- const concatStrings = (scope, left, right, global, name, assign) => {
453
+ const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
393
454
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
394
455
  // todo: convert left and right to strings if not
395
456
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -399,11 +460,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
399
460
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
400
461
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
401
462
 
402
- const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
403
- if (aotWFA) addVarMeta(name, { wellFormed: undefined });
404
-
405
463
  if (assign) {
406
- const pointer = arrays.get(name ?? '$undeclared');
464
+ const pointer = scope.arrays?.get(name ?? '$undeclared');
407
465
 
408
466
  return [
409
467
  // setup right
@@ -428,11 +486,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
428
486
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
429
487
 
430
488
  // copy right
431
- // dst = out pointer + length size + current length * i16 size
489
+ // dst = out pointer + length size + current length * sizeof valtype
432
490
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
433
491
 
434
492
  [ Opcodes.local_get, leftLength ],
435
- ...number(ValtypeSize.i16, Valtype.i32),
493
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
436
494
  [ Opcodes.i32_mul ],
437
495
  [ Opcodes.i32_add ],
438
496
 
@@ -441,9 +499,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
441
499
  ...number(ValtypeSize.i32, Valtype.i32),
442
500
  [ Opcodes.i32_add ],
443
501
 
444
- // size = right length * i16 size
502
+ // size = right length * sizeof valtype
445
503
  [ Opcodes.local_get, rightLength ],
446
- ...number(ValtypeSize.i16, Valtype.i32),
504
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
447
505
  [ Opcodes.i32_mul ],
448
506
 
449
507
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -501,11 +559,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
501
559
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
502
560
 
503
561
  // copy right
504
- // dst = out pointer + length size + left length * i16 size
562
+ // dst = out pointer + length size + left length * sizeof valtype
505
563
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
506
564
 
507
565
  [ Opcodes.local_get, leftLength ],
508
- ...number(ValtypeSize.i16, Valtype.i32),
566
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
509
567
  [ Opcodes.i32_mul ],
510
568
  [ Opcodes.i32_add ],
511
569
 
@@ -514,9 +572,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
514
572
  ...number(ValtypeSize.i32, Valtype.i32),
515
573
  [ Opcodes.i32_add ],
516
574
 
517
- // size = right length * i16 size
575
+ // size = right length * sizeof valtype
518
576
  [ Opcodes.local_get, rightLength ],
519
- ...number(ValtypeSize.i16, Valtype.i32),
577
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
520
578
  [ Opcodes.i32_mul ],
521
579
 
522
580
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -526,7 +584,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
526
584
  ];
527
585
  };
528
586
 
529
- const compareStrings = (scope, left, right) => {
587
+ const compareStrings = (scope, left, right, bytestrings = false) => {
530
588
  // todo: this should be rewritten into a func
531
589
  // todo: convert left and right to strings if not
532
590
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -535,7 +593,6 @@ const compareStrings = (scope, left, right) => {
535
593
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
536
594
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
537
595
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
538
- const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
539
596
 
540
597
  const index = localTmp(scope, 'compare_index', Valtype.i32);
541
598
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
@@ -563,7 +620,6 @@ const compareStrings = (scope, left, right) => {
563
620
 
564
621
  [ Opcodes.local_get, rightPointer ],
565
622
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
566
- [ Opcodes.local_tee, rightLength ],
567
623
 
568
624
  // fast path: check leftLength != rightLength
569
625
  [ Opcodes.i32_ne ],
@@ -578,11 +634,13 @@ const compareStrings = (scope, left, right) => {
578
634
  ...number(0, Valtype.i32),
579
635
  [ Opcodes.local_set, index ],
580
636
 
581
- // setup index end as length * sizeof i16 (2)
637
+ // setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
582
638
  // we do this instead of having to do mul/div each iter for perf™
583
639
  [ Opcodes.local_get, leftLength ],
584
- ...number(ValtypeSize.i16, Valtype.i32),
585
- [ Opcodes.i32_mul ],
640
+ ...(bytestrings ? [] : [
641
+ ...number(ValtypeSize.i16, Valtype.i32),
642
+ [ Opcodes.i32_mul ],
643
+ ]),
586
644
  [ Opcodes.local_set, indexEnd ],
587
645
 
588
646
  // iterate over each char and check if eq
@@ -592,13 +650,17 @@ const compareStrings = (scope, left, right) => {
592
650
  [ Opcodes.local_get, index ],
593
651
  [ Opcodes.local_get, leftPointer ],
594
652
  [ Opcodes.i32_add ],
595
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
653
+ bytestrings ?
654
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
655
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
596
656
 
597
657
  // fetch right
598
658
  [ Opcodes.local_get, index ],
599
659
  [ Opcodes.local_get, rightPointer ],
600
660
  [ Opcodes.i32_add ],
601
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
661
+ bytestrings ?
662
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
663
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
602
664
 
603
665
  // not equal, "return" false
604
666
  [ Opcodes.i32_ne ],
@@ -607,13 +669,13 @@ const compareStrings = (scope, left, right) => {
607
669
  [ Opcodes.br, 2 ],
608
670
  [ Opcodes.end ],
609
671
 
610
- // index += sizeof i16 (2)
672
+ // index += sizeof valtype (1 for bytestring, 2 for string)
611
673
  [ Opcodes.local_get, index ],
612
- ...number(ValtypeSize.i16, Valtype.i32),
674
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
613
675
  [ Opcodes.i32_add ],
614
676
  [ Opcodes.local_tee, index ],
615
677
 
616
- // if index != index end (length * sizeof 16), loop
678
+ // if index != index end (length * sizeof valtype), loop
617
679
  [ Opcodes.local_get, indexEnd ],
618
680
  [ Opcodes.i32_ne ],
619
681
  [ Opcodes.br_if, 0 ],
@@ -634,16 +696,18 @@ const compareStrings = (scope, left, right) => {
634
696
  };
635
697
 
636
698
  const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
637
- if (isIntOp(wasm[wasm.length - 1])) return [
699
+ if (isFloatToIntOp(wasm[wasm.length - 1])) return [
638
700
  ...wasm,
639
701
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
640
702
  ];
703
+ // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
641
704
 
642
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
705
+ const useTmp = knownType(scope, type) == null;
706
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
643
707
 
644
708
  const def = [
645
709
  // if value != 0
646
- [ Opcodes.local_get, tmp ],
710
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
647
711
 
648
712
  // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
649
713
  ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
@@ -655,7 +719,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
655
719
 
656
720
  return [
657
721
  ...wasm,
658
- [ Opcodes.local_set, tmp ],
722
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
659
723
 
660
724
  ...typeSwitch(scope, type, {
661
725
  // [TYPES.number]: def,
@@ -664,7 +728,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
664
728
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
665
729
  ],
666
730
  [TYPES.string]: [
667
- [ Opcodes.local_get, tmp ],
731
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
668
732
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
669
733
 
670
734
  // get length
@@ -675,16 +739,27 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
675
739
  [ Opcodes.i32_eqz ], */
676
740
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
677
741
  ],
742
+ [TYPES._bytestring]: [ // duplicate of string
743
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
744
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
745
+
746
+ // get length
747
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
748
+
749
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
750
+ ],
678
751
  default: def
679
752
  }, intOut ? Valtype.i32 : valtypeBinary)
680
753
  ];
681
754
  };
682
755
 
683
756
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
684
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
757
+ const useTmp = knownType(scope, type) == null;
758
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
759
+
685
760
  return [
686
761
  ...wasm,
687
- [ Opcodes.local_set, tmp ],
762
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
688
763
 
689
764
  ...typeSwitch(scope, type, {
690
765
  [TYPES._array]: [
@@ -692,7 +767,18 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
692
767
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
693
768
  ],
694
769
  [TYPES.string]: [
695
- [ Opcodes.local_get, tmp ],
770
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
771
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
772
+
773
+ // get length
774
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
775
+
776
+ // if length == 0
777
+ [ Opcodes.i32_eqz ],
778
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
779
+ ],
780
+ [TYPES._bytestring]: [ // duplicate of string
781
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
696
782
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
697
783
 
698
784
  // get length
@@ -704,7 +790,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
704
790
  ],
705
791
  default: [
706
792
  // if value == 0
707
- [ Opcodes.local_get, tmp ],
793
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
708
794
 
709
795
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
710
796
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -714,10 +800,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
714
800
  };
715
801
 
716
802
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
717
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
803
+ const useTmp = knownType(scope, type) == null;
804
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
805
+
718
806
  return [
719
807
  ...wasm,
720
- [ Opcodes.local_set, tmp ],
808
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
721
809
 
722
810
  ...typeSwitch(scope, type, {
723
811
  [TYPES.undefined]: [
@@ -726,7 +814,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
726
814
  ],
727
815
  [TYPES.object]: [
728
816
  // object, null if == 0
729
- [ Opcodes.local_get, tmp ],
817
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
730
818
 
731
819
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
732
820
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -755,11 +843,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
755
843
  return performLogicOp(scope, op, left, right, leftType, rightType);
756
844
  }
757
845
 
846
+ const knownLeft = knownType(scope, leftType);
847
+ const knownRight = knownType(scope, rightType);
848
+
758
849
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
759
850
  const strictOp = op === '===' || op === '!==';
760
851
 
761
852
  const startOut = [], endOut = [];
762
- const finalise = out => startOut.concat(out, endOut);
853
+ const finalize = out => startOut.concat(out, endOut);
763
854
 
764
855
  // if strict (in)equal check types match
765
856
  if (strictOp) {
@@ -804,31 +895,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
804
895
  // todo: if equality op and an operand is undefined, return false
805
896
  // todo: niche null hell with 0
806
897
 
807
- // if (leftType === TYPES.string || rightType === TYPES.string) {
808
- // if (op === '+') {
809
- // // string concat (a + b)
810
- // return finalise(concatStrings(scope, left, right, _global, _name, assign));
811
- // }
812
-
813
- // // not an equality op, NaN
814
- // if (!eqOp) return finalise(number(NaN));
815
-
816
- // // else leave bool ops
817
- // // todo: convert string to number if string and number/bool
818
- // // todo: string (>|>=|<|<=) string
819
-
820
- // // string comparison
821
- // if (op === '===' || op === '==') {
822
- // return finalise(compareStrings(scope, left, right));
823
- // }
824
-
825
- // if (op === '!==' || op === '!=') {
826
- // return finalise([
827
- // ...compareStrings(scope, left, right),
828
- // [ Opcodes.i32_eqz ]
829
- // ]);
830
- // }
831
- // }
898
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) {
899
+ if (op === '+') {
900
+ // todo: this should be dynamic too but for now only static
901
+ // string concat (a + b)
902
+ return concatStrings(scope, left, right, _global, _name, assign, false);
903
+ }
904
+
905
+ // not an equality op, NaN
906
+ if (!eqOp) return number(NaN);
907
+
908
+ // else leave bool ops
909
+ // todo: convert string to number if string and number/bool
910
+ // todo: string (>|>=|<|<=) string
911
+
912
+ // string comparison
913
+ if (op === '===' || op === '==') {
914
+ return compareStrings(scope, left, right);
915
+ }
916
+
917
+ if (op === '!==' || op === '!=') {
918
+ return [
919
+ ...compareStrings(scope, left, right),
920
+ [ Opcodes.i32_eqz ]
921
+ ];
922
+ }
923
+ }
924
+
925
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) {
926
+ if (op === '+') {
927
+ // todo: this should be dynamic too but for now only static
928
+ // string concat (a + b)
929
+ return concatStrings(scope, left, right, _global, _name, assign, true);
930
+ }
931
+
932
+ // not an equality op, NaN
933
+ if (!eqOp) return number(NaN);
934
+
935
+ // else leave bool ops
936
+ // todo: convert string to number if string and number/bool
937
+ // todo: string (>|>=|<|<=) string
938
+
939
+ // string comparison
940
+ if (op === '===' || op === '==') {
941
+ return compareStrings(scope, left, right, true);
942
+ }
943
+
944
+ if (op === '!==' || op === '!=') {
945
+ return [
946
+ ...compareStrings(scope, left, right, true),
947
+ [ Opcodes.i32_eqz ]
948
+ ];
949
+ }
950
+ }
832
951
 
833
952
  let ops = operatorOpcode[valtype][op];
834
953
 
@@ -838,24 +957,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
838
957
  includeBuiltin(scope, builtinName);
839
958
  const idx = funcIndex[builtinName];
840
959
 
841
- return finalise([
960
+ return finalize([
842
961
  ...left,
843
962
  ...right,
844
963
  [ Opcodes.call, idx ]
845
964
  ]);
846
965
  }
847
966
 
848
- if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
967
+ if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
849
968
 
850
969
  if (!Array.isArray(ops)) ops = [ ops ];
851
970
  ops = [ ops ];
852
971
 
853
972
  let tmpLeft, tmpRight;
854
973
  // if equal op, check if strings for compareStrings
855
- if (op === '===' || op === '==' || op === '!==' || op === '!=') {
974
+ // todo: intelligent partial skip later
975
+ // if neither known are string, stop this madness
976
+ // we already do known checks earlier, so don't need to recheck
977
+
978
+ if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
856
979
  tmpLeft = localTmp(scope, '__tmpop_left');
857
980
  tmpRight = localTmp(scope, '__tmpop_right');
858
981
 
982
+ // returns false for one string, one not - but more ops/slower
983
+ // ops.unshift(...stringOnly([
984
+ // // if left is string
985
+ // ...leftType,
986
+ // ...number(TYPES.string, Valtype.i32),
987
+ // [ Opcodes.i32_eq ],
988
+
989
+ // // if right is string
990
+ // ...rightType,
991
+ // ...number(TYPES.string, Valtype.i32),
992
+ // [ Opcodes.i32_eq ],
993
+
994
+ // // if either are true
995
+ // [ Opcodes.i32_or ],
996
+ // [ Opcodes.if, Blocktype.void ],
997
+
998
+ // // todo: convert non-strings to strings, for now fail immediately if one is not
999
+ // // if left is not string
1000
+ // ...leftType,
1001
+ // ...number(TYPES.string, Valtype.i32),
1002
+ // [ Opcodes.i32_ne ],
1003
+
1004
+ // // if right is not string
1005
+ // ...rightType,
1006
+ // ...number(TYPES.string, Valtype.i32),
1007
+ // [ Opcodes.i32_ne ],
1008
+
1009
+ // // if either are true
1010
+ // [ Opcodes.i32_or ],
1011
+ // [ Opcodes.if, Blocktype.void ],
1012
+ // ...number(0, Valtype.i32),
1013
+ // [ Opcodes.br, 2 ],
1014
+ // [ Opcodes.end ],
1015
+
1016
+ // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1017
+ // ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1018
+ // [ Opcodes.br, 1 ],
1019
+ // [ Opcodes.end ],
1020
+ // ]));
1021
+
1022
+ // does not handle one string, one not (such cases go past)
859
1023
  ops.unshift(...stringOnly([
860
1024
  // if left is string
861
1025
  ...leftType,
@@ -867,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
867
1031
  ...number(TYPES.string, Valtype.i32),
868
1032
  [ Opcodes.i32_eq ],
869
1033
 
870
- // if either are true
871
- [ Opcodes.i32_or ],
1034
+ // if both are true
1035
+ [ Opcodes.i32_and ],
872
1036
  [ Opcodes.if, Blocktype.void ],
1037
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1038
+ ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1039
+ [ Opcodes.br, 1 ],
1040
+ [ Opcodes.end ],
873
1041
 
874
- // todo: convert non-strings to strings, for now fail immediately if one is not
875
- // if left is not string
1042
+ // if left is bytestring
876
1043
  ...leftType,
877
- ...number(TYPES.string, Valtype.i32),
878
- [ Opcodes.i32_ne ],
1044
+ ...number(TYPES._bytestring, Valtype.i32),
1045
+ [ Opcodes.i32_eq ],
879
1046
 
880
- // if right is not string
1047
+ // if right is bytestring
881
1048
  ...rightType,
882
- ...number(TYPES.string, Valtype.i32),
883
- [ Opcodes.i32_ne ],
1049
+ ...number(TYPES._bytestring, Valtype.i32),
1050
+ [ Opcodes.i32_eq ],
884
1051
 
885
- // if either are true
886
- [ Opcodes.i32_or ],
1052
+ // if both are true
1053
+ [ Opcodes.i32_and ],
887
1054
  [ Opcodes.if, Blocktype.void ],
888
- ...number(0, Valtype.i32),
889
- [ Opcodes.br, 1 ],
890
- [ Opcodes.end ],
891
-
892
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
893
- // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1055
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
894
1056
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
895
1057
  [ Opcodes.br, 1 ],
896
1058
  [ Opcodes.end ],
@@ -904,7 +1066,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
904
1066
  // }
905
1067
  }
906
1068
 
907
- return finalise([
1069
+ return finalize([
908
1070
  ...left,
909
1071
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
910
1072
  ...right,
@@ -921,7 +1083,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
921
1083
  return out;
922
1084
  };
923
1085
 
924
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
1086
+ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1087
+ return func({ name, params, locals, returns, localInd }, {
1088
+ TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
1089
+ builtin: name => {
1090
+ let idx = funcIndex[name] ?? importedFuncs[name];
1091
+ if (idx === undefined && builtinFuncs[name]) {
1092
+ includeBuiltin(null, name);
1093
+ idx = funcIndex[name];
1094
+ }
1095
+
1096
+ return idx;
1097
+ }
1098
+ });
1099
+ };
1100
+
1101
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
925
1102
  const existing = funcs.find(x => x.name === name);
926
1103
  if (existing) return existing;
927
1104
 
@@ -933,6 +1110,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
933
1110
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
934
1111
  }
935
1112
 
1113
+ for (const x of _data) {
1114
+ const copy = { ...x };
1115
+ copy.offset += pages.size * pageSize;
1116
+ data.push(copy);
1117
+ }
1118
+
1119
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1120
+
936
1121
  let baseGlobalIdx, i = 0;
937
1122
  for (const type of globalTypes) {
938
1123
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -955,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
955
1140
  params,
956
1141
  locals,
957
1142
  returns,
958
- returnType: TYPES[returnType ?? 'number'],
1143
+ returnType: returnType ?? TYPES.number,
959
1144
  wasm,
960
1145
  internal: true,
961
1146
  index: currentFuncIndex++
@@ -978,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
978
1163
  return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
979
1164
  };
980
1165
 
1166
+ // potential future ideas for nan boxing (unused):
981
1167
  // T = JS type, V = value/pointer
982
1168
  // 0bTTT
983
1169
  // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
@@ -1001,58 +1187,29 @@ const generateLogicExp = (scope, decl) => {
1001
1187
  // 4: internal type
1002
1188
  // 5: pointer
1003
1189
 
1004
- const TYPES = {
1005
- number: 0x00,
1006
- boolean: 0x01,
1007
- string: 0x02,
1008
- undefined: 0x03,
1009
- object: 0x04,
1010
- function: 0x05,
1011
- symbol: 0x06,
1012
- bigint: 0x07,
1013
-
1014
- // these are not "typeof" types but tracked internally
1015
- _array: 0x10,
1016
- _regexp: 0x11,
1017
-
1018
- // typed arrays
1019
- _int8array: 0x20,
1020
- _uint8array: 0x21,
1021
- _uint8clampedarray: 0x22,
1022
- _int16array: 0x23,
1023
- _uint16array: 0x24,
1024
- _int32array: 0x25,
1025
- _uint32array: 0x26,
1026
- _float32array: 0x27,
1027
- _float64array: 0x28,
1028
- };
1029
-
1030
- const TYPE_NAMES = {
1031
- [TYPES.number]: 'Number',
1032
- [TYPES.boolean]: 'Boolean',
1033
- [TYPES.string]: 'String',
1034
- [TYPES.undefined]: 'undefined',
1035
- [TYPES.object]: 'Object',
1036
- [TYPES.function]: 'Function',
1037
- [TYPES.symbol]: 'Symbol',
1038
- [TYPES.bigint]: 'BigInt',
1039
-
1040
- [TYPES._array]: 'Array',
1041
- [TYPES._regexp]: 'RegExp'
1190
+ const isExistingProtoFunc = name => {
1191
+ if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES._array][name.slice(18)];
1192
+ if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
1193
+
1194
+ return false;
1042
1195
  };
1043
1196
 
1044
1197
  const getType = (scope, _name) => {
1045
1198
  const name = mapName(_name);
1046
1199
 
1200
+ // if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
1201
+
1202
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1047
1203
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1204
+
1205
+ if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
1048
1206
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1049
1207
 
1050
1208
  let type = TYPES.undefined;
1051
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1209
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1052
1210
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1053
1211
 
1054
- if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
1055
- name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
1212
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1056
1213
 
1057
1214
  return number(type, Valtype.i32);
1058
1215
  };
@@ -1062,17 +1219,29 @@ const setType = (scope, _name, type) => {
1062
1219
 
1063
1220
  const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
1064
1221
 
1222
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return [];
1065
1223
  if (scope.locals[name]) return [
1066
1224
  ...out,
1067
1225
  [ Opcodes.local_set, scope.locals[name + '#type'].idx ]
1068
1226
  ];
1069
1227
 
1228
+ if (typedInput && globals[name]?.metadata?.type != null) return [];
1070
1229
  if (globals[name]) return [
1071
1230
  ...out,
1072
1231
  [ Opcodes.global_set, globals[name + '#type'].idx ]
1073
1232
  ];
1074
1233
 
1075
1234
  // throw new Error('could not find var');
1235
+ return [];
1236
+ };
1237
+
1238
+ const getLastType = scope => {
1239
+ scope.gotLastType = true;
1240
+ return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1241
+ };
1242
+
1243
+ const setLastType = scope => {
1244
+ return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1076
1245
  };
1077
1246
 
1078
1247
  const getNodeType = (scope, node) => {
@@ -1080,6 +1249,8 @@ const getNodeType = (scope, node) => {
1080
1249
  if (node.type === 'Literal') {
1081
1250
  if (node.regex) return TYPES._regexp;
1082
1251
 
1252
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1253
+
1083
1254
  return TYPES[typeof node.value];
1084
1255
  }
1085
1256
 
@@ -1093,6 +1264,27 @@ const getNodeType = (scope, node) => {
1093
1264
 
1094
1265
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1095
1266
  const name = node.callee.name;
1267
+ if (!name) {
1268
+ // iife
1269
+ if (scope.locals['#last_type']) return getLastType(scope);
1270
+
1271
+ // presume
1272
+ // todo: warn here?
1273
+ return TYPES.number;
1274
+ }
1275
+
1276
+ if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
1277
+ if (builtinFuncs[name + '$constructor'].typedReturns) {
1278
+ if (scope.locals['#last_type']) return getLastType(scope);
1279
+
1280
+ // presume
1281
+ // todo: warn here?
1282
+ return TYPES.number;
1283
+ }
1284
+
1285
+ return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
1286
+ }
1287
+
1096
1288
  const func = funcs.find(x => x.name === name);
1097
1289
 
1098
1290
  if (func) {
@@ -1100,10 +1292,27 @@ const getNodeType = (scope, node) => {
1100
1292
  if (func.returnType) return func.returnType;
1101
1293
  }
1102
1294
 
1103
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1295
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1104
1296
  if (internalConstrs[name]) return internalConstrs[name].type;
1105
1297
 
1106
- if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1298
+ // check if this is a prototype function
1299
+ // if so and there is only one impl (eg charCodeAt)
1300
+ // use that return type as that is the only possibility
1301
+ // (if non-matching type it would error out)
1302
+ if (name.startsWith('__')) {
1303
+ const spl = name.slice(2).split('_');
1304
+
1305
+ const func = spl[spl.length - 1];
1306
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
1307
+ if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1308
+ }
1309
+
1310
+ if (name.startsWith('__Porffor_wasm_')) {
1311
+ // todo: return undefined for non-returning ops
1312
+ return TYPES.number;
1313
+ }
1314
+
1315
+ if (scope.locals['#last_type']) return getLastType(scope);
1107
1316
 
1108
1317
  // presume
1109
1318
  // todo: warn here?
@@ -1151,6 +1360,15 @@ const getNodeType = (scope, node) => {
1151
1360
 
1152
1361
  if (node.type === 'BinaryExpression') {
1153
1362
  if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1363
+ if (node.operator !== '+') return TYPES.number;
1364
+
1365
+ const knownLeft = knownType(scope, getNodeType(scope, node.left));
1366
+ const knownRight = knownType(scope, getNodeType(scope, node.right));
1367
+
1368
+ // todo: this should be dynamic but for now only static
1369
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1370
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) return TYPES._bytestring;
1371
+
1154
1372
  return TYPES.number;
1155
1373
 
1156
1374
  // todo: string concat types
@@ -1175,7 +1393,7 @@ const getNodeType = (scope, node) => {
1175
1393
  if (node.operator === '!') return TYPES.boolean;
1176
1394
  if (node.operator === 'void') return TYPES.undefined;
1177
1395
  if (node.operator === 'delete') return TYPES.boolean;
1178
- if (node.operator === 'typeof') return TYPES.string;
1396
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
1179
1397
 
1180
1398
  return TYPES.number;
1181
1399
  }
@@ -1184,11 +1402,23 @@ const getNodeType = (scope, node) => {
1184
1402
  // hack: if something.length, number type
1185
1403
  if (node.property.name === 'length') return TYPES.number;
1186
1404
 
1187
- // we cannot guess
1405
+ // ts hack
1406
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1407
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._bytestring) return TYPES._bytestring;
1408
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1409
+
1410
+ if (scope.locals['#last_type']) return getLastType(scope);
1411
+
1412
+ // presume
1188
1413
  return TYPES.number;
1189
1414
  }
1190
1415
 
1191
- if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1416
+ if (node.type === 'TaggedTemplateExpression') {
1417
+ // hack
1418
+ if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
1419
+ }
1420
+
1421
+ if (scope.locals['#last_type']) return getLastType(scope);
1192
1422
 
1193
1423
  // presume
1194
1424
  // todo: warn here?
@@ -1204,8 +1434,8 @@ const getNodeType = (scope, node) => {
1204
1434
  const generateLiteral = (scope, decl, global, name) => {
1205
1435
  if (decl.value === null) return number(NULL);
1206
1436
 
1437
+ // hack: just return 1 for regex literals
1207
1438
  if (decl.regex) {
1208
- scope.regex[name] = decl.regex;
1209
1439
  return number(1);
1210
1440
  }
1211
1441
 
@@ -1218,19 +1448,10 @@ const generateLiteral = (scope, decl, global, name) => {
1218
1448
  return number(decl.value ? 1 : 0);
1219
1449
 
1220
1450
  case 'string':
1221
- const str = decl.value;
1222
- const rawElements = new Array(str.length);
1223
- let j = 0;
1224
- for (let i = 0; i < str.length; i++) {
1225
- rawElements[i] = str.charCodeAt(i);
1226
- }
1227
-
1228
- return makeArray(scope, {
1229
- rawElements
1230
- }, global, name, false, 'i16')[0];
1451
+ return makeString(scope, decl.value, global, name);
1231
1452
 
1232
1453
  default:
1233
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1454
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1234
1455
  }
1235
1456
  };
1236
1457
 
@@ -1239,6 +1460,8 @@ const countLeftover = wasm => {
1239
1460
 
1240
1461
  for (let i = 0; i < wasm.length; i++) {
1241
1462
  const inst = wasm[i];
1463
+ if (inst[0] == null) continue;
1464
+
1242
1465
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1243
1466
  if (inst[0] === Opcodes.if) count--;
1244
1467
  if (inst[1] !== Blocktype.void) count++;
@@ -1247,18 +1470,25 @@ const countLeftover = wasm => {
1247
1470
  if (inst[0] === Opcodes.end) depth--;
1248
1471
 
1249
1472
  if (depth === 0)
1250
- if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1251
- else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
1252
- else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1253
- else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
1473
+ if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1474
+ else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
1475
+ else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const, Opcodes.memory_size].includes(inst[0])) count++;
1476
+ else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1254
1477
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1255
1478
  else if (inst[0] === Opcodes.return) count = 0;
1256
1479
  else if (inst[0] === Opcodes.call) {
1257
1480
  let func = funcs.find(x => x.index === inst[1]);
1258
- if (func) {
1259
- count -= func.params.length;
1260
- } else count--;
1261
- if (func) count += func.returns.length;
1481
+ if (inst[1] === -1) {
1482
+ // todo: count for calling self
1483
+ } else if (!func && inst[1] < importedFuncs.length) {
1484
+ count -= importedFuncs[inst[1]].params;
1485
+ count += importedFuncs[inst[1]].returns;
1486
+ } else {
1487
+ if (func) {
1488
+ count -= func.params.length;
1489
+ } else count--;
1490
+ if (func) count += func.returns.length;
1491
+ }
1262
1492
  } else count--;
1263
1493
 
1264
1494
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1276,7 +1506,7 @@ const disposeLeftover = wasm => {
1276
1506
  const generateExp = (scope, decl) => {
1277
1507
  const expression = decl.expression;
1278
1508
 
1279
- const out = generate(scope, expression);
1509
+ const out = generate(scope, expression, undefined, undefined, true);
1280
1510
  disposeLeftover(out);
1281
1511
 
1282
1512
  return out;
@@ -1334,7 +1564,7 @@ const RTArrayUtil = {
1334
1564
  ]
1335
1565
  };
1336
1566
 
1337
- const generateCall = (scope, decl, _global, _name) => {
1567
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1338
1568
  /* const callee = decl.callee;
1339
1569
  const args = decl.arguments;
1340
1570
 
@@ -1350,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name) => {
1350
1580
  name = func.name;
1351
1581
  }
1352
1582
 
1353
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1583
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1354
1584
  // literal eval hack
1355
- const code = decl.arguments[0].value;
1356
- const parsed = parse(code, []);
1585
+ const code = decl.arguments[0]?.value ?? '';
1586
+
1587
+ let parsed;
1588
+ try {
1589
+ parsed = parse(code, []);
1590
+ } catch (e) {
1591
+ if (e.name === 'SyntaxError') {
1592
+ // throw syntax errors of evals at runtime instead
1593
+ return internalThrow(scope, 'SyntaxError', e.message, true);
1594
+ }
1595
+
1596
+ throw e;
1597
+ }
1357
1598
 
1358
1599
  const out = generate(scope, {
1359
1600
  type: 'BlockStatement',
@@ -1367,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name) => {
1367
1608
  const finalStatement = parsed.body[parsed.body.length - 1];
1368
1609
  out.push(
1369
1610
  ...getNodeType(scope, finalStatement),
1370
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1611
+ ...setLastType(scope)
1371
1612
  );
1372
1613
  } else if (countLeftover(out) === 0) {
1373
1614
  out.push(...number(UNDEFINED));
1374
1615
  out.push(
1375
1616
  ...number(TYPES.undefined, Valtype.i32),
1376
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1617
+ ...setLastType(scope)
1377
1618
  );
1378
1619
  }
1379
1620
 
@@ -1391,18 +1632,20 @@ const generateCall = (scope, decl, _global, _name) => {
1391
1632
  if (name && name.startsWith('__')) {
1392
1633
  const spl = name.slice(2).split('_');
1393
1634
 
1394
- const func = spl[spl.length - 1];
1395
- protoName = func;
1635
+ protoName = spl[spl.length - 1];
1396
1636
 
1397
1637
  target = { ...decl.callee };
1398
1638
  target.name = spl.slice(0, -1).join('_');
1639
+
1640
+ // failed to lookup name, abort
1641
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1399
1642
  }
1400
1643
 
1401
1644
  // literal.func()
1402
1645
  if (!name && decl.callee.type === 'MemberExpression') {
1403
1646
  // megahack for /regex/.func()
1404
- if (decl.callee.object.regex) {
1405
- const funcName = decl.callee.property.name;
1647
+ const funcName = decl.callee.property.name;
1648
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1406
1649
  const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1407
1650
 
1408
1651
  funcIndex[func.name] = func.index;
@@ -1418,12 +1661,11 @@ const generateCall = (scope, decl, _global, _name) => {
1418
1661
  Opcodes.i32_from_u,
1419
1662
 
1420
1663
  ...number(TYPES.boolean, Valtype.i32),
1421
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1664
+ ...setLastType(scope)
1422
1665
  ];
1423
1666
  }
1424
1667
 
1425
- const func = decl.callee.property.name;
1426
- protoName = func;
1668
+ protoName = decl.callee.property.name;
1427
1669
 
1428
1670
  target = decl.callee.object;
1429
1671
  }
@@ -1444,23 +1686,48 @@ const generateCall = (scope, decl, _global, _name) => {
1444
1686
  // }
1445
1687
 
1446
1688
  if (protoName) {
1689
+ const protoBC = {};
1690
+
1691
+ const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1692
+
1693
+ if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
1694
+ for (const x of builtinProtoCands) {
1695
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1696
+ if (type == null) continue;
1697
+
1698
+ protoBC[type] = generateCall(scope, {
1699
+ callee: {
1700
+ type: 'Identifier',
1701
+ name: x
1702
+ },
1703
+ arguments: [ target, ...decl.arguments ],
1704
+ _protoInternalCall: true
1705
+ });
1706
+ }
1707
+ }
1708
+
1447
1709
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1448
- const f = prototypeFuncs[x][protoName];
1449
- if (f) acc[x] = f;
1710
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1450
1711
  return acc;
1451
1712
  }, {});
1452
1713
 
1453
- // no prototype function candidates, ignore
1454
1714
  if (Object.keys(protoCands).length > 0) {
1455
1715
  // use local for cached i32 length as commonly used
1456
1716
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1457
1717
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1458
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1459
1718
 
1460
1719
  // TODO: long-term, prototypes should be their individual separate funcs
1461
1720
 
1721
+ const rawPointer = [
1722
+ ...generate(scope, target),
1723
+ Opcodes.i32_to_u
1724
+ ];
1725
+
1726
+ const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
1727
+ const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
1728
+
1729
+ let allOptUnused = true;
1462
1730
  let lengthI32CacheUsed = false;
1463
- const protoBC = {};
1464
1731
  for (const x in protoCands) {
1465
1732
  const protoFunc = protoCands[x];
1466
1733
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1468,7 +1735,7 @@ const generateCall = (scope, decl, _global, _name) => {
1468
1735
  ...RTArrayUtil.getLength(getPointer),
1469
1736
 
1470
1737
  ...number(TYPES.number, Valtype.i32),
1471
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
1738
+ ...setLastType(scope)
1472
1739
  ];
1473
1740
  continue;
1474
1741
  }
@@ -1478,6 +1745,7 @@ const generateCall = (scope, decl, _global, _name) => {
1478
1745
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1479
1746
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1480
1747
 
1748
+ let optUnused = false;
1481
1749
  const protoOut = protoFunc(getPointer, {
1482
1750
  getCachedI32: () => {
1483
1751
  lengthI32CacheUsed = true;
@@ -1492,23 +1760,30 @@ const generateCall = (scope, decl, _global, _name) => {
1492
1760
  return makeArray(scope, {
1493
1761
  rawElements: new Array(length)
1494
1762
  }, _global, _name, true, itemType);
1763
+ }, () => {
1764
+ optUnused = true;
1765
+ return unusedValue;
1495
1766
  });
1496
1767
 
1768
+ if (!optUnused) allOptUnused = false;
1769
+
1497
1770
  protoBC[x] = [
1498
- [ Opcodes.block, valtypeBinary ],
1771
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1499
1772
  ...protoOut,
1500
1773
 
1501
1774
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1502
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
1775
+ ...setLastType(scope),
1503
1776
  [ Opcodes.end ]
1504
1777
  ];
1505
1778
  }
1506
1779
 
1507
- return [
1508
- ...generate(scope, target),
1780
+ // todo: if some cands use optUnused and some don't, we will probably crash
1509
1781
 
1510
- Opcodes.i32_to_u,
1511
- [ Opcodes.local_set, pointerLocal ],
1782
+ return [
1783
+ ...(usePointerCache ? [
1784
+ ...rawPointer,
1785
+ [ Opcodes.local_set, pointerLocal ],
1786
+ ] : []),
1512
1787
 
1513
1788
  ...(!lengthI32CacheUsed ? [] : [
1514
1789
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1520,13 +1795,22 @@ const generateCall = (scope, decl, _global, _name) => {
1520
1795
 
1521
1796
  // TODO: error better
1522
1797
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1523
- }, valtypeBinary),
1798
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1524
1799
  ];
1525
1800
  }
1801
+
1802
+ if (Object.keys(protoBC).length > 0) {
1803
+ return typeSwitch(scope, getNodeType(scope, target), {
1804
+ ...protoBC,
1805
+
1806
+ // TODO: error better
1807
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1808
+ }, valtypeBinary);
1809
+ }
1526
1810
  }
1527
1811
 
1528
1812
  // TODO: only allows callee as literal
1529
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1813
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1530
1814
 
1531
1815
  let idx = funcIndex[name] ?? importedFuncs[name];
1532
1816
  if (idx === undefined && builtinFuncs[name]) {
@@ -1559,15 +1843,66 @@ const generateCall = (scope, decl, _global, _name) => {
1559
1843
  idx = -1;
1560
1844
  }
1561
1845
 
1846
+ if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1847
+ const wasmOps = {
1848
+ // pointer, align, offset
1849
+ i32_load: { imms: 2, args: [ true ], returns: 1 },
1850
+ // pointer, value, align, offset
1851
+ i32_store: { imms: 2, args: [ true, true ], returns: 0 },
1852
+ // pointer, align, offset
1853
+ i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
1854
+ // pointer, value, align, offset
1855
+ i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
1856
+ // pointer, align, offset
1857
+ i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
1858
+ // pointer, value, align, offset
1859
+ i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
1860
+
1861
+ // pointer, align, offset
1862
+ f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
1863
+ // pointer, value, align, offset
1864
+ f64_store: { imms: 2, args: [ true, false ], returns: 0 },
1865
+
1866
+ // value
1867
+ i32_const: { imms: 1, args: [], returns: 1 },
1868
+
1869
+ // a, b
1870
+ i32_or: { imms: 0, args: [ true, true ], returns: 1 },
1871
+ };
1872
+
1873
+ const opName = name.slice('__Porffor_wasm_'.length);
1874
+
1875
+ if (wasmOps[opName]) {
1876
+ const op = wasmOps[opName];
1877
+
1878
+ const argOut = [];
1879
+ for (let i = 0; i < op.args.length; i++) argOut.push(
1880
+ ...generate(scope, decl.arguments[i]),
1881
+ ...(op.args[i] ? [ Opcodes.i32_to ] : [])
1882
+ );
1883
+
1884
+ // literals only
1885
+ const imms = decl.arguments.slice(op.args.length).map(x => x.value);
1886
+
1887
+ return [
1888
+ ...argOut,
1889
+ [ Opcodes[opName], ...imms ],
1890
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1891
+ ];
1892
+ }
1893
+ }
1894
+
1562
1895
  if (idx === undefined) {
1563
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1564
- return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1896
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1897
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1565
1898
  }
1566
1899
 
1567
1900
  const func = funcs.find(x => x.index === idx);
1568
1901
 
1569
1902
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1570
- const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
1903
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1904
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1905
+ const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1571
1906
 
1572
1907
  let args = decl.arguments;
1573
1908
  if (func && args.length < paramCount) {
@@ -1583,14 +1918,24 @@ const generateCall = (scope, decl, _global, _name) => {
1583
1918
  if (func && func.throws) scope.throws = true;
1584
1919
 
1585
1920
  let out = [];
1586
- for (const arg of args) {
1921
+ for (let i = 0; i < args.length; i++) {
1922
+ const arg = args[i];
1587
1923
  out = out.concat(generate(scope, arg));
1588
- if (userFunc) out = out.concat(getNodeType(scope, arg));
1924
+
1925
+ if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1926
+ out.push(Opcodes.i32_to);
1927
+ }
1928
+
1929
+ if (importedFuncs[name] && name.startsWith('profile')) {
1930
+ out.push(Opcodes.i32_to);
1931
+ }
1932
+
1933
+ if (typedParams) out = out.concat(getNodeType(scope, arg));
1589
1934
  }
1590
1935
 
1591
1936
  out.push([ Opcodes.call, idx ]);
1592
1937
 
1593
- if (!userFunc) {
1938
+ if (!typedReturns) {
1594
1939
  // let type;
1595
1940
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1596
1941
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1600,7 +1945,11 @@ const generateCall = (scope, decl, _global, _name) => {
1600
1945
  // ...number(type, Valtype.i32),
1601
1946
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1602
1947
  // );
1603
- } else out.push([ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]);
1948
+ } else out.push(...setLastType(scope));
1949
+
1950
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1951
+ out.push(Opcodes.i32_from);
1952
+ }
1604
1953
 
1605
1954
  return out;
1606
1955
  };
@@ -1608,8 +1957,21 @@ const generateCall = (scope, decl, _global, _name) => {
1608
1957
  const generateNew = (scope, decl, _global, _name) => {
1609
1958
  // hack: basically treat this as a normal call for builtins for now
1610
1959
  const name = mapName(decl.callee.name);
1960
+
1611
1961
  if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1612
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1962
+
1963
+ if (builtinFuncs[name + '$constructor']) {
1964
+ // custom ...$constructor override builtin func
1965
+ return generateCall(scope, {
1966
+ ...decl,
1967
+ callee: {
1968
+ type: 'Identifier',
1969
+ name: name + '$constructor'
1970
+ }
1971
+ }, _global, _name);
1972
+ }
1973
+
1974
+ if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1613
1975
 
1614
1976
  return generateCall(scope, decl, _global, _name);
1615
1977
  };
@@ -1625,17 +1987,124 @@ const unhackName = name => {
1625
1987
  return name;
1626
1988
  };
1627
1989
 
1990
+ const knownType = (scope, type) => {
1991
+ if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
1992
+ return type[0][1];
1993
+ }
1994
+
1995
+ if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
1996
+ const idx = type[0][1];
1997
+
1998
+ // type idx = var idx + 1
1999
+ const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
2000
+ if (v.metadata?.type != null) return v.metadata.type;
2001
+ }
2002
+
2003
+ return null;
2004
+ };
2005
+
2006
+ const brTable = (input, bc, returns) => {
2007
+ const out = [];
2008
+ const keys = Object.keys(bc);
2009
+ const count = keys.length;
2010
+
2011
+ if (count === 1) {
2012
+ // return [
2013
+ // ...input,
2014
+ // ...bc[keys[0]]
2015
+ // ];
2016
+ return bc[keys[0]];
2017
+ }
2018
+
2019
+ if (count === 2) {
2020
+ // just use if else
2021
+ const other = keys.find(x => x !== 'default');
2022
+ return [
2023
+ ...input,
2024
+ ...number(other, Valtype.i32),
2025
+ [ Opcodes.i32_eq ],
2026
+ [ Opcodes.if, returns ],
2027
+ ...bc[other],
2028
+ [ Opcodes.else ],
2029
+ ...bc.default,
2030
+ [ Opcodes.end ]
2031
+ ];
2032
+ }
2033
+
2034
+ for (let i = 0; i < count; i++) {
2035
+ if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2036
+ else out.push([ Opcodes.block, Blocktype.void ]);
2037
+ }
2038
+
2039
+ const nums = keys.filter(x => +x);
2040
+ const offset = Math.min(...nums);
2041
+ const max = Math.max(...nums);
2042
+
2043
+ const table = [];
2044
+ let br = 1;
2045
+
2046
+ for (let i = offset; i <= max; i++) {
2047
+ // if branch for this num, go to that block
2048
+ if (bc[i]) {
2049
+ table.push(br);
2050
+ br++;
2051
+ continue;
2052
+ }
2053
+
2054
+ // else default
2055
+ table.push(0);
2056
+ }
2057
+
2058
+ out.push(
2059
+ [ Opcodes.block, Blocktype.void ],
2060
+ ...input,
2061
+ ...(offset > 0 ? [
2062
+ ...number(offset, Valtype.i32),
2063
+ [ Opcodes.i32_sub ]
2064
+ ] : []),
2065
+ [ Opcodes.br_table, ...encodeVector(table), 0 ]
2066
+ );
2067
+
2068
+ // if you can guess why we sort the wrong way and then reverse
2069
+ // (instead of just sorting the correct way)
2070
+ // dm me and if you are correct and the first person
2071
+ // I will somehow shout you out or something
2072
+ const orderedBc = keys.sort((a, b) => b - a).reverse();
2073
+
2074
+ br = count - 1;
2075
+ for (const x of orderedBc) {
2076
+ out.push(
2077
+ [ Opcodes.end ],
2078
+ ...bc[x],
2079
+ ...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
2080
+ );
2081
+ br--;
2082
+ }
2083
+
2084
+ return [
2085
+ ...out,
2086
+ [ Opcodes.end, 'br table end' ]
2087
+ ];
2088
+ };
2089
+
1628
2090
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1629
- const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
2091
+ if (!Prefs.bytestring) delete bc[TYPES._bytestring];
2092
+
2093
+ const known = knownType(scope, type);
2094
+ if (known != null) {
2095
+ return bc[known] ?? bc.default;
2096
+ }
1630
2097
 
2098
+ if (Prefs.typeswitchUseBrtable)
2099
+ return brTable(type, bc, returns);
2100
+
2101
+ const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1631
2102
  const out = [
1632
2103
  ...type,
1633
2104
  [ Opcodes.local_set, tmp ],
1634
2105
  [ Opcodes.block, returns ]
1635
2106
  ];
1636
2107
 
1637
- // todo: use br_table?
1638
-
1639
2108
  for (const x in bc) {
1640
2109
  if (x === 'default') continue;
1641
2110
 
@@ -1659,7 +2128,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1659
2128
  return out;
1660
2129
  };
1661
2130
 
1662
- const allocVar = (scope, name, global = false) => {
2131
+ const allocVar = (scope, name, global = false, type = true) => {
1663
2132
  const target = global ? globals : scope.locals;
1664
2133
 
1665
2134
  // already declared
@@ -1673,12 +2142,62 @@ const allocVar = (scope, name, global = false) => {
1673
2142
  let idx = global ? globalInd++ : scope.localInd++;
1674
2143
  target[name] = { idx, type: valtypeBinary };
1675
2144
 
1676
- let typeIdx = global ? globalInd++ : scope.localInd++;
1677
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2145
+ if (type) {
2146
+ let typeIdx = global ? globalInd++ : scope.localInd++;
2147
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2148
+ }
1678
2149
 
1679
2150
  return idx;
1680
2151
  };
1681
2152
 
2153
+ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
2154
+ const target = global ? globals : scope.locals;
2155
+
2156
+ target[name].metadata ??= {};
2157
+ for (const x in metadata) {
2158
+ if (metadata[x] != null) target[name].metadata[x] = metadata[x];
2159
+ }
2160
+ };
2161
+
2162
+ const typeAnnoToPorfType = x => {
2163
+ if (!x) return null;
2164
+ if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
2165
+ if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
2166
+
2167
+ switch (x) {
2168
+ case 'i32':
2169
+ case 'i64':
2170
+ case 'f64':
2171
+ return TYPES.number;
2172
+ }
2173
+
2174
+ return null;
2175
+ };
2176
+
2177
+ const extractTypeAnnotation = decl => {
2178
+ let a = decl;
2179
+ while (a.typeAnnotation) a = a.typeAnnotation;
2180
+
2181
+ let type = null, elementType = null;
2182
+ if (a.typeName) {
2183
+ type = a.typeName.name;
2184
+ } else if (a.type.endsWith('Keyword')) {
2185
+ type = a.type.slice(2, -7).toLowerCase();
2186
+ } else if (a.type === 'TSArrayType') {
2187
+ type = 'array';
2188
+ elementType = extractTypeAnnotation(a.elementType).type;
2189
+ }
2190
+
2191
+ const typeName = type;
2192
+ type = typeAnnoToPorfType(type);
2193
+
2194
+ if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
2195
+
2196
+ // if (decl.name) console.log(decl.name, { type, elementType });
2197
+
2198
+ return { type, typeName, elementType };
2199
+ };
2200
+
1682
2201
  const generateVar = (scope, decl) => {
1683
2202
  let out = [];
1684
2203
 
@@ -1690,6 +2209,8 @@ const generateVar = (scope, decl) => {
1690
2209
  for (const x of decl.declarations) {
1691
2210
  const name = mapName(x.id.name);
1692
2211
 
2212
+ if (!name) return todo(scope, 'destructuring is not supported yet');
2213
+
1693
2214
  if (x.init && isFuncType(x.init.type)) {
1694
2215
  // hack for let a = function () { ... }
1695
2216
  x.init.id = { name };
@@ -1705,7 +2226,13 @@ const generateVar = (scope, decl) => {
1705
2226
  continue; // always ignore
1706
2227
  }
1707
2228
 
1708
- let idx = allocVar(scope, name, global);
2229
+ const typed = typedInput && x.id.typeAnnotation;
2230
+ let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
2231
+
2232
+ if (typed) {
2233
+ addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
2234
+ }
2235
+
1709
2236
  if (x.init) {
1710
2237
  out = out.concat(generate(scope, x.init, global, name));
1711
2238
 
@@ -1720,7 +2247,8 @@ const generateVar = (scope, decl) => {
1720
2247
  return out;
1721
2248
  };
1722
2249
 
1723
- const generateAssign = (scope, decl) => {
2250
+ // todo: optimize this func for valueUnused
2251
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1724
2252
  const { type, name } = decl.left;
1725
2253
 
1726
2254
  if (type === 'ObjectPattern') {
@@ -1738,9 +2266,9 @@ const generateAssign = (scope, decl) => {
1738
2266
  // hack: .length setter
1739
2267
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
1740
2268
  const name = decl.left.object.name;
1741
- const pointer = arrays.get(name);
2269
+ const pointer = scope.arrays?.get(name);
1742
2270
 
1743
- const aotPointer = pointer != null;
2271
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1744
2272
 
1745
2273
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
1746
2274
 
@@ -1765,9 +2293,9 @@ const generateAssign = (scope, decl) => {
1765
2293
  // arr[i]
1766
2294
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1767
2295
  const name = decl.left.object.name;
1768
- const pointer = arrays.get(name);
2296
+ const pointer = scope.arrays?.get(name);
1769
2297
 
1770
- const aotPointer = pointer != null;
2298
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1771
2299
 
1772
2300
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1773
2301
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
@@ -1823,6 +2351,8 @@ const generateAssign = (scope, decl) => {
1823
2351
  ];
1824
2352
  }
1825
2353
 
2354
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2355
+
1826
2356
  const [ local, isGlobal ] = lookupName(scope, name);
1827
2357
 
1828
2358
  if (local === undefined) {
@@ -1869,9 +2399,7 @@ const generateAssign = (scope, decl) => {
1869
2399
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1870
2400
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1871
2401
 
1872
- [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ],
1873
- // hack: type is idx+1
1874
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2402
+ ...setType(scope, name, getLastType(scope))
1875
2403
  ];
1876
2404
  }
1877
2405
 
@@ -1882,9 +2410,7 @@ const generateAssign = (scope, decl) => {
1882
2410
 
1883
2411
  // todo: string concat types
1884
2412
 
1885
- // hack: type is idx+1
1886
- ...number(TYPES.number, Valtype.i32),
1887
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2413
+ ...setType(scope, name, TYPES.number)
1888
2414
  ];
1889
2415
  };
1890
2416
 
@@ -1930,7 +2456,7 @@ const generateUnary = (scope, decl) => {
1930
2456
  return out;
1931
2457
  }
1932
2458
 
1933
- case 'delete':
2459
+ case 'delete': {
1934
2460
  let toReturn = true, toGenerate = true;
1935
2461
 
1936
2462
  if (decl.argument.type === 'Identifier') {
@@ -1952,38 +2478,60 @@ const generateUnary = (scope, decl) => {
1952
2478
 
1953
2479
  out.push(...number(toReturn ? 1 : 0));
1954
2480
  return out;
2481
+ }
2482
+
2483
+ case 'typeof': {
2484
+ let overrideType, toGenerate = true;
2485
+
2486
+ if (decl.argument.type === 'Identifier') {
2487
+ const out = generateIdent(scope, decl.argument);
2488
+
2489
+ // if ReferenceError (undeclared var), ignore and return undefined
2490
+ if (out[1]) {
2491
+ // does not exist (2 ops from throw)
2492
+ overrideType = number(TYPES.undefined, Valtype.i32);
2493
+ toGenerate = false;
2494
+ }
2495
+ }
1955
2496
 
1956
- case 'typeof':
1957
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2497
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2498
+ disposeLeftover(out);
2499
+
2500
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
1958
2501
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
1959
2502
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
1960
2503
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
1961
2504
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
1962
2505
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
1963
2506
 
2507
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2508
+
1964
2509
  // object and internal types
1965
2510
  default: makeString(scope, 'object', false, '#typeof_result'),
1966
- });
2511
+ }));
2512
+
2513
+ return out;
2514
+ }
1967
2515
 
1968
2516
  default:
1969
- return todo(`unary operator ${decl.operator} not implemented yet`);
2517
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
1970
2518
  }
1971
2519
  };
1972
2520
 
1973
- const generateUpdate = (scope, decl) => {
2521
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
1974
2522
  const { name } = decl.argument;
1975
2523
 
1976
2524
  const [ local, isGlobal ] = lookupName(scope, name);
1977
2525
 
1978
2526
  if (local === undefined) {
1979
- return todo(`update expression with undefined variable`);
2527
+ return todo(scope, `update expression with undefined variable`, true);
1980
2528
  }
1981
2529
 
1982
2530
  const idx = local.idx;
1983
2531
  const out = [];
1984
2532
 
1985
2533
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
1986
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2534
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
1987
2535
 
1988
2536
  switch (decl.operator) {
1989
2537
  case '++':
@@ -1996,7 +2544,7 @@ const generateUpdate = (scope, decl) => {
1996
2544
  }
1997
2545
 
1998
2546
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
1999
- if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2547
+ if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2000
2548
 
2001
2549
  return out;
2002
2550
  };
@@ -2036,7 +2584,7 @@ const generateConditional = (scope, decl) => {
2036
2584
  // note type
2037
2585
  out.push(
2038
2586
  ...getNodeType(scope, decl.consequent),
2039
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2587
+ ...setLastType(scope)
2040
2588
  );
2041
2589
 
2042
2590
  out.push([ Opcodes.else ]);
@@ -2045,7 +2593,7 @@ const generateConditional = (scope, decl) => {
2045
2593
  // note type
2046
2594
  out.push(
2047
2595
  ...getNodeType(scope, decl.alternate),
2048
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2596
+ ...setLastType(scope)
2049
2597
  );
2050
2598
 
2051
2599
  out.push([ Opcodes.end ]);
@@ -2059,15 +2607,17 @@ const generateFor = (scope, decl) => {
2059
2607
  const out = [];
2060
2608
 
2061
2609
  if (decl.init) {
2062
- out.push(...generate(scope, decl.init));
2610
+ out.push(...generate(scope, decl.init, false, undefined, true));
2063
2611
  disposeLeftover(out);
2064
2612
  }
2065
2613
 
2066
2614
  out.push([ Opcodes.loop, Blocktype.void ]);
2067
2615
  depth.push('for');
2068
2616
 
2069
- out.push(...generate(scope, decl.test));
2070
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2617
+ if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
2618
+ else out.push(...number(1, Valtype.i32));
2619
+
2620
+ out.push([ Opcodes.if, Blocktype.void ]);
2071
2621
  depth.push('if');
2072
2622
 
2073
2623
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2075,8 +2625,7 @@ const generateFor = (scope, decl) => {
2075
2625
  out.push(...generate(scope, decl.body));
2076
2626
  out.push([ Opcodes.end ]);
2077
2627
 
2078
- out.push(...generate(scope, decl.update));
2079
- depth.pop();
2628
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2080
2629
 
2081
2630
  out.push([ Opcodes.br, 1 ]);
2082
2631
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2104,6 +2653,36 @@ const generateWhile = (scope, decl) => {
2104
2653
  return out;
2105
2654
  };
2106
2655
 
2656
+ const generateDoWhile = (scope, decl) => {
2657
+ const out = [];
2658
+
2659
+ out.push([ Opcodes.loop, Blocktype.void ]);
2660
+ depth.push('dowhile');
2661
+
2662
+ // block for break (includes all)
2663
+ out.push([ Opcodes.block, Blocktype.void ]);
2664
+ depth.push('block');
2665
+
2666
+ // block for continue
2667
+ // includes body but not test+loop so we can exit body at anytime
2668
+ // and still test+loop after
2669
+ out.push([ Opcodes.block, Blocktype.void ]);
2670
+ depth.push('block');
2671
+
2672
+ out.push(...generate(scope, decl.body));
2673
+
2674
+ out.push([ Opcodes.end ]);
2675
+ depth.pop();
2676
+
2677
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2678
+ out.push([ Opcodes.br_if, 1 ]);
2679
+
2680
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2681
+ depth.pop(); depth.pop();
2682
+
2683
+ return out;
2684
+ };
2685
+
2107
2686
  const generateForOf = (scope, decl) => {
2108
2687
  const out = [];
2109
2688
 
@@ -2133,8 +2712,17 @@ const generateForOf = (scope, decl) => {
2133
2712
  // setup local for left
2134
2713
  generate(scope, decl.left);
2135
2714
 
2136
- const leftName = decl.left.declarations[0].id.name;
2715
+ let leftName = decl.left.declarations?.[0]?.id?.name;
2716
+ if (!leftName && decl.left.name) {
2717
+ leftName = decl.left.name;
2718
+
2719
+ generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2720
+ }
2721
+
2722
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2723
+
2137
2724
  const [ local, isGlobal ] = lookupName(scope, leftName);
2725
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2138
2726
 
2139
2727
  depth.push('block');
2140
2728
  depth.push('block');
@@ -2142,13 +2730,15 @@ const generateForOf = (scope, decl) => {
2142
2730
  // // todo: we should only do this for strings but we don't know at compile-time :(
2143
2731
  // hack: this is naughty and will break things!
2144
2732
  let newOut = number(0, Valtype.f64), newPointer = -1;
2145
- if (pages.hasString) {
2733
+ if (pages.hasAnyString) {
2734
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2146
2735
  0, [ newOut, newPointer ] = makeArray(scope, {
2147
2736
  rawElements: new Array(1)
2148
2737
  }, isGlobal, leftName, true, 'i16');
2149
2738
  }
2150
2739
 
2151
2740
  // set type for local
2741
+ // todo: optimize away counter and use end pointer
2152
2742
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2153
2743
  [TYPES._array]: [
2154
2744
  ...setType(scope, leftName, TYPES.number),
@@ -2233,6 +2823,56 @@ const generateForOf = (scope, decl) => {
2233
2823
  [ Opcodes.end ],
2234
2824
  [ Opcodes.end ]
2235
2825
  ],
2826
+ [TYPES._bytestring]: [
2827
+ ...setType(scope, leftName, TYPES._bytestring),
2828
+
2829
+ [ Opcodes.loop, Blocktype.void ],
2830
+
2831
+ // setup new/out array
2832
+ ...newOut,
2833
+ [ Opcodes.drop ],
2834
+
2835
+ ...number(0, Valtype.i32), // base 0 for store after
2836
+
2837
+ // load current string ind {arg}
2838
+ [ Opcodes.local_get, pointer ],
2839
+ [ Opcodes.local_get, counter ],
2840
+ [ Opcodes.i32_add ],
2841
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2842
+
2843
+ // store to new string ind 0
2844
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2845
+
2846
+ // return new string (page)
2847
+ ...number(newPointer),
2848
+
2849
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2850
+
2851
+ [ Opcodes.block, Blocktype.void ],
2852
+ [ Opcodes.block, Blocktype.void ],
2853
+ ...generate(scope, decl.body),
2854
+ [ Opcodes.end ],
2855
+
2856
+ // increment iter pointer
2857
+ // [ Opcodes.local_get, pointer ],
2858
+ // ...number(1, Valtype.i32),
2859
+ // [ Opcodes.i32_add ],
2860
+ // [ Opcodes.local_set, pointer ],
2861
+
2862
+ // increment counter by 1
2863
+ [ Opcodes.local_get, counter ],
2864
+ ...number(1, Valtype.i32),
2865
+ [ Opcodes.i32_add ],
2866
+ [ Opcodes.local_tee, counter ],
2867
+
2868
+ // loop if counter != length
2869
+ [ Opcodes.local_get, length ],
2870
+ [ Opcodes.i32_ne ],
2871
+ [ Opcodes.br_if, 1 ],
2872
+
2873
+ [ Opcodes.end ],
2874
+ [ Opcodes.end ]
2875
+ ],
2236
2876
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2237
2877
  }, Blocktype.void));
2238
2878
 
@@ -2243,28 +2883,65 @@ const generateForOf = (scope, decl) => {
2243
2883
  return out;
2244
2884
  };
2245
2885
 
2886
+ // find the nearest loop in depth map by type
2246
2887
  const getNearestLoop = () => {
2247
2888
  for (let i = depth.length - 1; i >= 0; i--) {
2248
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2889
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2249
2890
  }
2250
2891
 
2251
2892
  return -1;
2252
2893
  };
2253
2894
 
2254
2895
  const generateBreak = (scope, decl) => {
2255
- const nearestLoop = depth.length - getNearestLoop();
2896
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2897
+ const type = depth[target];
2898
+
2899
+ // different loop types have different branch offsets
2900
+ // as they have different wasm block/loop/if structures
2901
+ // we need to use the right offset by type to branch to the one we want
2902
+ // for a break: exit the loop without executing anything else inside it
2903
+ const offset = ({
2904
+ for: 2, // loop > if (wanted branch) > block (we are here)
2905
+ while: 2, // loop > if (wanted branch) (we are here)
2906
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
2907
+ forof: 2, // loop > block (wanted branch) > block (we are here)
2908
+ if: 1 // break inside if, branch 0 to skip the rest of the if
2909
+ })[type];
2910
+
2256
2911
  return [
2257
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2912
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2258
2913
  ];
2259
2914
  };
2260
2915
 
2261
2916
  const generateContinue = (scope, decl) => {
2262
- const nearestLoop = depth.length - getNearestLoop();
2917
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2918
+ const type = depth[target];
2919
+
2920
+ // different loop types have different branch offsets
2921
+ // as they have different wasm block/loop/if structures
2922
+ // we need to use the right offset by type to branch to the one we want
2923
+ // for a continue: do test for the loop, and then loop depending on that success
2924
+ const offset = ({
2925
+ for: 3, // loop (wanted branch) > if > block (we are here)
2926
+ while: 1, // loop (wanted branch) > if (we are here)
2927
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
2928
+ forof: 3 // loop > block > block (wanted branch) (we are here)
2929
+ })[type];
2930
+
2263
2931
  return [
2264
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2932
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2265
2933
  ];
2266
2934
  };
2267
2935
 
2936
+ const generateLabel = (scope, decl) => {
2937
+ scope.labels ??= new Map();
2938
+
2939
+ const name = decl.label.name;
2940
+ scope.labels.set(name, depth.length);
2941
+
2942
+ return generate(scope, decl.body);
2943
+ };
2944
+
2268
2945
  const generateThrow = (scope, decl) => {
2269
2946
  scope.throws = true;
2270
2947
 
@@ -2273,7 +2950,7 @@ const generateThrow = (scope, decl) => {
2273
2950
  // hack: throw new X("...") -> throw "..."
2274
2951
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2275
2952
  constructor = decl.argument.callee.name;
2276
- message = decl.argument.arguments[0].value;
2953
+ message = decl.argument.arguments[0]?.value ?? '';
2277
2954
  }
2278
2955
 
2279
2956
  if (tags.length === 0) tags.push({
@@ -2285,6 +2962,9 @@ const generateThrow = (scope, decl) => {
2285
2962
  let exceptId = exceptions.push({ constructor, message }) - 1;
2286
2963
  let tagIdx = tags[0].idx;
2287
2964
 
2965
+ scope.exceptions ??= [];
2966
+ scope.exceptions.push(exceptId);
2967
+
2288
2968
  // todo: write a description of how this works lol
2289
2969
 
2290
2970
  return [
@@ -2294,7 +2974,7 @@ const generateThrow = (scope, decl) => {
2294
2974
  };
2295
2975
 
2296
2976
  const generateTry = (scope, decl) => {
2297
- if (decl.finalizer) return todo('try finally not implemented yet');
2977
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2298
2978
 
2299
2979
  const out = [];
2300
2980
 
@@ -2325,29 +3005,35 @@ const generateAssignPat = (scope, decl) => {
2325
3005
  // TODO
2326
3006
  // if identifier declared, use that
2327
3007
  // else, use default (right)
2328
- return todo('assignment pattern (optional arg)');
3008
+ return todo(scope, 'assignment pattern (optional arg)');
2329
3009
  };
2330
3010
 
2331
3011
  let pages = new Map();
2332
- const allocPage = (reason, type) => {
3012
+ const allocPage = (scope, reason, type) => {
2333
3013
  if (pages.has(reason)) return pages.get(reason).ind;
2334
3014
 
2335
3015
  if (reason.startsWith('array:')) pages.hasArray = true;
2336
3016
  if (reason.startsWith('string:')) pages.hasString = true;
3017
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
3018
+ if (reason.includes('string:')) pages.hasAnyString = true;
2337
3019
 
2338
3020
  const ind = pages.size;
2339
3021
  pages.set(reason, { ind, type });
2340
3022
 
2341
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
3023
+ scope.pages ??= new Map();
3024
+ scope.pages.set(reason, { ind, type });
3025
+
3026
+ if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2342
3027
 
2343
3028
  return ind;
2344
3029
  };
2345
3030
 
3031
+ // todo: add scope.pages
2346
3032
  const freePage = reason => {
2347
3033
  const { ind } = pages.get(reason);
2348
3034
  pages.delete(reason);
2349
3035
 
2350
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
3036
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2351
3037
 
2352
3038
  return ind;
2353
3039
  };
@@ -2367,38 +3053,51 @@ const StoreOps = {
2367
3053
  f64: Opcodes.f64_store,
2368
3054
 
2369
3055
  // expects i32 input!
2370
- i16: Opcodes.i32_store16
3056
+ i8: Opcodes.i32_store8,
3057
+ i16: Opcodes.i32_store16,
2371
3058
  };
2372
3059
 
2373
3060
  let data = [];
2374
3061
 
2375
- const compileBytes = (val, itemType, signed = true) => {
3062
+ const compileBytes = (val, itemType) => {
2376
3063
  // todo: this is a mess and needs confirming / ????
2377
3064
  switch (itemType) {
2378
3065
  case 'i8': return [ val % 256 ];
2379
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2380
-
2381
- case 'i32':
2382
- case 'i64':
2383
- return enforceFourBytes(signedLEB128(val));
3066
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3067
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3068
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
3069
+ // todo: i64
2384
3070
 
2385
3071
  case 'f64': return ieee754_binary64(val);
2386
3072
  }
2387
3073
  };
2388
3074
 
3075
+ const getAllocType = itemType => {
3076
+ switch (itemType) {
3077
+ case 'i8': return 'bytestring';
3078
+ case 'i16': return 'string';
3079
+
3080
+ default: return 'array';
3081
+ }
3082
+ };
3083
+
2389
3084
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2390
3085
  const out = [];
2391
3086
 
3087
+ scope.arrays ??= new Map();
3088
+
2392
3089
  let firstAssign = false;
2393
- if (!arrays.has(name) || name === '$undeclared') {
3090
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2394
3091
  firstAssign = true;
2395
3092
 
2396
3093
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2397
3094
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2398
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
3095
+
3096
+ if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${scope.name} | ${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3097
+ else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2399
3098
  }
2400
3099
 
2401
- const pointer = arrays.get(name);
3100
+ const pointer = scope.arrays.get(name);
2402
3101
 
2403
3102
  const useRawElements = !!decl.rawElements;
2404
3103
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2406,19 +3105,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2406
3105
  const valtype = itemTypeToValtype[itemType];
2407
3106
  const length = elements.length;
2408
3107
 
2409
- if (firstAssign && useRawElements) {
2410
- let bytes = compileBytes(length, 'i32');
3108
+ if (firstAssign && useRawElements && !Prefs.noData) {
3109
+ // if length is 0 memory/data will just be 0000... anyway
3110
+ if (length !== 0) {
3111
+ let bytes = compileBytes(length, 'i32');
2411
3112
 
2412
- if (!initEmpty) for (let i = 0; i < length; i++) {
2413
- if (elements[i] == null) continue;
3113
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3114
+ if (elements[i] == null) continue;
2414
3115
 
2415
- bytes.push(...compileBytes(elements[i], itemType));
2416
- }
3116
+ bytes.push(...compileBytes(elements[i], itemType));
3117
+ }
2417
3118
 
2418
- data.push({
2419
- offset: pointer,
2420
- bytes
2421
- });
3119
+ const ind = data.push({
3120
+ offset: pointer,
3121
+ bytes
3122
+ }) - 1;
3123
+
3124
+ scope.data ??= [];
3125
+ scope.data.push(ind);
3126
+ }
2422
3127
 
2423
3128
  // local value as pointer
2424
3129
  out.push(...number(pointer));
@@ -2441,7 +3146,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2441
3146
  out.push(
2442
3147
  ...number(0, Valtype.i32),
2443
3148
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2444
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3149
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2445
3150
  );
2446
3151
  }
2447
3152
 
@@ -2451,31 +3156,56 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2451
3156
  return [ out, pointer ];
2452
3157
  };
2453
3158
 
2454
- const makeString = (scope, str, global = false, name = '$undeclared') => {
3159
+ const byteStringable = str => {
3160
+ if (!Prefs.bytestring) return false;
3161
+
3162
+ for (let i = 0; i < str.length; i++) {
3163
+ if (str.charCodeAt(i) > 0xFF) return false;
3164
+ }
3165
+
3166
+ return true;
3167
+ };
3168
+
3169
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2455
3170
  const rawElements = new Array(str.length);
3171
+ let byteStringable = Prefs.bytestring;
2456
3172
  for (let i = 0; i < str.length; i++) {
2457
- rawElements[i] = str.charCodeAt(i);
3173
+ const c = str.charCodeAt(i);
3174
+ rawElements[i] = c;
3175
+
3176
+ if (byteStringable && c > 0xFF) byteStringable = false;
2458
3177
  }
2459
3178
 
3179
+ if (byteStringable && forceBytestring === false) byteStringable = false;
3180
+
2460
3181
  return makeArray(scope, {
2461
3182
  rawElements
2462
- }, global, name, false, 'i16')[0];
3183
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2463
3184
  };
2464
3185
 
2465
- let arrays = new Map();
2466
3186
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2467
3187
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2468
3188
  };
2469
3189
 
2470
3190
  export const generateMember = (scope, decl, _global, _name) => {
2471
3191
  const name = decl.object.name;
2472
- const pointer = arrays.get(name);
3192
+ const pointer = scope.arrays?.get(name);
2473
3193
 
2474
- const aotPointer = pointer != null;
3194
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2475
3195
 
2476
3196
  // hack: .length
2477
3197
  if (decl.property.name === 'length') {
2478
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3198
+ const func = funcs.find(x => x.name === name);
3199
+ if (func) {
3200
+ const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3201
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3202
+ return number(typedParams ? func.params.length / 2 : func.params.length);
3203
+ }
3204
+
3205
+ if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3206
+ if (importedFuncs[name]) return number(importedFuncs[name].params);
3207
+ if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3208
+
2479
3209
  return [
2480
3210
  ...(aotPointer ? number(0, Valtype.i32) : [
2481
3211
  ...generate(scope, decl.object),
@@ -2487,10 +3217,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2487
3217
  ];
2488
3218
  }
2489
3219
 
3220
+ const object = generate(scope, decl.object);
3221
+ const property = generate(scope, decl.property);
3222
+
2490
3223
  // // todo: we should only do this for strings but we don't know at compile-time :(
2491
3224
  // hack: this is naughty and will break things!
2492
- let newOut = number(0, Valtype.f64), newPointer = -1;
2493
- if (pages.hasString) {
3225
+ let newOut = number(0, valtypeBinary), newPointer = -1;
3226
+ if (pages.hasAnyString) {
2494
3227
  0, [ newOut, newPointer ] = makeArray(scope, {
2495
3228
  rawElements: new Array(1)
2496
3229
  }, _global, _name, true, 'i16');
@@ -2499,7 +3232,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2499
3232
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2500
3233
  [TYPES._array]: [
2501
3234
  // get index as valtype
2502
- ...generate(scope, decl.property),
3235
+ ...property,
2503
3236
 
2504
3237
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2505
3238
  Opcodes.i32_to_u,
@@ -2507,7 +3240,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2507
3240
  [ Opcodes.i32_mul ],
2508
3241
 
2509
3242
  ...(aotPointer ? [] : [
2510
- ...generate(scope, decl.object),
3243
+ ...object,
2511
3244
  Opcodes.i32_to_u,
2512
3245
  [ Opcodes.i32_add ]
2513
3246
  ]),
@@ -2516,7 +3249,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2516
3249
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2517
3250
 
2518
3251
  ...number(TYPES.number, Valtype.i32),
2519
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
3252
+ ...setLastType(scope)
2520
3253
  ],
2521
3254
 
2522
3255
  [TYPES.string]: [
@@ -2526,14 +3259,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2526
3259
 
2527
3260
  ...number(0, Valtype.i32), // base 0 for store later
2528
3261
 
2529
- ...generate(scope, decl.property),
2530
-
3262
+ ...property,
2531
3263
  Opcodes.i32_to_u,
3264
+
2532
3265
  ...number(ValtypeSize.i16, Valtype.i32),
2533
3266
  [ Opcodes.i32_mul ],
2534
3267
 
2535
3268
  ...(aotPointer ? [] : [
2536
- ...generate(scope, decl.object),
3269
+ ...object,
2537
3270
  Opcodes.i32_to_u,
2538
3271
  [ Opcodes.i32_add ]
2539
3272
  ]),
@@ -2548,10 +3281,38 @@ export const generateMember = (scope, decl, _global, _name) => {
2548
3281
  ...number(newPointer),
2549
3282
 
2550
3283
  ...number(TYPES.string, Valtype.i32),
2551
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
3284
+ ...setLastType(scope)
3285
+ ],
3286
+ [TYPES._bytestring]: [
3287
+ // setup new/out array
3288
+ ...newOut,
3289
+ [ Opcodes.drop ],
3290
+
3291
+ ...number(0, Valtype.i32), // base 0 for store later
3292
+
3293
+ ...property,
3294
+ Opcodes.i32_to_u,
3295
+
3296
+ ...(aotPointer ? [] : [
3297
+ ...object,
3298
+ Opcodes.i32_to_u,
3299
+ [ Opcodes.i32_add ]
3300
+ ]),
3301
+
3302
+ // load current string ind {arg}
3303
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3304
+
3305
+ // store to new string ind 0
3306
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3307
+
3308
+ // return new string (page)
3309
+ ...number(newPointer),
3310
+
3311
+ ...number(TYPES._bytestring, Valtype.i32),
3312
+ ...setLastType(scope)
2552
3313
  ],
2553
3314
 
2554
- default: [ [ Opcodes.unreachable ] ]
3315
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2555
3316
  });
2556
3317
  };
2557
3318
 
@@ -2561,25 +3322,36 @@ const objectHack = node => {
2561
3322
  if (!node) return node;
2562
3323
 
2563
3324
  if (node.type === 'MemberExpression') {
2564
- if (node.computed || node.optional) return node;
3325
+ const out = (() => {
3326
+ if (node.computed || node.optional) return;
2565
3327
 
2566
- let objectName = node.object.name;
3328
+ let objectName = node.object.name;
2567
3329
 
2568
- // if object is not identifier or another member exp, give up
2569
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3330
+ // if object is not identifier or another member exp, give up
3331
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3332
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2570
3333
 
2571
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
3334
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2572
3335
 
2573
- // if .length, give up (hack within a hack!)
2574
- if (node.property.name === 'length') return node;
3336
+ // if .length, give up (hack within a hack!)
3337
+ if (node.property.name === 'length') {
3338
+ node.object = objectHack(node.object);
3339
+ return;
3340
+ }
2575
3341
 
2576
- const name = '__' + objectName + '_' + node.property.name;
2577
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3342
+ // no object name, give up
3343
+ if (!objectName) return;
2578
3344
 
2579
- return {
2580
- type: 'Identifier',
2581
- name
2582
- };
3345
+ const name = '__' + objectName + '_' + node.property.name;
3346
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3347
+
3348
+ return {
3349
+ type: 'Identifier',
3350
+ name
3351
+ };
3352
+ })();
3353
+
3354
+ if (out) return out;
2583
3355
  }
2584
3356
 
2585
3357
  for (const x in node) {
@@ -2593,11 +3365,11 @@ const objectHack = node => {
2593
3365
  };
2594
3366
 
2595
3367
  const generateFunc = (scope, decl) => {
2596
- if (decl.async) return todo('async functions are not supported');
2597
- if (decl.generator) return todo('generator functions are not supported');
3368
+ if (decl.async) return todo(scope, 'async functions are not supported');
3369
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
2598
3370
 
2599
3371
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2600
- const params = decl.params?.map(x => x.name) ?? [];
3372
+ const params = decl.params ?? [];
2601
3373
 
2602
3374
  // const innerScope = { ...scope };
2603
3375
  // TODO: share scope/locals between !!!
@@ -2610,8 +3382,17 @@ const generateFunc = (scope, decl) => {
2610
3382
  name
2611
3383
  };
2612
3384
 
3385
+ if (typedInput && decl.returnType) {
3386
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3387
+ innerScope.returns = [ valtypeBinary ];
3388
+ }
3389
+
2613
3390
  for (let i = 0; i < params.length; i++) {
2614
- allocVar(innerScope, params[i], false);
3391
+ allocVar(innerScope, params[i].name, false);
3392
+
3393
+ if (typedInput && params[i].typeAnnotation) {
3394
+ addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
3395
+ }
2615
3396
  }
2616
3397
 
2617
3398
  let body = objectHack(decl.body);
@@ -2627,13 +3408,13 @@ const generateFunc = (scope, decl) => {
2627
3408
  const func = {
2628
3409
  name,
2629
3410
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2630
- returns: innerScope.returns,
2631
- locals: innerScope.locals,
2632
- throws: innerScope.throws,
2633
- index: currentFuncIndex++
3411
+ index: currentFuncIndex++,
3412
+ ...innerScope
2634
3413
  };
2635
3414
  funcIndex[name] = func.index;
2636
3415
 
3416
+ if (name === 'main') func.gotLastType = true;
3417
+
2637
3418
  // quick hack fixes
2638
3419
  for (const inst of wasm) {
2639
3420
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -2650,117 +3431,6 @@ const generateFunc = (scope, decl) => {
2650
3431
  );
2651
3432
  }
2652
3433
 
2653
- // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
2654
- let offset = 0, vecParams = 0;
2655
- for (let i = 0; i < params.length; i++) {
2656
- const name = params[i];
2657
- const local = func.locals[name];
2658
- if (local.type === Valtype.v128) {
2659
- vecParams++;
2660
-
2661
- /* wasm.unshift( // add v128 load for param
2662
- [ Opcodes.i32_const, 0 ],
2663
- [ ...Opcodes.v128_load, 0, i * 16 ],
2664
- [ Opcodes.local_set, local.idx ]
2665
- ); */
2666
-
2667
- // using params and replace_lane is noticably faster than just loading from memory (above) somehow
2668
-
2669
- // extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
2670
- const { vecType } = local;
2671
- let [ type, lanes ] = vecType.split('x');
2672
- if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
2673
-
2674
- lanes = parseInt(lanes);
2675
- type = Valtype[type];
2676
-
2677
- const name = params[i]; // get original param name
2678
-
2679
- func.params.splice(offset, 1, ...new Array(lanes).fill(type)); // add new params of {type}, {lanes} times
2680
-
2681
- // update index of original local
2682
- // delete func.locals[name];
2683
-
2684
- // add new locals for params
2685
- for (let j = 0; j < lanes; j++) {
2686
- func.locals[name + j] = { idx: offset + j, type, vecParamAutogen: true };
2687
- }
2688
-
2689
- // prepend wasm to generate expected v128 locals
2690
- wasm.splice(i * 2 + offset * 2, 0,
2691
- ...i32x4(0, 0, 0, 0),
2692
- ...new Array(lanes).fill(0).flatMap((_, j) => [
2693
- [ Opcodes.local_get, offset + j ],
2694
- [ ...Opcodes[vecType + '_replace_lane'], j ]
2695
- ]),
2696
- [ Opcodes.local_set, i ]
2697
- );
2698
-
2699
- offset += lanes;
2700
-
2701
- // note: wrapping is disabled for now due to perf/dx concerns (so this will never run)
2702
- /* if (!func.name.startsWith('#')) func.name = '##' + func.name;
2703
-
2704
- // add vec type index to hash name prefix for wrapper to know how to wrap
2705
- const vecTypeIdx = [ 'i8x16', 'i16x8', 'i32x4', 'i64x2', 'f32x4', 'f64x2' ].indexOf(local.vecType);
2706
- const secondHash = func.name.slice(1).indexOf('#');
2707
- func.name = '#' + func.name.slice(1, secondHash) + vecTypeIdx + func.name.slice(secondHash); */
2708
- }
2709
- }
2710
-
2711
- if (offset !== 0) {
2712
- // bump local indexes for all other locals after
2713
- for (const x in func.locals) {
2714
- const local = func.locals[x];
2715
- if (!local.vecParamAutogen) local.idx += offset;
2716
- }
2717
-
2718
- // bump local indexes in wasm local.get/set
2719
- for (let j = 0; j < wasm.length; j++) {
2720
- const inst = wasm[j];
2721
- if (j < offset * 2 + vecParams * 2) {
2722
- if (inst[0] === Opcodes.local_set) inst[1] += offset;
2723
- continue;
2724
- }
2725
-
2726
- if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set) inst[1] += offset;
2727
- }
2728
- }
2729
-
2730
- // change v128 return into many <type> instead as unsupported return valtype
2731
- const lastReturnLocal = wasm.length > 2 && wasm[wasm.length - 1][0] === Opcodes.return && Object.values(func.locals).find(x => x.idx === wasm[wasm.length - 2][1]);
2732
- if (lastReturnLocal && lastReturnLocal.type === Valtype.v128) {
2733
- const name = Object.keys(func.locals)[Object.values(func.locals).indexOf(lastReturnLocal)];
2734
- // extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
2735
- const { vecType } = lastReturnLocal;
2736
- let [ type, lanes ] = vecType.split('x');
2737
- if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
2738
-
2739
- lanes = parseInt(lanes);
2740
- type = Valtype[type];
2741
-
2742
- const vecIdx = lastReturnLocal.idx;
2743
-
2744
- const lastIdx = Math.max(0, ...Object.values(func.locals).map(x => x.idx));
2745
- const tmpIdx = [];
2746
- for (let i = 0; i < lanes; i++) {
2747
- const idx = lastIdx + i + 1;
2748
- tmpIdx.push(idx);
2749
- func.locals[name + i] = { idx, type, vecReturnAutogen: true };
2750
- }
2751
-
2752
- wasm.splice(wasm.length - 1, 1,
2753
- ...new Array(lanes).fill(0).flatMap((_, i) => [
2754
- i === 0 ? null : [ Opcodes.local_get, vecIdx ],
2755
- [ ...Opcodes[vecType + '_extract_lane'], i ],
2756
- [ Opcodes.local_set, tmpIdx[i] ],
2757
- ].filter(x => x !== null)),
2758
- ...new Array(lanes).fill(0).map((_, i) => [ Opcodes.local_get, tmpIdx[i]])
2759
- );
2760
-
2761
- func.returns = new Array(lanes).fill(type);
2762
- }
2763
-
2764
3434
  func.wasm = wasm;
2765
3435
 
2766
3436
  funcs.push(func);
@@ -2796,7 +3466,7 @@ const internalConstrs = {
2796
3466
 
2797
3467
  // todo: check in wasm instead of here
2798
3468
  const literalValue = arg.value ?? 0;
2799
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3469
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
2800
3470
 
2801
3471
  return [
2802
3472
  ...number(0, Valtype.i32),
@@ -2807,7 +3477,8 @@ const internalConstrs = {
2807
3477
  ...number(pointer)
2808
3478
  ];
2809
3479
  },
2810
- type: TYPES._array
3480
+ type: TYPES._array,
3481
+ length: 1
2811
3482
  },
2812
3483
 
2813
3484
  __Array_of: {
@@ -2819,7 +3490,131 @@ const internalConstrs = {
2819
3490
  }, global, name);
2820
3491
  },
2821
3492
  type: TYPES._array,
3493
+ notConstr: true,
3494
+ length: 0
3495
+ },
3496
+
3497
+ __Porffor_fastOr: {
3498
+ generate: (scope, decl) => {
3499
+ const out = [];
3500
+
3501
+ for (let i = 0; i < decl.arguments.length; i++) {
3502
+ out.push(
3503
+ ...generate(scope, decl.arguments[i]),
3504
+ Opcodes.i32_to_u,
3505
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3506
+ );
3507
+ }
3508
+
3509
+ out.push(Opcodes.i32_from_u);
3510
+
3511
+ return out;
3512
+ },
3513
+ type: TYPES.boolean,
2822
3514
  notConstr: true
3515
+ },
3516
+
3517
+ __Porffor_fastAnd: {
3518
+ generate: (scope, decl) => {
3519
+ const out = [];
3520
+
3521
+ for (let i = 0; i < decl.arguments.length; i++) {
3522
+ out.push(
3523
+ ...generate(scope, decl.arguments[i]),
3524
+ Opcodes.i32_to_u,
3525
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3526
+ );
3527
+ }
3528
+
3529
+ out.push(Opcodes.i32_from_u);
3530
+
3531
+ return out;
3532
+ },
3533
+ type: TYPES.boolean,
3534
+ notConstr: true
3535
+ },
3536
+
3537
+ Boolean: {
3538
+ generate: (scope, decl) => {
3539
+ // todo: boolean object when used as constructor
3540
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3541
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3542
+ },
3543
+ type: TYPES.boolean,
3544
+ length: 1
3545
+ },
3546
+
3547
+ __Math_max: {
3548
+ generate: (scope, decl) => {
3549
+ const out = [
3550
+ ...number(-Infinity)
3551
+ ];
3552
+
3553
+ for (let i = 0; i < decl.arguments.length; i++) {
3554
+ out.push(
3555
+ ...generate(scope, decl.arguments[i]),
3556
+ [ Opcodes.f64_max ]
3557
+ );
3558
+ }
3559
+
3560
+ return out;
3561
+ },
3562
+ type: TYPES.number,
3563
+ notConstr: true,
3564
+ length: 2
3565
+ },
3566
+
3567
+ __Math_min: {
3568
+ generate: (scope, decl) => {
3569
+ const out = [
3570
+ ...number(Infinity)
3571
+ ];
3572
+
3573
+ for (let i = 0; i < decl.arguments.length; i++) {
3574
+ out.push(
3575
+ ...generate(scope, decl.arguments[i]),
3576
+ [ Opcodes.f64_min ]
3577
+ );
3578
+ }
3579
+
3580
+ return out;
3581
+ },
3582
+ type: TYPES.number,
3583
+ notConstr: true,
3584
+ length: 2
3585
+ },
3586
+
3587
+ __console_log: {
3588
+ generate: (scope, decl) => {
3589
+ const out = [];
3590
+
3591
+ for (let i = 0; i < decl.arguments.length; i++) {
3592
+ out.push(
3593
+ ...generateCall(scope, {
3594
+ callee: {
3595
+ type: 'Identifier',
3596
+ name: '__Porffor_print'
3597
+ },
3598
+ arguments: [ decl.arguments[i] ]
3599
+ }),
3600
+
3601
+ // print space
3602
+ ...number(32),
3603
+ [ Opcodes.call, importedFuncs.printChar ]
3604
+ );
3605
+ }
3606
+
3607
+ // print newline
3608
+ out.push(
3609
+ ...number(10),
3610
+ [ Opcodes.call, importedFuncs.printChar ]
3611
+ );
3612
+
3613
+ return out;
3614
+ },
3615
+ type: TYPES.undefined,
3616
+ notConstr: true,
3617
+ length: 0
2823
3618
  }
2824
3619
  };
2825
3620
 
@@ -2848,7 +3643,6 @@ export default program => {
2848
3643
  funcs = [];
2849
3644
  funcIndex = {};
2850
3645
  depth = [];
2851
- arrays = new Map();
2852
3646
  pages = new Map();
2853
3647
  data = [];
2854
3648
  currentFuncIndex = importedFuncs.length;
@@ -2862,6 +3656,10 @@ export default program => {
2862
3656
 
2863
3657
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
2864
3658
 
3659
+ globalThis.pageSize = PageSize;
3660
+ const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3661
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3662
+
2865
3663
  // set generic opcodes for current valtype
2866
3664
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
2867
3665
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -2870,10 +3668,10 @@ export default program => {
2870
3668
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
2871
3669
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
2872
3670
 
2873
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
2874
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
2875
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
2876
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3671
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3672
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3673
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3674
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
2877
3675
 
2878
3676
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
2879
3677
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -2886,10 +3684,6 @@ export default program => {
2886
3684
 
2887
3685
  program.id = { name: 'main' };
2888
3686
 
2889
- globalThis.pageSize = PageSize;
2890
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
2891
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
2892
-
2893
3687
  const scope = {
2894
3688
  locals: {},
2895
3689
  localInd: 0
@@ -2900,7 +3694,7 @@ export default program => {
2900
3694
  body: program.body
2901
3695
  };
2902
3696
 
2903
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3697
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
2904
3698
 
2905
3699
  generateFunc(scope, program);
2906
3700
 
@@ -2917,7 +3711,11 @@ export default program => {
2917
3711
  }
2918
3712
 
2919
3713
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
2920
- main.returns = [];
3714
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3715
+ main.wasm.splice(main.wasm.length - 1, 1);
3716
+ } else {
3717
+ main.returns = [];
3718
+ }
2921
3719
  }
2922
3720
 
2923
3721
  if (lastInst[0] === Opcodes.call) {