porffor 0.2.0-2265539 → 0.2.0-22b3092

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 (56) hide show
  1. package/.vscode/launch.json +18 -0
  2. package/LICENSE +20 -20
  3. package/README.md +61 -43
  4. package/asur/README.md +2 -0
  5. package/asur/index.js +1262 -0
  6. package/byg/index.js +237 -0
  7. package/compiler/2c.js +1 -1
  8. package/compiler/{sections.js → assemble.js} +57 -10
  9. package/compiler/builtins/annexb_string.js +72 -0
  10. package/compiler/builtins/annexb_string.ts +19 -0
  11. package/compiler/builtins/array.ts +145 -0
  12. package/compiler/builtins/base64.ts +49 -39
  13. package/compiler/builtins/crypto.ts +120 -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 +26 -19
  18. package/compiler/builtins/string.ts +1055 -0
  19. package/compiler/builtins/tostring.ts +45 -0
  20. package/compiler/builtins.js +412 -105
  21. package/compiler/{codeGen.js → codegen.js} +674 -287
  22. package/compiler/embedding.js +22 -22
  23. package/compiler/encoding.js +108 -10
  24. package/compiler/generated_builtins.js +684 -4
  25. package/compiler/index.js +16 -14
  26. package/compiler/log.js +6 -3
  27. package/compiler/opt.js +23 -22
  28. package/compiler/parse.js +31 -25
  29. package/compiler/precompile.js +19 -25
  30. package/compiler/prefs.js +2 -2
  31. package/compiler/prototype.js +2 -18
  32. package/compiler/types.js +37 -0
  33. package/compiler/wasmSpec.js +27 -8
  34. package/compiler/wrap.js +46 -45
  35. package/package.json +9 -5
  36. package/porf +2 -0
  37. package/rhemyn/compile.js +3 -2
  38. package/rhemyn/parse.js +323 -320
  39. package/rhemyn/test/parse.js +58 -58
  40. package/runner/compare.js +34 -34
  41. package/runner/debug.js +122 -0
  42. package/runner/index.js +31 -9
  43. package/runner/profiler.js +102 -0
  44. package/runner/repl.js +40 -7
  45. package/runner/sizes.js +37 -37
  46. package/test262_changes_from_1afe9b87d2_to_04-09.md +270 -0
  47. package/demo.js +0 -3
  48. package/demo.ts +0 -1
  49. package/filesize.cmd +0 -2
  50. package/hello +0 -0
  51. package/runner/info.js +0 -89
  52. package/runner/profile.js +0 -46
  53. package/runner/results.json +0 -1
  54. package/runner/transform.js +0 -15
  55. package/tmp.c +0 -152
  56. package/util/enum.js +0 -20
@@ -8,6 +8,7 @@ import { log } from "./log.js";
8
8
  import parse from "./parse.js";
9
9
  import * as Rhemyn from "../rhemyn/compile.js";
10
10
  import Prefs from './prefs.js';
11
+ import { TYPES, TYPE_NAMES } from './types.js';
11
12
 
12
13
  let globals = {};
13
14
  let globalInd = 0;
@@ -24,35 +25,37 @@ const debug = str => {
24
25
  const logChar = n => {
25
26
  code.push(...number(n));
26
27
 
27
- code.push(Opcodes.call);
28
- code.push(...unsignedLEB128(0));
28
+ code.push([ Opcodes.call, 0 ]);
29
29
  };
30
30
 
31
31
  for (let i = 0; i < str.length; i++) {
32
32
  logChar(str.charCodeAt(i));
33
33
  }
34
34
 
35
- logChar('\n'.charCodeAt(0));
35
+ logChar(10); // new line
36
36
 
37
37
  return code;
38
38
  };
39
39
 
40
- const todo = msg => {
41
- class TodoError extends Error {
42
- constructor(message) {
43
- super(message);
44
- this.name = 'TodoError';
45
- }
40
+ class TodoError extends Error {
41
+ constructor(message) {
42
+ super(message);
43
+ this.name = 'TodoError';
46
44
  }
45
+ }
46
+ const todo = (scope, msg, expectsValue = undefined) => {
47
+ switch (Prefs.todoTime ?? 'runtime') {
48
+ case 'compile':
49
+ throw new TodoError(msg);
47
50
 
48
- throw new TodoError(`todo: ${msg}`);
49
-
50
- const code = [];
51
-
52
- code.push(...debug(`todo! ` + msg));
53
- code.push(Opcodes.unreachable);
51
+ case 'runtime':
52
+ return internalThrow(scope, 'TodoError', msg, expectsValue);
54
53
 
55
- return code;
54
+ // return [
55
+ // ...debug(`todo! ${msg}`),
56
+ // [ Opcodes.unreachable ]
57
+ // ];
58
+ }
56
59
  };
57
60
 
58
61
  const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
@@ -105,7 +108,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
105
108
  return generateUnary(scope, decl);
106
109
 
107
110
  case 'UpdateExpression':
108
- return generateUpdate(scope, decl);
111
+ return generateUpdate(scope, decl, global, name, valueUnused);
109
112
 
110
113
  case 'IfStatement':
111
114
  return generateIf(scope, decl);
@@ -116,6 +119,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
116
119
  case 'WhileStatement':
117
120
  return generateWhile(scope, decl);
118
121
 
122
+ case 'DoWhileStatement':
123
+ return generateDoWhile(scope, decl);
124
+
119
125
  case 'ForOfStatement':
120
126
  return generateForOf(scope, decl);
121
127
 
@@ -125,6 +131,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
125
131
  case 'ContinueStatement':
126
132
  return generateContinue(scope, decl);
127
133
 
134
+ case 'LabeledStatement':
135
+ return generateLabel(scope, decl);
136
+
128
137
  case 'EmptyStatement':
129
138
  return generateEmpty(scope, decl);
130
139
 
@@ -138,7 +147,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
138
147
  return generateTry(scope, decl);
139
148
 
140
149
  case 'DebuggerStatement':
141
- // todo: add fancy terminal debugger?
150
+ // todo: hook into terminal debugger
142
151
  return [];
143
152
 
144
153
  case 'ArrayExpression':
@@ -152,10 +161,16 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
152
161
  const funcsBefore = funcs.length;
153
162
  generate(scope, decl.declaration);
154
163
 
155
- 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');
156
171
 
157
- const newFunc = funcs[funcs.length - 1];
158
- newFunc.export = true;
172
+ // const newFunc = funcs[funcs.length - 1];
173
+ // newFunc.export = true;
159
174
 
160
175
  return [];
161
176
 
@@ -202,35 +217,42 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
202
217
  },
203
218
 
204
219
  __Porffor_bs: str => [
205
- ...makeString(scope, str, undefined, undefined, true),
220
+ ...makeString(scope, str, global, name, true),
206
221
 
207
- ...number(TYPES._bytestring, Valtype.i32),
208
- setLastType(scope)
222
+ ...(name ? setType(scope, name, TYPES._bytestring) : [
223
+ ...number(TYPES._bytestring, Valtype.i32),
224
+ ...setLastType(scope)
225
+ ])
209
226
  ],
210
227
  __Porffor_s: str => [
211
- ...makeString(scope, str, undefined, undefined, false),
228
+ ...makeString(scope, str, global, name, false),
212
229
 
213
- ...number(TYPES.string, Valtype.i32),
214
- setLastType(scope)
230
+ ...(name ? setType(scope, name, TYPES.string) : [
231
+ ...number(TYPES.string, Valtype.i32),
232
+ ...setLastType(scope)
233
+ ])
215
234
  ],
216
235
  };
217
236
 
218
- const name = decl.tag.name;
237
+ const func = decl.tag.name;
219
238
  // hack for inline asm
220
- if (!funcs[name]) return todo('tagged template expressions not implemented');
239
+ if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
221
240
 
222
241
  const { quasis, expressions } = decl.quasi;
223
242
  let str = quasis[0].value.raw;
224
243
 
225
244
  for (let i = 0; i < expressions.length; i++) {
226
245
  const e = expressions[i];
227
- str += lookupName(scope, e.name)[0].idx;
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
+
228
252
  str += quasis[i + 1].value.raw;
229
253
  }
230
254
 
231
- console.log(str);
232
-
233
- return funcs[name](str);
255
+ return funcs[func](str);
234
256
  }
235
257
 
236
258
  default:
@@ -240,7 +262,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
240
262
  return [];
241
263
  }
242
264
 
243
- return todo(`no generation for ${decl.type}!`);
265
+ return todo(scope, `no generation for ${decl.type}!`);
244
266
  }
245
267
  };
246
268
 
@@ -268,7 +290,7 @@ const lookupName = (scope, _name) => {
268
290
  return [ undefined, undefined ];
269
291
  };
270
292
 
271
- const internalThrow = (scope, constructor, message, expectsValue = false) => [
293
+ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
272
294
  ...generateThrow(scope, {
273
295
  argument: {
274
296
  type: 'NewExpression',
@@ -292,7 +314,10 @@ const generateIdent = (scope, decl) => {
292
314
 
293
315
  if (Object.hasOwn(builtinVars, name)) {
294
316
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
295
- return builtinVars[name];
317
+
318
+ let wasm = builtinVars[name];
319
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
320
+ return wasm.slice();
296
321
  }
297
322
 
298
323
  if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
@@ -300,6 +325,11 @@ const generateIdent = (scope, decl) => {
300
325
  return number(1);
301
326
  }
302
327
 
328
+ if (isExistingProtoFunc(name)) {
329
+ // todo: return an actual something
330
+ return number(1);
331
+ }
332
+
303
333
  if (local?.idx === undefined) {
304
334
  // no local var with name
305
335
  if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
@@ -330,14 +360,18 @@ const generateReturn = (scope, decl) => {
330
360
  // just bare "return"
331
361
  return [
332
362
  ...number(UNDEFINED), // "undefined" if func returns
333
- ...number(TYPES.undefined, Valtype.i32), // type undefined
363
+ ...(scope.returnType != null ? [] : [
364
+ ...number(TYPES.undefined, Valtype.i32) // type undefined
365
+ ]),
334
366
  [ Opcodes.return ]
335
367
  ];
336
368
  }
337
369
 
338
370
  return [
339
371
  ...generate(scope, decl.argument),
340
- ...getNodeType(scope, decl.argument),
372
+ ...(scope.returnType != null ? [] : [
373
+ ...getNodeType(scope, decl.argument)
374
+ ]),
341
375
  [ Opcodes.return ]
342
376
  ];
343
377
  };
@@ -351,7 +385,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
351
385
  return idx;
352
386
  };
353
387
 
354
- 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);
355
390
 
356
391
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
357
392
  const checks = {
@@ -360,7 +395,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
360
395
  '??': nullish
361
396
  };
362
397
 
363
- 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);
364
399
 
365
400
  // generic structure for {a} OP {b}
366
401
  // -->
@@ -368,8 +403,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
368
403
 
369
404
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
370
405
  // (like if we are in an if condition - very common)
371
- const leftIsInt = isIntOp(left[left.length - 1]);
372
- const rightIsInt = isIntOp(right[right.length - 1]);
406
+ const leftIsInt = isFloatToIntOp(left[left.length - 1]);
407
+ const rightIsInt = isFloatToIntOp(right[right.length - 1]);
373
408
 
374
409
  const canInt = leftIsInt && rightIsInt;
375
410
 
@@ -386,12 +421,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
386
421
  ...right,
387
422
  // note type
388
423
  ...rightType,
389
- setLastType(scope),
424
+ ...setLastType(scope),
390
425
  [ Opcodes.else ],
391
426
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
392
427
  // note type
393
428
  ...leftType,
394
- setLastType(scope),
429
+ ...setLastType(scope),
395
430
  [ Opcodes.end ],
396
431
  Opcodes.i32_from
397
432
  ];
@@ -405,17 +440,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
405
440
  ...right,
406
441
  // note type
407
442
  ...rightType,
408
- setLastType(scope),
443
+ ...setLastType(scope),
409
444
  [ Opcodes.else ],
410
445
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
411
446
  // note type
412
447
  ...leftType,
413
- setLastType(scope),
448
+ ...setLastType(scope),
414
449
  [ Opcodes.end ]
415
450
  ];
416
451
  };
417
452
 
418
- const concatStrings = (scope, left, right, global, name, assign) => {
453
+ const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
419
454
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
420
455
  // todo: convert left and right to strings if not
421
456
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -426,7 +461,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
426
461
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
427
462
 
428
463
  if (assign) {
429
- const pointer = arrays.get(name ?? '$undeclared');
464
+ const pointer = scope.arrays?.get(name ?? '$undeclared');
430
465
 
431
466
  return [
432
467
  // setup right
@@ -451,11 +486,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
451
486
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
452
487
 
453
488
  // copy right
454
- // dst = out pointer + length size + current length * i16 size
489
+ // dst = out pointer + length size + current length * sizeof valtype
455
490
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
456
491
 
457
492
  [ Opcodes.local_get, leftLength ],
458
- ...number(ValtypeSize.i16, Valtype.i32),
493
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
459
494
  [ Opcodes.i32_mul ],
460
495
  [ Opcodes.i32_add ],
461
496
 
@@ -464,9 +499,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
464
499
  ...number(ValtypeSize.i32, Valtype.i32),
465
500
  [ Opcodes.i32_add ],
466
501
 
467
- // size = right length * i16 size
502
+ // size = right length * sizeof valtype
468
503
  [ Opcodes.local_get, rightLength ],
469
- ...number(ValtypeSize.i16, Valtype.i32),
504
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
470
505
  [ Opcodes.i32_mul ],
471
506
 
472
507
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -524,11 +559,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
524
559
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
525
560
 
526
561
  // copy right
527
- // dst = out pointer + length size + left length * i16 size
562
+ // dst = out pointer + length size + left length * sizeof valtype
528
563
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
529
564
 
530
565
  [ Opcodes.local_get, leftLength ],
531
- ...number(ValtypeSize.i16, Valtype.i32),
566
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
532
567
  [ Opcodes.i32_mul ],
533
568
  [ Opcodes.i32_add ],
534
569
 
@@ -537,9 +572,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
537
572
  ...number(ValtypeSize.i32, Valtype.i32),
538
573
  [ Opcodes.i32_add ],
539
574
 
540
- // size = right length * i16 size
575
+ // size = right length * sizeof valtype
541
576
  [ Opcodes.local_get, rightLength ],
542
- ...number(ValtypeSize.i16, Valtype.i32),
577
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
543
578
  [ Opcodes.i32_mul ],
544
579
 
545
580
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -549,7 +584,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
549
584
  ];
550
585
  };
551
586
 
552
- const compareStrings = (scope, left, right) => {
587
+ const compareStrings = (scope, left, right, bytestrings = false) => {
553
588
  // todo: this should be rewritten into a func
554
589
  // todo: convert left and right to strings if not
555
590
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -558,7 +593,6 @@ const compareStrings = (scope, left, right) => {
558
593
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
559
594
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
560
595
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
561
- const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
562
596
 
563
597
  const index = localTmp(scope, 'compare_index', Valtype.i32);
564
598
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
@@ -586,7 +620,6 @@ const compareStrings = (scope, left, right) => {
586
620
 
587
621
  [ Opcodes.local_get, rightPointer ],
588
622
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
589
- [ Opcodes.local_tee, rightLength ],
590
623
 
591
624
  // fast path: check leftLength != rightLength
592
625
  [ Opcodes.i32_ne ],
@@ -601,11 +634,13 @@ const compareStrings = (scope, left, right) => {
601
634
  ...number(0, Valtype.i32),
602
635
  [ Opcodes.local_set, index ],
603
636
 
604
- // setup index end as length * sizeof i16 (2)
637
+ // setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
605
638
  // we do this instead of having to do mul/div each iter for perf™
606
639
  [ Opcodes.local_get, leftLength ],
607
- ...number(ValtypeSize.i16, Valtype.i32),
608
- [ Opcodes.i32_mul ],
640
+ ...(bytestrings ? [] : [
641
+ ...number(ValtypeSize.i16, Valtype.i32),
642
+ [ Opcodes.i32_mul ],
643
+ ]),
609
644
  [ Opcodes.local_set, indexEnd ],
610
645
 
611
646
  // iterate over each char and check if eq
@@ -615,13 +650,17 @@ const compareStrings = (scope, left, right) => {
615
650
  [ Opcodes.local_get, index ],
616
651
  [ Opcodes.local_get, leftPointer ],
617
652
  [ Opcodes.i32_add ],
618
- [ 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 ],
619
656
 
620
657
  // fetch right
621
658
  [ Opcodes.local_get, index ],
622
659
  [ Opcodes.local_get, rightPointer ],
623
660
  [ Opcodes.i32_add ],
624
- [ 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 ],
625
664
 
626
665
  // not equal, "return" false
627
666
  [ Opcodes.i32_ne ],
@@ -630,13 +669,13 @@ const compareStrings = (scope, left, right) => {
630
669
  [ Opcodes.br, 2 ],
631
670
  [ Opcodes.end ],
632
671
 
633
- // index += sizeof i16 (2)
672
+ // index += sizeof valtype (1 for bytestring, 2 for string)
634
673
  [ Opcodes.local_get, index ],
635
- ...number(ValtypeSize.i16, Valtype.i32),
674
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
636
675
  [ Opcodes.i32_add ],
637
676
  [ Opcodes.local_tee, index ],
638
677
 
639
- // if index != index end (length * sizeof 16), loop
678
+ // if index != index end (length * sizeof valtype), loop
640
679
  [ Opcodes.local_get, indexEnd ],
641
680
  [ Opcodes.i32_ne ],
642
681
  [ Opcodes.br_if, 0 ],
@@ -657,13 +696,14 @@ const compareStrings = (scope, left, right) => {
657
696
  };
658
697
 
659
698
  const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
660
- if (isIntOp(wasm[wasm.length - 1])) return [
699
+ if (isFloatToIntOp(wasm[wasm.length - 1])) return [
661
700
  ...wasm,
662
701
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
663
702
  ];
703
+ // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
664
704
 
665
705
  const useTmp = knownType(scope, type) == null;
666
- const tmp = !useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
706
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
667
707
 
668
708
  const def = [
669
709
  // if value != 0
@@ -715,7 +755,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
715
755
 
716
756
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
717
757
  const useTmp = knownType(scope, type) == null;
718
- const tmp = !useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
758
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
719
759
 
720
760
  return [
721
761
  ...wasm,
@@ -761,7 +801,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
761
801
 
762
802
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
763
803
  const useTmp = knownType(scope, type) == null;
764
- const tmp = !useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
804
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
765
805
 
766
806
  return [
767
807
  ...wasm,
@@ -855,11 +895,11 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
855
895
  // todo: if equality op and an operand is undefined, return false
856
896
  // todo: niche null hell with 0
857
897
 
858
- // todo: this should be dynamic but for now only static
859
898
  if (knownLeft === TYPES.string || knownRight === TYPES.string) {
860
899
  if (op === '+') {
900
+ // todo: this should be dynamic too but for now only static
861
901
  // string concat (a + b)
862
- return concatStrings(scope, left, right, _global, _name, assign);
902
+ return concatStrings(scope, left, right, _global, _name, assign, false);
863
903
  }
864
904
 
865
905
  // not an equality op, NaN
@@ -882,6 +922,33 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
882
922
  }
883
923
  }
884
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
+ }
951
+
885
952
  let ops = operatorOpcode[valtype][op];
886
953
 
887
954
  // some complex ops are implemented as builtin funcs
@@ -897,23 +964,62 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
897
964
  ]);
898
965
  }
899
966
 
900
- 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);
901
968
 
902
969
  if (!Array.isArray(ops)) ops = [ ops ];
903
970
  ops = [ ops ];
904
971
 
905
972
  let tmpLeft, tmpRight;
906
973
  // if equal op, check if strings for compareStrings
907
- if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
908
- // todo: intelligent partial skip later
909
- // if neither known are string, stop this madness
910
- if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
911
- return;
912
- }
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
913
977
 
978
+ if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
914
979
  tmpLeft = localTmp(scope, '__tmpop_left');
915
980
  tmpRight = localTmp(scope, '__tmpop_right');
916
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)
917
1023
  ops.unshift(...stringOnly([
918
1024
  // if left is string
919
1025
  ...leftType,
@@ -925,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
925
1031
  ...number(TYPES.string, Valtype.i32),
926
1032
  [ Opcodes.i32_eq ],
927
1033
 
928
- // if either are true
929
- [ Opcodes.i32_or ],
1034
+ // if both are true
1035
+ [ Opcodes.i32_and ],
930
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 ],
931
1041
 
932
- // todo: convert non-strings to strings, for now fail immediately if one is not
933
- // if left is not string
1042
+ // if left is bytestring
934
1043
  ...leftType,
935
- ...number(TYPES.string, Valtype.i32),
936
- [ Opcodes.i32_ne ],
1044
+ ...number(TYPES._bytestring, Valtype.i32),
1045
+ [ Opcodes.i32_eq ],
937
1046
 
938
- // if right is not string
1047
+ // if right is bytestring
939
1048
  ...rightType,
940
- ...number(TYPES.string, Valtype.i32),
941
- [ Opcodes.i32_ne ],
1049
+ ...number(TYPES._bytestring, Valtype.i32),
1050
+ [ Opcodes.i32_eq ],
942
1051
 
943
- // if either are true
944
- [ Opcodes.i32_or ],
1052
+ // if both are true
1053
+ [ Opcodes.i32_and ],
945
1054
  [ Opcodes.if, Blocktype.void ],
946
- ...number(0, Valtype.i32),
947
- [ Opcodes.br, 2 ],
948
- [ Opcodes.end ],
949
-
950
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
951
- // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1055
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
952
1056
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
953
1057
  [ Opcodes.br, 1 ],
954
1058
  [ Opcodes.end ],
@@ -960,7 +1064,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
960
1064
  // endOut.push(stringOnly([ Opcodes.end ]));
961
1065
  endOut.unshift(stringOnly([ Opcodes.end ]));
962
1066
  // }
963
- })();
1067
+ }
964
1068
 
965
1069
  return finalize([
966
1070
  ...left,
@@ -979,6 +1083,21 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
979
1083
  return out;
980
1084
  };
981
1085
 
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
+
982
1101
  const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
983
1102
  const existing = funcs.find(x => x.name === name);
984
1103
  if (existing) return existing;
@@ -997,28 +1116,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
997
1116
  data.push(copy);
998
1117
  }
999
1118
 
1000
- if (typeof wasm === 'function') {
1001
- const scope = {
1002
- name,
1003
- params,
1004
- locals,
1005
- returns,
1006
- localInd: allLocals.length,
1007
- };
1008
-
1009
- wasm = wasm(scope, {
1010
- TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
1011
- builtin: name => {
1012
- let idx = funcIndex[name] ?? importedFuncs[name];
1013
- if (idx === undefined && builtinFuncs[name]) {
1014
- includeBuiltin(scope, name);
1015
- idx = funcIndex[name];
1016
- }
1017
-
1018
- return idx;
1019
- }
1020
- });
1021
- }
1119
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1022
1120
 
1023
1121
  let baseGlobalIdx, i = 0;
1024
1122
  for (const type of globalTypes) {
@@ -1042,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1042
1140
  params,
1043
1141
  locals,
1044
1142
  returns,
1045
- returnType: TYPES[returnType ?? 'number'],
1143
+ returnType: returnType ?? TYPES.number,
1046
1144
  wasm,
1047
1145
  internal: true,
1048
1146
  index: currentFuncIndex++
@@ -1065,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
1065
1163
  return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
1066
1164
  };
1067
1165
 
1166
+ // potential future ideas for nan boxing (unused):
1068
1167
  // T = JS type, V = value/pointer
1069
1168
  // 0bTTT
1070
1169
  // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
@@ -1088,40 +1187,18 @@ const generateLogicExp = (scope, decl) => {
1088
1187
  // 4: internal type
1089
1188
  // 5: pointer
1090
1189
 
1091
- const TYPES = {
1092
- number: 0x00,
1093
- boolean: 0x01,
1094
- string: 0x02,
1095
- undefined: 0x03,
1096
- object: 0x04,
1097
- function: 0x05,
1098
- symbol: 0x06,
1099
- bigint: 0x07,
1100
-
1101
- // these are not "typeof" types but tracked internally
1102
- _array: 0x10,
1103
- _regexp: 0x11,
1104
- _bytestring: 0x12
1105
- };
1106
-
1107
- const TYPE_NAMES = {
1108
- [TYPES.number]: 'Number',
1109
- [TYPES.boolean]: 'Boolean',
1110
- [TYPES.string]: 'String',
1111
- [TYPES.undefined]: 'undefined',
1112
- [TYPES.object]: 'Object',
1113
- [TYPES.function]: 'Function',
1114
- [TYPES.symbol]: 'Symbol',
1115
- [TYPES.bigint]: 'BigInt',
1116
-
1117
- [TYPES._array]: 'Array',
1118
- [TYPES._regexp]: 'RegExp',
1119
- [TYPES._bytestring]: 'ByteString'
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;
1120
1195
  };
1121
1196
 
1122
1197
  const getType = (scope, _name) => {
1123
1198
  const name = mapName(_name);
1124
1199
 
1200
+ // if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
1201
+
1125
1202
  if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1126
1203
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1127
1204
 
@@ -1129,11 +1206,10 @@ const getType = (scope, _name) => {
1129
1206
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1130
1207
 
1131
1208
  let type = TYPES.undefined;
1132
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1209
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1133
1210
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1134
1211
 
1135
- if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
1136
- name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
1212
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1137
1213
 
1138
1214
  return number(type, Valtype.i32);
1139
1215
  };
@@ -1156,15 +1232,16 @@ const setType = (scope, _name, type) => {
1156
1232
  ];
1157
1233
 
1158
1234
  // throw new Error('could not find var');
1235
+ return [];
1159
1236
  };
1160
1237
 
1161
1238
  const getLastType = scope => {
1162
1239
  scope.gotLastType = true;
1163
- return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1240
+ return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1164
1241
  };
1165
1242
 
1166
1243
  const setLastType = scope => {
1167
- return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1244
+ return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1168
1245
  };
1169
1246
 
1170
1247
  const getNodeType = (scope, node) => {
@@ -1189,7 +1266,7 @@ const getNodeType = (scope, node) => {
1189
1266
  const name = node.callee.name;
1190
1267
  if (!name) {
1191
1268
  // iife
1192
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1269
+ if (scope.locals['#last_type']) return getLastType(scope);
1193
1270
 
1194
1271
  // presume
1195
1272
  // todo: warn here?
@@ -1203,7 +1280,7 @@ const getNodeType = (scope, node) => {
1203
1280
  if (func.returnType) return func.returnType;
1204
1281
  }
1205
1282
 
1206
- if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1283
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1207
1284
  if (internalConstrs[name]) return internalConstrs[name].type;
1208
1285
 
1209
1286
  // check if this is a prototype function
@@ -1223,7 +1300,7 @@ const getNodeType = (scope, node) => {
1223
1300
  return TYPES.number;
1224
1301
  }
1225
1302
 
1226
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1303
+ if (scope.locals['#last_type']) return getLastType(scope);
1227
1304
 
1228
1305
  // presume
1229
1306
  // todo: warn here?
@@ -1278,6 +1355,7 @@ const getNodeType = (scope, node) => {
1278
1355
 
1279
1356
  // todo: this should be dynamic but for now only static
1280
1357
  if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1358
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) return TYPES._bytestring;
1281
1359
 
1282
1360
  return TYPES.number;
1283
1361
 
@@ -1314,15 +1392,21 @@ const getNodeType = (scope, node) => {
1314
1392
 
1315
1393
  // ts hack
1316
1394
  if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1395
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._bytestring) return TYPES._bytestring;
1317
1396
  if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1318
1397
 
1319
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1398
+ if (scope.locals['#last_type']) return getLastType(scope);
1320
1399
 
1321
1400
  // presume
1322
1401
  return TYPES.number;
1323
1402
  }
1324
1403
 
1325
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1404
+ if (node.type === 'TaggedTemplateExpression') {
1405
+ // hack
1406
+ if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
1407
+ }
1408
+
1409
+ if (scope.locals['#last_type']) return getLastType(scope);
1326
1410
 
1327
1411
  // presume
1328
1412
  // todo: warn here?
@@ -1355,7 +1439,7 @@ const generateLiteral = (scope, decl, global, name) => {
1355
1439
  return makeString(scope, decl.value, global, name);
1356
1440
 
1357
1441
  default:
1358
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1442
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1359
1443
  }
1360
1444
  };
1361
1445
 
@@ -1364,6 +1448,8 @@ const countLeftover = wasm => {
1364
1448
 
1365
1449
  for (let i = 0; i < wasm.length; i++) {
1366
1450
  const inst = wasm[i];
1451
+ if (inst[0] == null) continue;
1452
+
1367
1453
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1368
1454
  if (inst[0] === Opcodes.if) count--;
1369
1455
  if (inst[1] !== Blocktype.void) count++;
@@ -1372,7 +1458,7 @@ const countLeftover = wasm => {
1372
1458
  if (inst[0] === Opcodes.end) depth--;
1373
1459
 
1374
1460
  if (depth === 0)
1375
- if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1461
+ if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1376
1462
  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)) {}
1377
1463
  else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1378
1464
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
@@ -1475,10 +1561,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1475
1561
  name = func.name;
1476
1562
  }
1477
1563
 
1478
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1564
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1479
1565
  // literal eval hack
1480
- const code = decl.arguments[0].value;
1481
- const parsed = parse(code, []);
1566
+ const code = decl.arguments[0]?.value ?? '';
1567
+
1568
+ let parsed;
1569
+ try {
1570
+ parsed = parse(code, []);
1571
+ } catch (e) {
1572
+ if (e.name === 'SyntaxError') {
1573
+ // throw syntax errors of evals at runtime instead
1574
+ return internalThrow(scope, 'SyntaxError', e.message, true);
1575
+ }
1576
+
1577
+ throw e;
1578
+ }
1482
1579
 
1483
1580
  const out = generate(scope, {
1484
1581
  type: 'BlockStatement',
@@ -1492,13 +1589,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1492
1589
  const finalStatement = parsed.body[parsed.body.length - 1];
1493
1590
  out.push(
1494
1591
  ...getNodeType(scope, finalStatement),
1495
- setLastType(scope)
1592
+ ...setLastType(scope)
1496
1593
  );
1497
1594
  } else if (countLeftover(out) === 0) {
1498
1595
  out.push(...number(UNDEFINED));
1499
1596
  out.push(
1500
1597
  ...number(TYPES.undefined, Valtype.i32),
1501
- setLastType(scope)
1598
+ ...setLastType(scope)
1502
1599
  );
1503
1600
  }
1504
1601
 
@@ -1520,6 +1617,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1520
1617
 
1521
1618
  target = { ...decl.callee };
1522
1619
  target.name = spl.slice(0, -1).join('_');
1620
+
1621
+ // failed to lookup name, abort
1622
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1523
1623
  }
1524
1624
 
1525
1625
  // literal.func()
@@ -1542,7 +1642,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1542
1642
  Opcodes.i32_from_u,
1543
1643
 
1544
1644
  ...number(TYPES.boolean, Valtype.i32),
1545
- setLastType(scope)
1645
+ ...setLastType(scope)
1546
1646
  ];
1547
1647
  }
1548
1648
 
@@ -1567,12 +1667,30 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1567
1667
  // }
1568
1668
 
1569
1669
  if (protoName) {
1670
+ const protoBC = {};
1671
+
1672
+ const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1673
+
1674
+ if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
1675
+ for (const x of builtinProtoCands) {
1676
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1677
+ if (type == null) continue;
1678
+
1679
+ protoBC[type] = generateCall(scope, {
1680
+ callee: {
1681
+ name: x
1682
+ },
1683
+ arguments: [ target, ...decl.arguments ],
1684
+ _protoInternalCall: true
1685
+ });
1686
+ }
1687
+ }
1688
+
1570
1689
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1571
1690
  if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1572
1691
  return acc;
1573
1692
  }, {});
1574
1693
 
1575
- // no prototype function candidates, ignore
1576
1694
  if (Object.keys(protoCands).length > 0) {
1577
1695
  // use local for cached i32 length as commonly used
1578
1696
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
@@ -1590,7 +1708,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1590
1708
 
1591
1709
  let allOptUnused = true;
1592
1710
  let lengthI32CacheUsed = false;
1593
- const protoBC = {};
1594
1711
  for (const x in protoCands) {
1595
1712
  const protoFunc = protoCands[x];
1596
1713
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1598,7 +1715,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1598
1715
  ...RTArrayUtil.getLength(getPointer),
1599
1716
 
1600
1717
  ...number(TYPES.number, Valtype.i32),
1601
- setLastType(scope)
1718
+ ...setLastType(scope)
1602
1719
  ];
1603
1720
  continue;
1604
1721
  }
@@ -1635,7 +1752,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1635
1752
  ...protoOut,
1636
1753
 
1637
1754
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1638
- setLastType(scope),
1755
+ ...setLastType(scope),
1639
1756
  [ Opcodes.end ]
1640
1757
  ];
1641
1758
  }
@@ -1661,10 +1778,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1661
1778
  }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1662
1779
  ];
1663
1780
  }
1781
+
1782
+ if (Object.keys(protoBC).length > 0) {
1783
+ return typeSwitch(scope, getNodeType(scope, target), {
1784
+ ...protoBC,
1785
+
1786
+ // TODO: error better
1787
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1788
+ }, valtypeBinary);
1789
+ }
1664
1790
  }
1665
1791
 
1666
1792
  // TODO: only allows callee as literal
1667
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1793
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1668
1794
 
1669
1795
  let idx = funcIndex[name] ?? importedFuncs[name];
1670
1796
  if (idx === undefined && builtinFuncs[name]) {
@@ -1700,21 +1826,28 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1700
1826
  if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1701
1827
  const wasmOps = {
1702
1828
  // pointer, align, offset
1703
- i32_load8_u: { imms: 2, args: 1 },
1829
+ i32_load: { imms: 2, args: 1, returns: 1 },
1704
1830
  // pointer, value, align, offset
1705
- i32_store8: { imms: 2, args: 2 },
1831
+ i32_store: { imms: 2, args: 2, returns: 0 },
1706
1832
  // pointer, align, offset
1707
- i64_load: { imms: 2, args: 1 },
1708
- // a, b
1709
- i64_shr_u: { imms: 0, args: 2 },
1710
- // x
1711
- i32_wrap_i64: { imms: 0, args: 1, },
1712
- // x
1713
- i64_extend_i32_u: { imms: 0, args: 1 },
1833
+ i32_load8_u: { imms: 2, args: 1, returns: 1 },
1834
+ // pointer, value, align, offset
1835
+ i32_store8: { imms: 2, args: 2, returns: 0 },
1836
+ // pointer, align, offset
1837
+ i32_load16_u: { imms: 2, args: 1, returns: 1 },
1838
+ // pointer, value, align, offset
1839
+ i32_store16: { imms: 2, args: 2, returns: 0 },
1840
+
1841
+ // pointer, align, offset
1842
+ f64_load: { imms: 2, args: 1, returns: 1 },
1843
+ // pointer, value, align, offset
1844
+ f64_store: { imms: 2, args: 2, returns: 0 },
1845
+
1846
+ // value
1847
+ i32_const: { imms: 1, args: 0, returns: 1 },
1848
+
1714
1849
  // a, b
1715
- i64_and: { imms: 0, args: 2 },
1716
- // val (todo: support >255)
1717
- i64_const: { imms: 1, args: 0 },
1850
+ i32_or: { imms: 0, args: 2, returns: 1 },
1718
1851
  };
1719
1852
 
1720
1853
  const opName = name.slice('__Porffor_wasm_'.length);
@@ -1723,28 +1856,32 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1723
1856
  const op = wasmOps[opName];
1724
1857
 
1725
1858
  const argOut = [];
1726
- for (let i = 0; i < op.args; i++) argOut.push(...generate(scope, decl.arguments[i]));
1859
+ for (let i = 0; i < op.args; i++) argOut.push(
1860
+ ...generate(scope, decl.arguments[i]),
1861
+ Opcodes.i32_to
1862
+ );
1727
1863
 
1728
1864
  // literals only
1729
1865
  const imms = decl.arguments.slice(op.args).map(x => x.value);
1730
1866
 
1731
1867
  return [
1732
1868
  ...argOut,
1733
- [ Opcodes[opName], ...imms ]
1869
+ [ Opcodes[opName], ...imms ],
1870
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1734
1871
  ];
1735
1872
  }
1736
1873
  }
1737
1874
 
1738
1875
  if (idx === undefined) {
1739
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1740
- return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1876
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1877
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1741
1878
  }
1742
1879
 
1743
1880
  const func = funcs.find(x => x.index === idx);
1744
1881
 
1745
1882
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1746
1883
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1747
- const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
1884
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1748
1885
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1749
1886
 
1750
1887
  let args = decl.arguments;
@@ -1765,7 +1902,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1765
1902
  const arg = args[i];
1766
1903
  out = out.concat(generate(scope, arg));
1767
1904
 
1768
- if (builtinFuncs[name] && builtinFuncs[name].params[i] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1905
+ if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1906
+ out.push(Opcodes.i32_to);
1907
+ }
1908
+
1909
+ if (importedFuncs[name] && name.startsWith('profile')) {
1769
1910
  out.push(Opcodes.i32_to);
1770
1911
  }
1771
1912
 
@@ -1784,9 +1925,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1784
1925
  // ...number(type, Valtype.i32),
1785
1926
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1786
1927
  // );
1787
- } else out.push(setLastType(scope));
1928
+ } else out.push(...setLastType(scope));
1788
1929
 
1789
- if (builtinFuncs[name] && builtinFuncs[name].returns[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1930
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1790
1931
  out.push(Opcodes.i32_from);
1791
1932
  }
1792
1933
 
@@ -1797,7 +1938,7 @@ const generateNew = (scope, decl, _global, _name) => {
1797
1938
  // hack: basically treat this as a normal call for builtins for now
1798
1939
  const name = mapName(decl.callee.name);
1799
1940
  if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1800
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1941
+ if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1801
1942
 
1802
1943
  return generateCall(scope, decl, _global, _name);
1803
1944
  };
@@ -1931,8 +2072,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1931
2072
  [ Opcodes.block, returns ]
1932
2073
  ];
1933
2074
 
1934
- // todo: use br_table?
1935
-
1936
2075
  for (const x in bc) {
1937
2076
  if (x === 'default') continue;
1938
2077
 
@@ -1988,12 +2127,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1988
2127
  };
1989
2128
 
1990
2129
  const typeAnnoToPorfType = x => {
1991
- if (TYPES[x]) return TYPES[x];
1992
- if (TYPES['_' + x]) return TYPES['_' + x];
2130
+ if (!x) return null;
2131
+ if (TYPES[x] != null) return TYPES[x];
2132
+ if (TYPES['_' + x] != null) return TYPES['_' + x];
1993
2133
 
1994
2134
  switch (x) {
1995
2135
  case 'i32':
1996
2136
  case 'i64':
2137
+ case 'f64':
1997
2138
  return TYPES.number;
1998
2139
  }
1999
2140
 
@@ -2004,7 +2145,7 @@ const extractTypeAnnotation = decl => {
2004
2145
  let a = decl;
2005
2146
  while (a.typeAnnotation) a = a.typeAnnotation;
2006
2147
 
2007
- let type, elementType;
2148
+ let type = null, elementType = null;
2008
2149
  if (a.typeName) {
2009
2150
  type = a.typeName.name;
2010
2151
  } else if (a.type.endsWith('Keyword')) {
@@ -2035,7 +2176,7 @@ const generateVar = (scope, decl) => {
2035
2176
  for (const x of decl.declarations) {
2036
2177
  const name = mapName(x.id.name);
2037
2178
 
2038
- if (!name) return todo('destructuring is not supported yet');
2179
+ if (!name) return todo(scope, 'destructuring is not supported yet');
2039
2180
 
2040
2181
  if (x.init && isFuncType(x.init.type)) {
2041
2182
  // hack for let a = function () { ... }
@@ -2052,9 +2193,10 @@ const generateVar = (scope, decl) => {
2052
2193
  continue; // always ignore
2053
2194
  }
2054
2195
 
2055
- let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
2196
+ const typed = typedInput && x.id.typeAnnotation;
2197
+ let idx = allocVar(scope, name, global, !typed);
2056
2198
 
2057
- if (typedInput && x.id.typeAnnotation) {
2199
+ if (typed) {
2058
2200
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
2059
2201
  }
2060
2202
 
@@ -2072,7 +2214,8 @@ const generateVar = (scope, decl) => {
2072
2214
  return out;
2073
2215
  };
2074
2216
 
2075
- const generateAssign = (scope, decl) => {
2217
+ // todo: optimize this func for valueUnused
2218
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2076
2219
  const { type, name } = decl.left;
2077
2220
 
2078
2221
  if (type === 'ObjectPattern') {
@@ -2090,9 +2233,9 @@ const generateAssign = (scope, decl) => {
2090
2233
  // hack: .length setter
2091
2234
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
2092
2235
  const name = decl.left.object.name;
2093
- const pointer = arrays.get(name);
2236
+ const pointer = scope.arrays?.get(name);
2094
2237
 
2095
- const aotPointer = pointer != null;
2238
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2096
2239
 
2097
2240
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2098
2241
 
@@ -2117,9 +2260,9 @@ const generateAssign = (scope, decl) => {
2117
2260
  // arr[i]
2118
2261
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2119
2262
  const name = decl.left.object.name;
2120
- const pointer = arrays.get(name);
2263
+ const pointer = scope.arrays?.get(name);
2121
2264
 
2122
- const aotPointer = pointer != null;
2265
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2123
2266
 
2124
2267
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2125
2268
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
@@ -2175,7 +2318,7 @@ const generateAssign = (scope, decl) => {
2175
2318
  ];
2176
2319
  }
2177
2320
 
2178
- if (!name) return todo('destructuring is not supported yet');
2321
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2179
2322
 
2180
2323
  const [ local, isGlobal ] = lookupName(scope, name);
2181
2324
 
@@ -2223,9 +2366,7 @@ const generateAssign = (scope, decl) => {
2223
2366
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
2224
2367
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2225
2368
 
2226
- getLastType(scope),
2227
- // hack: type is idx+1
2228
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2369
+ ...setType(scope, name, getLastType(scope))
2229
2370
  ];
2230
2371
  }
2231
2372
 
@@ -2236,9 +2377,7 @@ const generateAssign = (scope, decl) => {
2236
2377
 
2237
2378
  // todo: string concat types
2238
2379
 
2239
- // hack: type is idx+1
2240
- ...number(TYPES.number, Valtype.i32),
2241
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2380
+ ...setType(scope, name, TYPES.number)
2242
2381
  ];
2243
2382
  };
2244
2383
 
@@ -2284,7 +2423,7 @@ const generateUnary = (scope, decl) => {
2284
2423
  return out;
2285
2424
  }
2286
2425
 
2287
- case 'delete':
2426
+ case 'delete': {
2288
2427
  let toReturn = true, toGenerate = true;
2289
2428
 
2290
2429
  if (decl.argument.type === 'Identifier') {
@@ -2306,9 +2445,26 @@ const generateUnary = (scope, decl) => {
2306
2445
 
2307
2446
  out.push(...number(toReturn ? 1 : 0));
2308
2447
  return out;
2448
+ }
2449
+
2450
+ case 'typeof': {
2451
+ let overrideType, toGenerate = true;
2452
+
2453
+ if (decl.argument.type === 'Identifier') {
2454
+ const out = generateIdent(scope, decl.argument);
2455
+
2456
+ // if ReferenceError (undeclared var), ignore and return undefined
2457
+ if (out[1]) {
2458
+ // does not exist (2 ops from throw)
2459
+ overrideType = number(TYPES.undefined, Valtype.i32);
2460
+ toGenerate = false;
2461
+ }
2462
+ }
2309
2463
 
2310
- case 'typeof':
2311
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2464
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2465
+ disposeLeftover(out);
2466
+
2467
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2312
2468
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2313
2469
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2314
2470
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
@@ -2319,27 +2475,30 @@ const generateUnary = (scope, decl) => {
2319
2475
 
2320
2476
  // object and internal types
2321
2477
  default: makeString(scope, 'object', false, '#typeof_result'),
2322
- });
2478
+ }));
2479
+
2480
+ return out;
2481
+ }
2323
2482
 
2324
2483
  default:
2325
- return todo(`unary operator ${decl.operator} not implemented yet`);
2484
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2326
2485
  }
2327
2486
  };
2328
2487
 
2329
- const generateUpdate = (scope, decl) => {
2488
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2330
2489
  const { name } = decl.argument;
2331
2490
 
2332
2491
  const [ local, isGlobal ] = lookupName(scope, name);
2333
2492
 
2334
2493
  if (local === undefined) {
2335
- return todo(`update expression with undefined variable`);
2494
+ return todo(scope, `update expression with undefined variable`, true);
2336
2495
  }
2337
2496
 
2338
2497
  const idx = local.idx;
2339
2498
  const out = [];
2340
2499
 
2341
2500
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2342
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2501
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2343
2502
 
2344
2503
  switch (decl.operator) {
2345
2504
  case '++':
@@ -2352,7 +2511,7 @@ const generateUpdate = (scope, decl) => {
2352
2511
  }
2353
2512
 
2354
2513
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2355
- if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2514
+ if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2356
2515
 
2357
2516
  return out;
2358
2517
  };
@@ -2392,7 +2551,7 @@ const generateConditional = (scope, decl) => {
2392
2551
  // note type
2393
2552
  out.push(
2394
2553
  ...getNodeType(scope, decl.consequent),
2395
- setLastType(scope)
2554
+ ...setLastType(scope)
2396
2555
  );
2397
2556
 
2398
2557
  out.push([ Opcodes.else ]);
@@ -2401,7 +2560,7 @@ const generateConditional = (scope, decl) => {
2401
2560
  // note type
2402
2561
  out.push(
2403
2562
  ...getNodeType(scope, decl.alternate),
2404
- setLastType(scope)
2563
+ ...setLastType(scope)
2405
2564
  );
2406
2565
 
2407
2566
  out.push([ Opcodes.end ]);
@@ -2415,7 +2574,7 @@ const generateFor = (scope, decl) => {
2415
2574
  const out = [];
2416
2575
 
2417
2576
  if (decl.init) {
2418
- out.push(...generate(scope, decl.init));
2577
+ out.push(...generate(scope, decl.init, false, undefined, true));
2419
2578
  disposeLeftover(out);
2420
2579
  }
2421
2580
 
@@ -2433,7 +2592,7 @@ const generateFor = (scope, decl) => {
2433
2592
  out.push(...generate(scope, decl.body));
2434
2593
  out.push([ Opcodes.end ]);
2435
2594
 
2436
- if (decl.update) out.push(...generate(scope, decl.update));
2595
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2437
2596
 
2438
2597
  out.push([ Opcodes.br, 1 ]);
2439
2598
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2461,6 +2620,36 @@ const generateWhile = (scope, decl) => {
2461
2620
  return out;
2462
2621
  };
2463
2622
 
2623
+ const generateDoWhile = (scope, decl) => {
2624
+ const out = [];
2625
+
2626
+ out.push([ Opcodes.loop, Blocktype.void ]);
2627
+ depth.push('dowhile');
2628
+
2629
+ // block for break (includes all)
2630
+ out.push([ Opcodes.block, Blocktype.void ]);
2631
+ depth.push('block');
2632
+
2633
+ // block for continue
2634
+ // includes body but not test+loop so we can exit body at anytime
2635
+ // and still test+loop after
2636
+ out.push([ Opcodes.block, Blocktype.void ]);
2637
+ depth.push('block');
2638
+
2639
+ out.push(...generate(scope, decl.body));
2640
+
2641
+ out.push([ Opcodes.end ]);
2642
+ depth.pop();
2643
+
2644
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2645
+ out.push([ Opcodes.br_if, 1 ]);
2646
+
2647
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2648
+ depth.pop(); depth.pop();
2649
+
2650
+ return out;
2651
+ };
2652
+
2464
2653
  const generateForOf = (scope, decl) => {
2465
2654
  const out = [];
2466
2655
 
@@ -2497,7 +2686,10 @@ const generateForOf = (scope, decl) => {
2497
2686
  generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2498
2687
  }
2499
2688
 
2689
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2690
+
2500
2691
  const [ local, isGlobal ] = lookupName(scope, leftName);
2692
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2501
2693
 
2502
2694
  depth.push('block');
2503
2695
  depth.push('block');
@@ -2506,6 +2698,7 @@ const generateForOf = (scope, decl) => {
2506
2698
  // hack: this is naughty and will break things!
2507
2699
  let newOut = number(0, Valtype.f64), newPointer = -1;
2508
2700
  if (pages.hasAnyString) {
2701
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2509
2702
  0, [ newOut, newPointer ] = makeArray(scope, {
2510
2703
  rawElements: new Array(1)
2511
2704
  }, isGlobal, leftName, true, 'i16');
@@ -2597,6 +2790,56 @@ const generateForOf = (scope, decl) => {
2597
2790
  [ Opcodes.end ],
2598
2791
  [ Opcodes.end ]
2599
2792
  ],
2793
+ [TYPES._bytestring]: [
2794
+ ...setType(scope, leftName, TYPES._bytestring),
2795
+
2796
+ [ Opcodes.loop, Blocktype.void ],
2797
+
2798
+ // setup new/out array
2799
+ ...newOut,
2800
+ [ Opcodes.drop ],
2801
+
2802
+ ...number(0, Valtype.i32), // base 0 for store after
2803
+
2804
+ // load current string ind {arg}
2805
+ [ Opcodes.local_get, pointer ],
2806
+ [ Opcodes.local_get, counter ],
2807
+ [ Opcodes.i32_add ],
2808
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2809
+
2810
+ // store to new string ind 0
2811
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2812
+
2813
+ // return new string (page)
2814
+ ...number(newPointer),
2815
+
2816
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2817
+
2818
+ [ Opcodes.block, Blocktype.void ],
2819
+ [ Opcodes.block, Blocktype.void ],
2820
+ ...generate(scope, decl.body),
2821
+ [ Opcodes.end ],
2822
+
2823
+ // increment iter pointer
2824
+ // [ Opcodes.local_get, pointer ],
2825
+ // ...number(1, Valtype.i32),
2826
+ // [ Opcodes.i32_add ],
2827
+ // [ Opcodes.local_set, pointer ],
2828
+
2829
+ // increment counter by 1
2830
+ [ Opcodes.local_get, counter ],
2831
+ ...number(1, Valtype.i32),
2832
+ [ Opcodes.i32_add ],
2833
+ [ Opcodes.local_tee, counter ],
2834
+
2835
+ // loop if counter != length
2836
+ [ Opcodes.local_get, length ],
2837
+ [ Opcodes.i32_ne ],
2838
+ [ Opcodes.br_if, 1 ],
2839
+
2840
+ [ Opcodes.end ],
2841
+ [ Opcodes.end ]
2842
+ ],
2600
2843
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2601
2844
  }, Blocktype.void));
2602
2845
 
@@ -2607,28 +2850,65 @@ const generateForOf = (scope, decl) => {
2607
2850
  return out;
2608
2851
  };
2609
2852
 
2853
+ // find the nearest loop in depth map by type
2610
2854
  const getNearestLoop = () => {
2611
2855
  for (let i = depth.length - 1; i >= 0; i--) {
2612
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2856
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2613
2857
  }
2614
2858
 
2615
2859
  return -1;
2616
2860
  };
2617
2861
 
2618
2862
  const generateBreak = (scope, decl) => {
2619
- const nearestLoop = depth.length - getNearestLoop();
2863
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2864
+ const type = depth[target];
2865
+
2866
+ // different loop types have different branch offsets
2867
+ // as they have different wasm block/loop/if structures
2868
+ // we need to use the right offset by type to branch to the one we want
2869
+ // for a break: exit the loop without executing anything else inside it
2870
+ const offset = ({
2871
+ for: 2, // loop > if (wanted branch) > block (we are here)
2872
+ while: 2, // loop > if (wanted branch) (we are here)
2873
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
2874
+ forof: 2, // loop > block (wanted branch) > block (we are here)
2875
+ if: 1 // break inside if, branch 0 to skip the rest of the if
2876
+ })[type];
2877
+
2620
2878
  return [
2621
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2879
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2622
2880
  ];
2623
2881
  };
2624
2882
 
2625
2883
  const generateContinue = (scope, decl) => {
2626
- const nearestLoop = depth.length - getNearestLoop();
2884
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2885
+ const type = depth[target];
2886
+
2887
+ // different loop types have different branch offsets
2888
+ // as they have different wasm block/loop/if structures
2889
+ // we need to use the right offset by type to branch to the one we want
2890
+ // for a continue: do test for the loop, and then loop depending on that success
2891
+ const offset = ({
2892
+ for: 3, // loop (wanted branch) > if > block (we are here)
2893
+ while: 1, // loop (wanted branch) > if (we are here)
2894
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
2895
+ forof: 3 // loop > block > block (wanted branch) (we are here)
2896
+ })[type];
2897
+
2627
2898
  return [
2628
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2899
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2629
2900
  ];
2630
2901
  };
2631
2902
 
2903
+ const generateLabel = (scope, decl) => {
2904
+ scope.labels ??= new Map();
2905
+
2906
+ const name = decl.label.name;
2907
+ scope.labels.set(name, depth.length);
2908
+
2909
+ return generate(scope, decl.body);
2910
+ };
2911
+
2632
2912
  const generateThrow = (scope, decl) => {
2633
2913
  scope.throws = true;
2634
2914
 
@@ -2661,7 +2941,7 @@ const generateThrow = (scope, decl) => {
2661
2941
  };
2662
2942
 
2663
2943
  const generateTry = (scope, decl) => {
2664
- if (decl.finalizer) return todo('try finally not implemented yet');
2944
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2665
2945
 
2666
2946
  const out = [];
2667
2947
 
@@ -2692,7 +2972,7 @@ const generateAssignPat = (scope, decl) => {
2692
2972
  // TODO
2693
2973
  // if identifier declared, use that
2694
2974
  // else, use default (right)
2695
- return todo('assignment pattern (optional arg)');
2975
+ return todo(scope, 'assignment pattern (optional arg)');
2696
2976
  };
2697
2977
 
2698
2978
  let pages = new Map();
@@ -2771,16 +3051,20 @@ const getAllocType = itemType => {
2771
3051
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2772
3052
  const out = [];
2773
3053
 
3054
+ scope.arrays ??= new Map();
3055
+
2774
3056
  let firstAssign = false;
2775
- if (!arrays.has(name) || name === '$undeclared') {
3057
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2776
3058
  firstAssign = true;
2777
3059
 
2778
3060
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2779
3061
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2780
- arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3062
+
3063
+ if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${scope.name} | ${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3064
+ else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2781
3065
  }
2782
3066
 
2783
- const pointer = arrays.get(name);
3067
+ const pointer = scope.arrays.get(name);
2784
3068
 
2785
3069
  const useRawElements = !!decl.rawElements;
2786
3070
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2866,20 +3150,29 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
2866
3150
  }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2867
3151
  };
2868
3152
 
2869
- let arrays = new Map();
2870
3153
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2871
3154
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2872
3155
  };
2873
3156
 
2874
3157
  export const generateMember = (scope, decl, _global, _name) => {
2875
3158
  const name = decl.object.name;
2876
- const pointer = arrays.get(name);
3159
+ const pointer = scope.arrays?.get(name);
2877
3160
 
2878
- const aotPointer = pointer != null;
3161
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2879
3162
 
2880
3163
  // hack: .length
2881
3164
  if (decl.property.name === 'length') {
2882
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3165
+ const func = funcs.find(x => x.name === name);
3166
+ if (func) {
3167
+ const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3168
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3169
+ return number(typedParams ? func.params.length / 2 : func.params.length);
3170
+ }
3171
+
3172
+ if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3173
+ if (importedFuncs[name]) return number(importedFuncs[name].params);
3174
+ if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3175
+
2883
3176
  return [
2884
3177
  ...(aotPointer ? number(0, Valtype.i32) : [
2885
3178
  ...generate(scope, decl.object),
@@ -2923,7 +3216,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2923
3216
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2924
3217
 
2925
3218
  ...number(TYPES.number, Valtype.i32),
2926
- setLastType(scope)
3219
+ ...setLastType(scope)
2927
3220
  ],
2928
3221
 
2929
3222
  [TYPES.string]: [
@@ -2955,7 +3248,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2955
3248
  ...number(newPointer),
2956
3249
 
2957
3250
  ...number(TYPES.string, Valtype.i32),
2958
- setLastType(scope)
3251
+ ...setLastType(scope)
2959
3252
  ],
2960
3253
  [TYPES._bytestring]: [
2961
3254
  // setup new/out array
@@ -2974,19 +3267,19 @@ export const generateMember = (scope, decl, _global, _name) => {
2974
3267
  ]),
2975
3268
 
2976
3269
  // load current string ind {arg}
2977
- [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3270
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2978
3271
 
2979
3272
  // store to new string ind 0
2980
- [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3273
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2981
3274
 
2982
3275
  // return new string (page)
2983
3276
  ...number(newPointer),
2984
3277
 
2985
3278
  ...number(TYPES._bytestring, Valtype.i32),
2986
- setLastType(scope)
3279
+ ...setLastType(scope)
2987
3280
  ],
2988
3281
 
2989
- default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
3282
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2990
3283
  });
2991
3284
  };
2992
3285
 
@@ -2996,28 +3289,36 @@ const objectHack = node => {
2996
3289
  if (!node) return node;
2997
3290
 
2998
3291
  if (node.type === 'MemberExpression') {
2999
- if (node.computed || node.optional) return node;
3292
+ const out = (() => {
3293
+ if (node.computed || node.optional) return;
3000
3294
 
3001
- let objectName = node.object.name;
3295
+ let objectName = node.object.name;
3002
3296
 
3003
- // if object is not identifier or another member exp, give up
3004
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3297
+ // if object is not identifier or another member exp, give up
3298
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3299
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
3005
3300
 
3006
- if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3301
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3007
3302
 
3008
- // if .length, give up (hack within a hack!)
3009
- if (node.property.name === 'length') return node;
3303
+ // if .length, give up (hack within a hack!)
3304
+ if (node.property.name === 'length') {
3305
+ node.object = objectHack(node.object);
3306
+ return;
3307
+ }
3010
3308
 
3011
- // no object name, give up
3012
- if (!objectName) return node;
3309
+ // no object name, give up
3310
+ if (!objectName) return;
3013
3311
 
3014
- const name = '__' + objectName + '_' + node.property.name;
3015
- if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3312
+ const name = '__' + objectName + '_' + node.property.name;
3313
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3016
3314
 
3017
- return {
3018
- type: 'Identifier',
3019
- name
3020
- };
3315
+ return {
3316
+ type: 'Identifier',
3317
+ name
3318
+ };
3319
+ })();
3320
+
3321
+ if (out) return out;
3021
3322
  }
3022
3323
 
3023
3324
  for (const x in node) {
@@ -3031,8 +3332,8 @@ const objectHack = node => {
3031
3332
  };
3032
3333
 
3033
3334
  const generateFunc = (scope, decl) => {
3034
- if (decl.async) return todo('async functions are not supported');
3035
- if (decl.generator) return todo('generator functions are not supported');
3335
+ if (decl.async) return todo(scope, 'async functions are not supported');
3336
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
3036
3337
 
3037
3338
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3038
3339
  const params = decl.params ?? [];
@@ -3048,6 +3349,11 @@ const generateFunc = (scope, decl) => {
3048
3349
  name
3049
3350
  };
3050
3351
 
3352
+ if (typedInput && decl.returnType) {
3353
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3354
+ innerScope.returns = [ valtypeBinary ];
3355
+ }
3356
+
3051
3357
  for (let i = 0; i < params.length; i++) {
3052
3358
  allocVar(innerScope, params[i].name, false);
3053
3359
 
@@ -3110,16 +3416,6 @@ const generateCode = (scope, decl) => {
3110
3416
  };
3111
3417
 
3112
3418
  const internalConstrs = {
3113
- Boolean: {
3114
- generate: (scope, decl) => {
3115
- if (decl.arguments.length === 0) return number(0);
3116
-
3117
- // should generate/run all args
3118
- return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3119
- },
3120
- type: TYPES.boolean
3121
- },
3122
-
3123
3419
  Array: {
3124
3420
  generate: (scope, decl, global, name) => {
3125
3421
  // new Array(i0, i1, ...)
@@ -3137,7 +3433,7 @@ const internalConstrs = {
3137
3433
 
3138
3434
  // todo: check in wasm instead of here
3139
3435
  const literalValue = arg.value ?? 0;
3140
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3436
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
3141
3437
 
3142
3438
  return [
3143
3439
  ...number(0, Valtype.i32),
@@ -3148,7 +3444,8 @@ const internalConstrs = {
3148
3444
  ...number(pointer)
3149
3445
  ];
3150
3446
  },
3151
- type: TYPES._array
3447
+ type: TYPES._array,
3448
+ length: 1
3152
3449
  },
3153
3450
 
3154
3451
  __Array_of: {
@@ -3160,7 +3457,94 @@ const internalConstrs = {
3160
3457
  }, global, name);
3161
3458
  },
3162
3459
  type: TYPES._array,
3460
+ notConstr: true,
3461
+ length: 0
3462
+ },
3463
+
3464
+ __Porffor_fastOr: {
3465
+ generate: (scope, decl) => {
3466
+ const out = [];
3467
+
3468
+ for (let i = 0; i < decl.arguments.length; i++) {
3469
+ out.push(
3470
+ ...generate(scope, decl.arguments[i]),
3471
+ Opcodes.i32_to_u,
3472
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3473
+ );
3474
+ }
3475
+
3476
+ return out;
3477
+ },
3478
+ type: TYPES.boolean,
3163
3479
  notConstr: true
3480
+ },
3481
+
3482
+ __Porffor_fastAnd: {
3483
+ generate: (scope, decl) => {
3484
+ const out = [];
3485
+
3486
+ for (let i = 0; i < decl.arguments.length; i++) {
3487
+ out.push(
3488
+ ...generate(scope, decl.arguments[i]),
3489
+ Opcodes.i32_to_u,
3490
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3491
+ );
3492
+ }
3493
+
3494
+ return out;
3495
+ },
3496
+ type: TYPES.boolean,
3497
+ notConstr: true
3498
+ },
3499
+
3500
+ Boolean: {
3501
+ generate: (scope, decl) => {
3502
+ // todo: boolean object when used as constructor
3503
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3504
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3505
+ },
3506
+ type: TYPES.boolean,
3507
+ length: 1
3508
+ },
3509
+
3510
+ __Math_max: {
3511
+ generate: (scope, decl) => {
3512
+ const out = [
3513
+ ...number(-Infinity)
3514
+ ];
3515
+
3516
+ for (let i = 0; i < decl.arguments.length; i++) {
3517
+ out.push(
3518
+ ...generate(scope, decl.arguments[i]),
3519
+ [ Opcodes.f64_max ]
3520
+ );
3521
+ }
3522
+
3523
+ return out;
3524
+ },
3525
+ type: TYPES.number,
3526
+ notConstr: true,
3527
+ length: 2
3528
+ },
3529
+
3530
+ __Math_min: {
3531
+ generate: (scope, decl) => {
3532
+ const out = [
3533
+ ...number(Infinity)
3534
+ ];
3535
+
3536
+ for (let i = 0; i < decl.arguments.length; i++) {
3537
+ out.push(
3538
+ ...generate(scope, decl.arguments[i]),
3539
+ [ Opcodes.f64_min ]
3540
+ );
3541
+ }
3542
+
3543
+ return out;
3544
+ },
3545
+ type: TYPES.number,
3546
+ notConstr: true,
3547
+ length: 2
3164
3548
  }
3165
3549
  };
3166
3550
 
@@ -3189,7 +3573,6 @@ export default program => {
3189
3573
  funcs = [];
3190
3574
  funcIndex = {};
3191
3575
  depth = [];
3192
- arrays = new Map();
3193
3576
  pages = new Map();
3194
3577
  data = [];
3195
3578
  currentFuncIndex = importedFuncs.length;
@@ -3203,6 +3586,10 @@ export default program => {
3203
3586
 
3204
3587
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
3205
3588
 
3589
+ globalThis.pageSize = PageSize;
3590
+ const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3591
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3592
+
3206
3593
  // set generic opcodes for current valtype
3207
3594
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
3208
3595
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -3211,10 +3598,10 @@ export default program => {
3211
3598
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
3212
3599
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
3213
3600
 
3214
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3215
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3216
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3217
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3601
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3602
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3603
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3604
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3218
3605
 
3219
3606
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
3220
3607
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -3227,10 +3614,6 @@ export default program => {
3227
3614
 
3228
3615
  program.id = { name: 'main' };
3229
3616
 
3230
- globalThis.pageSize = PageSize;
3231
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3232
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3233
-
3234
3617
  const scope = {
3235
3618
  locals: {},
3236
3619
  localInd: 0
@@ -3241,7 +3624,7 @@ export default program => {
3241
3624
  body: program.body
3242
3625
  };
3243
3626
 
3244
- if (Prefs.astLog) console.log(program.body.body);
3627
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3245
3628
 
3246
3629
  generateFunc(scope, program);
3247
3630
 
@@ -3258,7 +3641,11 @@ export default program => {
3258
3641
  }
3259
3642
 
3260
3643
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3261
- main.returns = [];
3644
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3645
+ main.wasm.splice(main.wasm.length - 1, 1);
3646
+ } else {
3647
+ main.returns = [];
3648
+ }
3262
3649
  }
3263
3650
 
3264
3651
  if (lastInst[0] === Opcodes.call) {