porffor 0.14.0-f67c123a1 → 0.16.0-08983b6b1

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 (45) hide show
  1. package/CONTRIBUTING.md +16 -10
  2. package/README.md +14 -30
  3. package/asur/index.js +1 -1
  4. package/compiler/2c.js +172 -59
  5. package/compiler/allocators.js +128 -0
  6. package/compiler/assemble.js +37 -5
  7. package/compiler/builtins/annexb_string.ts +1 -0
  8. package/compiler/builtins/array.ts +154 -7
  9. package/compiler/builtins/base64.ts +1 -0
  10. package/compiler/builtins/boolean.ts +3 -1
  11. package/compiler/builtins/console.ts +6 -0
  12. package/compiler/builtins/crypto.ts +1 -0
  13. package/compiler/builtins/date.ts +5 -30
  14. package/compiler/builtins/error.js +22 -0
  15. package/compiler/builtins/escape.ts +1 -2
  16. package/compiler/builtins/function.ts +2 -0
  17. package/compiler/builtins/int.ts +2 -0
  18. package/compiler/builtins/math.ts +410 -0
  19. package/compiler/builtins/number.ts +12 -21
  20. package/compiler/builtins/object.ts +2 -0
  21. package/compiler/builtins/porffor.d.ts +18 -0
  22. package/compiler/builtins/set.ts +22 -12
  23. package/compiler/builtins/string.ts +1 -0
  24. package/compiler/builtins/string_f64.ts +10 -0
  25. package/compiler/builtins/symbol.ts +62 -0
  26. package/compiler/builtins/z_ecma262.ts +62 -0
  27. package/compiler/builtins.js +50 -12
  28. package/compiler/codegen.js +826 -552
  29. package/compiler/cyclone.js +535 -0
  30. package/compiler/decompile.js +7 -1
  31. package/compiler/generated_builtins.js +635 -84
  32. package/compiler/havoc.js +93 -0
  33. package/compiler/index.js +108 -15
  34. package/compiler/opt.js +10 -44
  35. package/compiler/parse.js +3 -9
  36. package/compiler/pgo.js +212 -0
  37. package/compiler/precompile.js +17 -11
  38. package/compiler/prefs.js +13 -5
  39. package/compiler/prototype.js +200 -186
  40. package/compiler/wasmSpec.js +2 -2
  41. package/compiler/wrap.js +128 -44
  42. package/package.json +3 -5
  43. package/runner/index.js +31 -15
  44. package/runner/repl.js +18 -2
  45. /package/runner/{profiler.js → profile.js} +0 -0
@@ -1,5 +1,5 @@
1
- import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from './wasmSpec.js';
2
- import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from './encoding.js';
1
+ import { Blocktype, Opcodes, Valtype, ValtypeSize } from './wasmSpec.js';
2
+ import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector, read_signedLEB128 } 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';
@@ -9,15 +9,16 @@ import * as Rhemyn from '../rhemyn/compile.js';
9
9
  import parse from './parse.js';
10
10
  import { log } from './log.js';
11
11
  import Prefs from './prefs.js';
12
+ import makeAllocator from './allocators.js';
12
13
 
13
14
  let globals = {};
14
- let globalInd = 0;
15
15
  let tags = [];
16
16
  let funcs = [];
17
17
  let exceptions = [];
18
18
  let funcIndex = {};
19
19
  let currentFuncIndex = importedFuncs.length;
20
20
  let builtinFuncs = {}, builtinVars = {}, prototypeFuncs = {};
21
+ let allocator;
21
22
 
22
23
  class TodoError extends Error {
23
24
  constructor(message) {
@@ -32,19 +33,14 @@ const todo = (scope, msg, expectsValue = undefined) => {
32
33
 
33
34
  case 'runtime':
34
35
  return internalThrow(scope, 'TodoError', msg, expectsValue);
35
-
36
- // return [
37
- // ...debug(`todo! ${msg}`),
38
- // [ Opcodes.unreachable ]
39
- // ];
40
36
  }
41
37
  };
42
38
 
43
- const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
44
- const hasFuncWithName = name => {
45
- const func = funcs.find(x => x.name === name);
46
- return !!(func || builtinFuncs[name] || importedFuncs[name] || internalConstrs[name]);
47
- };
39
+ const isFuncType = type =>
40
+ type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
41
+ const hasFuncWithName = name =>
42
+ funcIndex[name] != null || builtinFuncs[name] != null || importedFuncs[name] != null || internalConstrs[name] != null;
43
+
48
44
  const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
49
45
  switch (decl.type) {
50
46
  case 'BinaryExpression':
@@ -140,18 +136,23 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
140
136
  case 'ArrayExpression':
141
137
  return generateArray(scope, decl, global, name);
142
138
 
139
+ case 'ObjectExpression':
140
+ return generateObject(scope, decl, global, name);
141
+
143
142
  case 'MemberExpression':
144
143
  return generateMember(scope, decl, global, name);
145
144
 
146
145
  case 'ExportNamedDeclaration':
147
- // hack to flag new func for export
148
- const funcsBefore = funcs.length;
146
+ const funcsBefore = funcs.map(x => x.name);
149
147
  generate(scope, decl.declaration);
150
148
 
151
- if (funcsBefore !== funcs.length) {
152
- // new func added
153
- const newFunc = funcs[funcs.length - 1];
154
- newFunc.export = true;
149
+ // set new funcs as exported
150
+ if (funcsBefore.length !== funcs.length) {
151
+ const newFuncs = funcs.filter(x => !funcsBefore.includes(x.name)).filter(x => !x.internal);
152
+
153
+ for (const x of newFuncs) {
154
+ x.export = true;
155
+ }
155
156
  }
156
157
 
157
158
  return [];
@@ -176,12 +177,6 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
176
177
  continue;
177
178
  }
178
179
 
179
- if (asm[0] === 'memory') {
180
- allocPage(scope, 'asm instrinsic');
181
- // todo: add to store/load offset insts
182
- continue;
183
- }
184
-
185
180
  let inst = Opcodes[asm[0].replace('.', '_')];
186
181
  if (inst == null) throw new Error(`inline asm: inst ${asm[0]} not found`);
187
182
 
@@ -200,19 +195,11 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
200
195
 
201
196
  __Porffor_bs: str => [
202
197
  ...makeString(scope, str, global, name, true),
203
-
204
- ...(name ? setType(scope, name, TYPES.bytestring) : [
205
- ...number(TYPES.bytestring, Valtype.i32),
206
- ...setLastType(scope)
207
- ])
198
+ ...(name ? setType(scope, name, TYPES.bytestring) : setLastType(scope, TYPES.bytestring))
208
199
  ],
209
200
  __Porffor_s: str => [
210
201
  ...makeString(scope, str, global, name, false),
211
-
212
- ...(name ? setType(scope, name, TYPES.string) : [
213
- ...number(TYPES.string, Valtype.i32),
214
- ...setLastType(scope)
215
- ])
202
+ ...(name ? setType(scope, name, TYPES.string) : setLastType(scope, TYPES.string))
216
203
  ],
217
204
  };
218
205
 
@@ -351,9 +338,7 @@ const generateReturn = (scope, decl) => {
351
338
 
352
339
  return [
353
340
  ...generate(scope, decl.argument),
354
- ...(scope.returnType != null ? [] : [
355
- ...getNodeType(scope, decl.argument)
356
- ]),
341
+ ...(scope.returnType != null ? [] : getNodeType(scope, decl.argument)),
357
342
  [ Opcodes.return ]
358
343
  ];
359
344
  };
@@ -368,7 +353,7 @@ const localTmp = (scope, name, type = valtypeBinary) => {
368
353
  };
369
354
 
370
355
  const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
371
- const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
356
+ const isIntToFloatOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
372
357
 
373
358
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
374
359
  const checks = {
@@ -385,10 +370,10 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
385
370
 
386
371
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
387
372
  // (like if we are in an if condition - very common)
388
- const leftIsInt = isFloatToIntOp(left[left.length - 1]);
389
- const rightIsInt = isFloatToIntOp(right[right.length - 1]);
373
+ const leftWasInt = isIntToFloatOp(left[left.length - 1]);
374
+ const rightWasInt = isIntToFloatOp(right[right.length - 1]);
390
375
 
391
- const canInt = leftIsInt && rightIsInt;
376
+ const canInt = leftWasInt && rightWasInt;
392
377
 
393
378
  if (canInt) {
394
379
  // remove int -> float conversions from left and right
@@ -402,13 +387,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
402
387
  [ Opcodes.if, Valtype.i32 ],
403
388
  ...right,
404
389
  // note type
405
- ...rightType,
406
- ...setLastType(scope),
390
+ ...setLastType(scope, rightType),
407
391
  [ Opcodes.else ],
408
392
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
409
393
  // note type
410
- ...leftType,
411
- ...setLastType(scope),
394
+ ...setLastType(scope, leftType),
412
395
  [ Opcodes.end ],
413
396
  Opcodes.i32_from
414
397
  ];
@@ -421,13 +404,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
421
404
  [ Opcodes.if, valtypeBinary ],
422
405
  ...right,
423
406
  // note type
424
- ...rightType,
425
- ...setLastType(scope),
407
+ ...setLastType(scope, rightType),
426
408
  [ Opcodes.else ],
427
409
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
428
410
  // note type
429
- ...leftType,
430
- ...setLastType(scope),
411
+ ...setLastType(scope, leftType),
431
412
  [ Opcodes.end ]
432
413
  ];
433
414
  };
@@ -442,63 +423,12 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
442
423
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
443
424
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
444
425
 
445
- if (assign && Prefs.aotPointerOpt) {
446
- const pointer = scope.arrays?.get(name ?? '$undeclared');
447
-
448
- return [
449
- // setup right
450
- ...right,
451
- Opcodes.i32_to_u,
452
- [ Opcodes.local_set, rightPointer ],
453
-
454
- // calculate length
455
- ...number(0, Valtype.i32), // base 0 for store later
456
-
457
- ...number(pointer, Valtype.i32),
458
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
459
- [ Opcodes.local_tee, leftLength ],
460
-
461
- [ Opcodes.local_get, rightPointer ],
462
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
463
- [ Opcodes.local_tee, rightLength ],
464
-
465
- [ Opcodes.i32_add ],
466
-
467
- // store length
468
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
469
-
470
- // copy right
471
- // dst = out pointer + length size + current length * sizeof valtype
472
- ...number(pointer + ValtypeSize.i32, Valtype.i32),
473
-
474
- [ Opcodes.local_get, leftLength ],
475
- ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
476
- [ Opcodes.i32_mul ],
477
- [ Opcodes.i32_add ],
478
-
479
- // src = right pointer + length size
480
- [ Opcodes.local_get, rightPointer ],
481
- ...number(ValtypeSize.i32, Valtype.i32),
482
- [ Opcodes.i32_add ],
483
-
484
- // size = right length * sizeof valtype
485
- [ Opcodes.local_get, rightLength ],
486
- ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
487
- [ Opcodes.i32_mul ],
488
-
489
- [ ...Opcodes.memory_copy, 0x00, 0x00 ],
490
-
491
- // return new string (page)
492
- ...number(pointer)
493
- ];
494
- }
495
-
496
426
  const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
497
427
 
498
428
  // alloc/assign array
499
- const [ , pointer ] = makeArray(scope, {
429
+ const [ out, pointer ] = makeArray(scope, {
500
430
  rawElements: new Array(0)
501
- }, global, name, true, 'i16');
431
+ }, assign ? false : global, assign ? undefined : name, true, 'i16', true);
502
432
 
503
433
  return [
504
434
  // setup left
@@ -512,24 +442,26 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
512
442
  [ Opcodes.local_set, rightPointer ],
513
443
 
514
444
  // calculate length
515
- ...number(0, Valtype.i32), // base 0 for store later
445
+ ...out,
516
446
 
517
447
  [ Opcodes.local_get, leftPointer ],
518
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
448
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
519
449
  [ Opcodes.local_tee, leftLength ],
520
450
 
521
451
  [ Opcodes.local_get, rightPointer ],
522
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
452
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
523
453
  [ Opcodes.local_tee, rightLength ],
524
454
 
525
455
  [ Opcodes.i32_add ],
526
456
 
527
457
  // store length
528
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
458
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ],
529
459
 
530
460
  // copy left
531
461
  // dst = out pointer + length size
532
- ...number(pointer + ValtypeSize.i32, Valtype.i32),
462
+ ...pointer,
463
+ ...number(ValtypeSize.i32, Valtype.i32),
464
+ [ Opcodes.i32_add ],
533
465
 
534
466
  // src = left pointer + length size
535
467
  [ Opcodes.local_get, leftPointer ],
@@ -542,7 +474,9 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
542
474
 
543
475
  // copy right
544
476
  // dst = out pointer + length size + left length * sizeof valtype
545
- ...number(pointer + ValtypeSize.i32, Valtype.i32),
477
+ ...pointer,
478
+ ...number(ValtypeSize.i32, Valtype.i32),
479
+ [ Opcodes.i32_add ],
546
480
 
547
481
  [ Opcodes.local_get, leftLength ],
548
482
  ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
@@ -562,7 +496,8 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
562
496
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
563
497
 
564
498
  // return new string (page)
565
- ...number(pointer)
499
+ ...pointer,
500
+ Opcodes.i32_from_u
566
501
  ];
567
502
  };
568
503
 
@@ -597,11 +532,11 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
597
532
 
598
533
  // get lengths
599
534
  [ Opcodes.local_get, leftPointer ],
600
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
535
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
601
536
  [ Opcodes.local_tee, leftLength ],
602
537
 
603
538
  [ Opcodes.local_get, rightPointer ],
604
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
539
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
605
540
 
606
541
  // fast path: check leftLength != rightLength
607
542
  [ Opcodes.i32_ne ],
@@ -657,9 +592,9 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
657
592
  [ Opcodes.i32_add ],
658
593
  [ Opcodes.local_tee, index ],
659
594
 
660
- // if index != index end (length * sizeof valtype), loop
595
+ // if index < index end (length * sizeof valtype), loop
661
596
  [ Opcodes.local_get, indexEnd ],
662
- [ Opcodes.i32_ne ],
597
+ [ Opcodes.i32_lt_s ],
663
598
  [ Opcodes.br_if, 0 ],
664
599
  [ Opcodes.end ],
665
600
 
@@ -677,38 +612,50 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
677
612
  ];
678
613
  };
679
614
 
680
- const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
681
- if (isFloatToIntOp(wasm[wasm.length - 1])) return [
682
- ...wasm,
683
- ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
684
- ];
615
+ const truthy = (scope, wasm, type, intIn = false, intOut = false, forceTruthyMode = undefined) => {
616
+ // if (isIntToFloatOp(wasm[wasm.length - 1])) return [
617
+ // ...wasm,
618
+ // ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
619
+ // ];
685
620
  // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
686
621
 
622
+ // todo/perf: use knownType and custom bytecode here instead of typeSwitch
623
+
687
624
  const useTmp = knownType(scope, type) == null;
688
625
  const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
689
626
 
690
- const def = [
691
- // if value != 0
692
- ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
627
+ const def = (truthyMode => {
628
+ if (truthyMode === 'full') return [
629
+ // if value != 0 or NaN
630
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
631
+ ...(intIn ? [ ] : [ Opcodes.i32_to ]),
693
632
 
694
- // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
695
- ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
633
+ [ Opcodes.i32_eqz ],
634
+ [ Opcodes.i32_eqz ],
696
635
 
697
- /* Opcodes.eqz,
698
- [ Opcodes.i32_eqz ],
699
- Opcodes.i32_from */
700
- ];
636
+ ...(intOut ? [] : [ Opcodes.i32_from ]),
637
+ ];
638
+
639
+ if (truthyMode === 'no_negative') return [
640
+ // if value != 0 or NaN, non-binary output. negative numbers not truthy :/
641
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
642
+ ...(intIn ? [] : [ Opcodes.i32_to ]),
643
+ ...(intOut ? [] : [ Opcodes.i32_from ])
644
+ ];
645
+
646
+ if (truthyMode === 'no_nan_negative') return [
647
+ // simpler and faster but makes NaN truthy and negative numbers not truthy,
648
+ // plus non-binary output
649
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
650
+ ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ])
651
+ ];
652
+ })(forceTruthyMode ?? Prefs.truthy ?? 'full');
701
653
 
702
654
  return [
703
655
  ...wasm,
704
656
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
705
657
 
706
658
  ...typeSwitch(scope, type, {
707
- // [TYPES.number]: def,
708
- [TYPES.array]: [
709
- // arrays are always truthy
710
- ...number(1, intOut ? Valtype.i32 : valtypeBinary)
711
- ],
712
659
  [TYPES.string]: [
713
660
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
714
661
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -744,10 +691,6 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
744
691
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
745
692
 
746
693
  ...typeSwitch(scope, type, {
747
- [TYPES.array]: [
748
- // arrays are always truthy
749
- ...number(0, intOut ? Valtype.i32 : valtypeBinary)
750
- ],
751
694
  [TYPES.string]: [
752
695
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
753
696
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -991,7 +934,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
991
934
  // if both are true
992
935
  [ Opcodes.i32_and ],
993
936
  [ Opcodes.if, Blocktype.void ],
994
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
937
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], false),
995
938
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
996
939
  [ Opcodes.br, 1 ],
997
940
  [ Opcodes.end ],
@@ -1015,12 +958,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
1015
958
  [ Opcodes.end ],
1016
959
  ]));
1017
960
 
1018
- // if not already in block, add a block
1019
- // if (endOut.length === 0) {
1020
- startOut.push(stringOnly([ Opcodes.block, Valtype.i32 ]));
1021
- // endOut.push(stringOnly([ Opcodes.end ]));
1022
- endOut.unshift(stringOnly([ Opcodes.end ]));
1023
- // }
961
+ // add a surrounding block
962
+ startOut.push(stringOnly([ Opcodes.block, Valtype.i32 ]));
963
+ endOut.unshift(stringOnly([ Opcodes.end ]));
1024
964
  }
1025
965
 
1026
966
  return finalize([
@@ -1040,14 +980,14 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
1040
980
  return out;
1041
981
  };
1042
982
 
1043
- const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1044
- return func({ name, params, locals, returns, localInd }, {
983
+ const asmFuncToAsm = (func, scope) => {
984
+ return func(scope, {
1045
985
  TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
1046
- builtin: name => {
1047
- let idx = funcIndex[name] ?? importedFuncs[name];
1048
- if (idx === undefined && builtinFuncs[name]) {
1049
- includeBuiltin(null, name);
1050
- idx = funcIndex[name];
986
+ builtin: n => {
987
+ let idx = funcIndex[n] ?? importedFuncs[n];
988
+ if (idx == null && builtinFuncs[n]) {
989
+ includeBuiltin(null, n);
990
+ idx = funcIndex[n];
1051
991
  }
1052
992
 
1053
993
  return idx;
@@ -1055,7 +995,7 @@ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals =
1055
995
  });
1056
996
  };
1057
997
 
1058
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
998
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits = [], returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false }) => {
1059
999
  const existing = funcs.find(x => x.name === name);
1060
1000
  if (existing) return existing;
1061
1001
 
@@ -1069,17 +1009,32 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1069
1009
 
1070
1010
  for (const x of _data) {
1071
1011
  const copy = { ...x };
1072
- copy.offset += pages.size * pageSize;
1012
+ if (copy.offset != null) copy.offset += pages.size * pageSize;
1073
1013
  data.push(copy);
1074
1014
  }
1075
1015
 
1076
- if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1016
+ const func = {
1017
+ name,
1018
+ params,
1019
+ locals,
1020
+ localInd: allLocals.length,
1021
+ returns,
1022
+ returnType,
1023
+ internal: true,
1024
+ index: currentFuncIndex++,
1025
+ table
1026
+ };
1027
+
1028
+ funcs.push(func);
1029
+ funcIndex[name] = func.index;
1030
+
1031
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, func);
1077
1032
 
1078
1033
  let baseGlobalIdx, i = 0;
1079
1034
  for (const type of globalTypes) {
1080
- if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
1035
+ if (baseGlobalIdx === undefined) baseGlobalIdx = globals['#ind'];
1081
1036
 
1082
- globals[globalNames[i] ?? `${name}_global_${i}`] = { idx: globalInd++, type, init: globalInits[i] ?? 0 };
1037
+ globals[globalNames[i] ?? `${name}_global_${i}`] = { idx: globals['#ind']++, type, init: globalInits[i] ?? 0 };
1083
1038
  i++;
1084
1039
  }
1085
1040
 
@@ -1092,19 +1047,18 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1092
1047
  }
1093
1048
  }
1094
1049
 
1095
- const func = {
1096
- name,
1097
- params,
1098
- locals,
1099
- returns,
1100
- returnType: returnType ?? TYPES.number,
1101
- wasm,
1102
- internal: true,
1103
- index: currentFuncIndex++
1104
- };
1050
+ if (table) {
1051
+ for (const inst of wasm) {
1052
+ if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
1053
+ inst.splice(2, 99);
1054
+ inst.push(...unsignedLEB128(allocPage({}, 'func argc lut') * pageSize));
1055
+ }
1056
+ }
1105
1057
 
1106
- funcs.push(func);
1107
- funcIndex[name] = func.index;
1058
+ funcs.table = true;
1059
+ }
1060
+
1061
+ func.wasm = wasm;
1108
1062
 
1109
1063
  return func;
1110
1064
  };
@@ -1196,9 +1150,10 @@ const getLastType = scope => {
1196
1150
  return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1197
1151
  };
1198
1152
 
1199
- const setLastType = scope => {
1200
- return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1201
- };
1153
+ const setLastType = (scope, type = []) => [
1154
+ ...(typeof type === 'number' ? number(type, Valtype.i32) : type),
1155
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1156
+ ];
1202
1157
 
1203
1158
  const getNodeType = (scope, node) => {
1204
1159
  const ret = (() => {
@@ -1244,7 +1199,7 @@ const getNodeType = (scope, node) => {
1244
1199
  const func = funcs.find(x => x.name === name);
1245
1200
 
1246
1201
  if (func) {
1247
- if (func.returnType) return func.returnType;
1202
+ if (func.returnType != null) return func.returnType;
1248
1203
  }
1249
1204
 
1250
1205
  if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
@@ -1259,7 +1214,9 @@ const getNodeType = (scope, node) => {
1259
1214
 
1260
1215
  const func = spl[spl.length - 1];
1261
1216
  const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
1262
- if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1217
+ if (protoFuncs.length === 1) {
1218
+ if (protoFuncs[0].returnType != null) return protoFuncs[0].returnType;
1219
+ }
1263
1220
  }
1264
1221
 
1265
1222
  if (name.startsWith('__Porffor_wasm_')) {
@@ -1306,7 +1263,15 @@ const getNodeType = (scope, node) => {
1306
1263
  }
1307
1264
 
1308
1265
  if (node.type === 'AssignmentExpression') {
1309
- return getNodeType(scope, node.right);
1266
+ const op = node.operator.slice(0, -1) || '=';
1267
+ if (op === '=') return getNodeType(scope, node.right);
1268
+
1269
+ return getNodeType(scope, {
1270
+ type: ['||', '&&', '??'].includes(op) ? 'LogicalExpression' : 'BinaryExpression',
1271
+ left: node.left,
1272
+ right: node.right,
1273
+ operator: op
1274
+ });
1310
1275
  }
1311
1276
 
1312
1277
  if (node.type === 'ArrayExpression') {
@@ -1325,23 +1290,6 @@ const getNodeType = (scope, node) => {
1325
1290
  if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) return TYPES.bytestring;
1326
1291
 
1327
1292
  return TYPES.number;
1328
-
1329
- // todo: string concat types
1330
- // if (node.operator !== '+') return TYPES.number;
1331
- // else return [
1332
- // // if left is string
1333
- // ...getNodeType(scope, node.left),
1334
- // ...number(TYPES.string, Valtype.i32),
1335
- // [ Opcodes.i32_eq ],
1336
-
1337
- // // if right is string
1338
- // ...getNodeType(scope, node.right),
1339
- // ...number(TYPES.string, Valtype.i32),
1340
- // [ Opcodes.i32_eq ],
1341
-
1342
- // // if either are true
1343
- // [ Opcodes.i32_or ],
1344
- // ];
1345
1293
  }
1346
1294
 
1347
1295
  if (node.type === 'UnaryExpression') {
@@ -1354,22 +1302,25 @@ const getNodeType = (scope, node) => {
1354
1302
  }
1355
1303
 
1356
1304
  if (node.type === 'MemberExpression') {
1357
- // hack: if something.name, string type
1358
- if (node.property.name === 'name') {
1359
- if (hasFuncWithName(node.object.name)) {
1360
- return TYPES.bytestring;
1361
- } else {
1362
- return TYPES.undefined;
1363
- }
1305
+ const name = node.property.name;
1306
+
1307
+ if (name === 'length') {
1308
+ if (hasFuncWithName(node.object.name)) return TYPES.number;
1309
+ if (Prefs.fastLength) return TYPES.number;
1364
1310
  }
1365
1311
 
1366
- // hack: if something.length, number type
1367
- if (node.property.name === 'length') return TYPES.number;
1312
+ const objectKnownType = knownType(scope, getNodeType(scope, node.object));
1313
+ if (objectKnownType != null) {
1314
+ if (name === 'length') {
1315
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(objectKnownType)) return TYPES.number;
1316
+ else return TYPES.undefined;
1317
+ }
1368
1318
 
1369
- // ts hack
1370
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1371
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.bytestring) return TYPES.bytestring;
1372
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.array) return TYPES.number;
1319
+ if (node.computed) {
1320
+ if (objectKnownType === TYPES.string) return TYPES.string;
1321
+ if (objectKnownType === TYPES.bytestring) return TYPES.bytestring;
1322
+ }
1323
+ }
1373
1324
 
1374
1325
  if (scope.locals['#last_type']) return getLastType(scope);
1375
1326
 
@@ -1433,24 +1384,23 @@ const countLeftover = wasm => {
1433
1384
 
1434
1385
  if (depth === 0)
1435
1386
  if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1436
- 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)) {}
1387
+ 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] < 0x04)) {}
1437
1388
  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++;
1438
1389
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1439
- else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1390
+ else if (inst[0] === Opcodes.memory_copy[0] && (inst[1] === Opcodes.memory_copy[1] || inst[1] === Opcodes.memory_init[1])) count -= 3;
1440
1391
  else if (inst[0] === Opcodes.return) count = 0;
1441
1392
  else if (inst[0] === Opcodes.call) {
1442
1393
  let func = funcs.find(x => x.index === inst[1]);
1443
- if (inst[1] === -1) {
1444
- // todo: count for calling self
1445
- } else if (!func && inst[1] < importedFuncs.length) {
1446
- count -= importedFuncs[inst[1]].params;
1447
- count += importedFuncs[inst[1]].returns;
1394
+ if (inst[1] < importedFuncs.length) {
1395
+ func = importedFuncs[inst[1]];
1396
+ count = count - func.params + func.returns;
1448
1397
  } else {
1449
- if (func) {
1450
- count -= func.params.length;
1451
- } else count--;
1452
- if (func) count += func.returns.length;
1398
+ count = count - func.params.length + func.returns.length;
1453
1399
  }
1400
+ } else if (inst[0] === Opcodes.call_indirect) {
1401
+ count--; // funcidx
1402
+ count -= inst[1] * 2; // params * 2 (typed)
1403
+ count += 2; // fixed return (value, type)
1454
1404
  } else count--;
1455
1405
 
1456
1406
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1468,7 +1418,7 @@ const disposeLeftover = wasm => {
1468
1418
  const generateExp = (scope, decl) => {
1469
1419
  const expression = decl.expression;
1470
1420
 
1471
- const out = generate(scope, expression, undefined, undefined, true);
1421
+ const out = generate(scope, expression, undefined, undefined, Prefs.optUnused);
1472
1422
  disposeLeftover(out);
1473
1423
 
1474
1424
  return out;
@@ -1559,16 +1509,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1559
1509
  out.splice(out.length - 1, 1);
1560
1510
 
1561
1511
  const finalStatement = parsed.body[parsed.body.length - 1];
1562
- out.push(
1563
- ...getNodeType(scope, finalStatement),
1564
- ...setLastType(scope)
1565
- );
1512
+ out.push(...setLastType(scope, getNodeType(scope, finalStatement)));
1566
1513
  } else if (countLeftover(out) === 0) {
1567
1514
  out.push(...number(UNDEFINED));
1568
- out.push(
1569
- ...number(TYPES.undefined, Valtype.i32),
1570
- ...setLastType(scope)
1571
- );
1515
+ out.push(...setLastType(scope, TYPES.undefined));
1572
1516
  }
1573
1517
 
1574
1518
  // if (lastInst && lastInst[0] === Opcodes.drop) {
@@ -1604,6 +1548,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1604
1548
 
1605
1549
  if (!funcIndex[rhemynName]) {
1606
1550
  const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1551
+ func.internal = true;
1607
1552
 
1608
1553
  funcIndex[func.name] = func.index;
1609
1554
  funcs.push(func);
@@ -1620,8 +1565,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1620
1565
  [ Opcodes.call, idx ],
1621
1566
  Opcodes.i32_from_u,
1622
1567
 
1623
- ...number(TYPES.boolean, Valtype.i32),
1624
- ...setLastType(scope)
1568
+ ...setLastType(scope, TYPES.boolean)
1625
1569
  ];
1626
1570
  }
1627
1571
 
@@ -1693,9 +1637,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1693
1637
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
1694
1638
  protoBC[x] = [
1695
1639
  ...RTArrayUtil.getLength(getPointer),
1696
-
1697
- ...number(TYPES.number, Valtype.i32),
1698
- ...setLastType(scope)
1640
+ ...setLastType(scope, TYPES.number)
1699
1641
  ];
1700
1642
  continue;
1701
1643
  }
@@ -1714,10 +1656,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1714
1656
  getI32: () => RTArrayUtil.getLengthI32(getPointer),
1715
1657
  set: value => RTArrayUtil.setLength(getPointer, value),
1716
1658
  setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
1717
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1659
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), getNodeType(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1718
1660
  return makeArray(scope, {
1719
1661
  rawElements: new Array(length)
1720
- }, _global, _name, true, itemType);
1662
+ }, _global, _name, true, itemType, true);
1721
1663
  }, () => {
1722
1664
  optUnused = true;
1723
1665
  return unusedValue;
@@ -1728,9 +1670,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1728
1670
  protoBC[x] = [
1729
1671
  [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1730
1672
  ...protoOut,
1731
-
1732
- ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1733
- ...setLastType(scope),
1673
+ ...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
1734
1674
  [ Opcodes.end ]
1735
1675
  ];
1736
1676
  }
@@ -1780,11 +1720,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1780
1720
 
1781
1721
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1782
1722
 
1783
- if (idx === undefined && name === scope.name) {
1784
- // hack: calling self, func generator will fix later
1785
- idx = -1;
1786
- }
1787
-
1788
1723
  if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1789
1724
  const wasmOps = {
1790
1725
  // pointer, align, offset
@@ -1806,7 +1741,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1806
1741
  f64_store: { imms: 2, args: [ true, false ], returns: 0 },
1807
1742
 
1808
1743
  // value
1809
- i32_const: { imms: 1, args: [], returns: 1 },
1744
+ i32_const: { imms: 1, args: [], returns: 0 },
1810
1745
  };
1811
1746
 
1812
1747
  const opName = name.slice('__Porffor_wasm_'.length);
@@ -1833,30 +1768,143 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1833
1768
 
1834
1769
  if (idx === undefined) {
1835
1770
  if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) {
1836
- if (!Prefs.indirectCalls) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1837
-
1838
1771
  const [ local, global ] = lookupName(scope, name);
1772
+ if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1773
+
1774
+ // todo: only works when function uses typedParams and typedReturns
1775
+
1776
+ const indirectMode = Prefs.indirectCallMode ?? 'vararg';
1777
+ // options: vararg, strict
1778
+ // - strict: simpler, smaller size usage, no func argc lut needed.
1779
+ // ONLY works when arg count of call == arg count of function being called
1780
+ // - vararg: large size usage, cursed.
1781
+ // works when arg count of call != arg count of function being called*
1782
+ // * most of the time, some edgecases
1783
+
1839
1784
  funcs.table = true;
1785
+ scope.table = true;
1786
+
1787
+ let args = decl.arguments;
1788
+ let out = [];
1789
+
1790
+ let locals = [];
1791
+
1792
+ if (indirectMode === 'vararg') {
1793
+ const minArgc = Prefs.indirectCallMinArgc ?? 3;
1794
+
1795
+ if (args.length < minArgc) {
1796
+ args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
1797
+ }
1798
+ }
1799
+
1800
+ for (let i = 0; i < args.length; i++) {
1801
+ const arg = args[i];
1802
+ out = out.concat(generate(scope, arg));
1803
+
1804
+ if (valtypeBinary !== Valtype.i32 && (
1805
+ (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1806
+ (importedFuncs[name] && name.startsWith('profile'))
1807
+ )) {
1808
+ out.push(Opcodes.i32_to);
1809
+ }
1810
+
1811
+ out = out.concat(getNodeType(scope, arg));
1812
+
1813
+ if (indirectMode === 'vararg') {
1814
+ const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
1815
+ const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
1816
+
1817
+ locals.push([valLocal, typeLocal]);
1818
+
1819
+ out.push(
1820
+ [ Opcodes.local_set, typeLocal ],
1821
+ [ Opcodes.local_set, valLocal ]
1822
+ );
1823
+ }
1824
+ }
1825
+
1826
+ if (indirectMode === 'strict') {
1827
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1828
+ [TYPES.function]: [
1829
+ ...out,
1830
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1831
+ Opcodes.i32_to_u,
1832
+ [ Opcodes.call_indirect, args.length, 0 ],
1833
+ ...setLastType(scope)
1834
+ ],
1835
+ default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1836
+ });
1837
+ }
1840
1838
 
1841
- // todo: only works when:
1842
- // 1. arg count matches arg count of function
1843
- // 2. function uses typedParams and typedReturns
1844
- return typeSwitch(scope, getNodeType(decl.callee), {
1839
+ // hi, I will now explain how vararg mode works:
1840
+ // wasm's indirect_call instruction requires you know the func type at compile-time
1841
+ // since we have varargs (variable argument count), we do not know it.
1842
+ // we could just store args in memory and not use wasm func args,
1843
+ // but that is slow (probably) and breaks js exports.
1844
+ // instead, we generate every* possibility of argc and use different indirect_call
1845
+ // ops for each one, with type depending on argc for that branch.
1846
+ // then we load the argc for the wanted function from a memory lut,
1847
+ // and call the branch with the matching argc we require.
1848
+ // sorry, yes it is very cursed (and size inefficient), but indirect calls
1849
+ // are kind of rare anyway (mostly callbacks) so I am not concerned atm.
1850
+ // *for argc 0-3, in future (todo:) the max number should be
1851
+ // dynamically changed to the max argc of any func in the js file.
1852
+
1853
+ const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
1854
+
1855
+ const gen = argc => {
1856
+ const out = [];
1857
+ for (let i = 0; i < argc; i++) {
1858
+ out.push(
1859
+ [ Opcodes.local_get, locals[i][0] ],
1860
+ [ Opcodes.local_get, locals[i][1] ]
1861
+ );
1862
+ }
1863
+
1864
+ out.push(
1865
+ [ Opcodes.local_get, funcLocal ],
1866
+ [ Opcodes.call_indirect, argc, 0 ],
1867
+ ...setLastType(scope)
1868
+ )
1869
+
1870
+ return out;
1871
+ };
1872
+
1873
+ const tableBc = {};
1874
+ for (let i = 0; i <= args.length; i++) {
1875
+ tableBc[i] = gen(i);
1876
+ }
1877
+
1878
+ // todo/perf: check if we should use br_table here or just generate our own big if..elses
1879
+
1880
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1845
1881
  [TYPES.function]: [
1882
+ ...out,
1883
+
1846
1884
  [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1847
- [ Opcodes.call_indirect ]
1885
+ Opcodes.i32_to_u,
1886
+ [ Opcodes.local_set, funcLocal ],
1887
+
1888
+ ...brTable([
1889
+ // get argc of func we are calling
1890
+ [ Opcodes.local_get, funcLocal ],
1891
+ ...number(ValtypeSize.i16, Valtype.i32),
1892
+ [ Opcodes.i32_mul ],
1893
+
1894
+ [ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
1895
+ ], tableBc, valtypeBinary)
1848
1896
  ],
1849
1897
  default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1850
1898
  });
1851
1899
  }
1900
+
1852
1901
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1853
1902
  }
1854
1903
 
1855
- const func = funcs.find(x => x.index === idx);
1856
-
1857
- const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1904
+ const func = funcs[idx - importedFuncs.length]; // idx === scope.index ? scope : funcs.find(x => x.index === idx);
1905
+ const userFunc = func && !func.internal;
1858
1906
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1859
- const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1907
+ const typedReturns = (userFunc && func.returnType == null) || builtinFuncs[name]?.typedReturns;
1860
1908
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1861
1909
 
1862
1910
  let args = decl.arguments;
@@ -1877,13 +1925,25 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1877
1925
  const arg = args[i];
1878
1926
  out = out.concat(generate(scope, arg));
1879
1927
 
1880
- if (valtypeBinary !== Valtype.i32 && (
1881
- (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1882
- (importedFuncs[name] && name.startsWith('profile'))
1883
- )) {
1928
+ // todo: this should be used instead of the too many args thing above (by removing that)
1929
+ if (i >= paramCount) {
1930
+ // over param count of func, drop arg
1931
+ out.push([ Opcodes.drop ]);
1932
+ continue;
1933
+ }
1934
+
1935
+ if (valtypeBinary !== Valtype.i32 &&
1936
+ (func && func.params[i * (typedParams ? 2 : 1)] === Valtype.i32)
1937
+ ) {
1884
1938
  out.push(Opcodes.i32_to);
1885
1939
  }
1886
1940
 
1941
+ if (valtypeBinary === Valtype.i32 &&
1942
+ (func && func.params[i * (typedParams ? 2 : 1)] === Valtype.f64)
1943
+ ) {
1944
+ out.push([ Opcodes.f64_convert_i32_s ]);
1945
+ }
1946
+
1887
1947
  if (typedParams) out = out.concat(getNodeType(scope, arg));
1888
1948
  }
1889
1949
 
@@ -1905,6 +1965,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1905
1965
  out.push(Opcodes.i32_from);
1906
1966
  }
1907
1967
 
1968
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.f64 && valtypeBinary === Valtype.i32) {
1969
+ out.push(Opcodes.i32_trunc_sat_f64_s);
1970
+ }
1971
+
1908
1972
  return out;
1909
1973
  };
1910
1974
 
@@ -1925,7 +1989,12 @@ const generateNew = (scope, decl, _global, _name) => {
1925
1989
  }, _global, _name);
1926
1990
  }
1927
1991
 
1928
- if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1992
+ if (
1993
+ (builtinFuncs[name] && !builtinFuncs[name].constr) ||
1994
+ (internalConstrs[name] && builtinFuncs[name].notConstr)
1995
+ ) return internalThrow(scope, 'TypeError', `${name} is not a constructor`, true);
1996
+
1997
+ if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`, true); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1929
1998
 
1930
1999
  return generateCall(scope, decl, _global, _name);
1931
2000
  };
@@ -1950,8 +2019,11 @@ const knownType = (scope, type) => {
1950
2019
  const idx = type[0][1];
1951
2020
 
1952
2021
  // type idx = var idx + 1
1953
- const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
1954
- if (v.metadata?.type != null) return v.metadata.type;
2022
+ const name = Object.values(scope.locals).find(x => x.idx === idx)?.name;
2023
+ if (name) {
2024
+ const local = scope.locals[name];
2025
+ if (local.metadata?.type != null) return v.metadata.type;
2026
+ }
1955
2027
  }
1956
2028
 
1957
2029
  return null;
@@ -1986,16 +2058,16 @@ const brTable = (input, bc, returns) => {
1986
2058
  }
1987
2059
 
1988
2060
  for (let i = 0; i < count; i++) {
1989
- if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2061
+ if (i === 0) out.push([ Opcodes.block, returns ]);
1990
2062
  else out.push([ Opcodes.block, Blocktype.void ]);
1991
2063
  }
1992
2064
 
1993
- const nums = keys.filter(x => +x);
2065
+ const nums = keys.filter(x => +x >= 0);
1994
2066
  const offset = Math.min(...nums);
1995
2067
  const max = Math.max(...nums);
1996
2068
 
1997
2069
  const table = [];
1998
- let br = 1;
2070
+ let br = 0;
1999
2071
 
2000
2072
  for (let i = offset; i <= max; i++) {
2001
2073
  // if branch for this num, go to that block
@@ -2019,10 +2091,8 @@ const brTable = (input, bc, returns) => {
2019
2091
  [ Opcodes.br_table, ...encodeVector(table), 0 ]
2020
2092
  );
2021
2093
 
2022
- // if you can guess why we sort the wrong way and then reverse
2023
- // (instead of just sorting the correct way)
2024
- // dm me and if you are correct and the first person
2025
- // I will somehow shout you out or something
2094
+ // sort the wrong way and then reverse
2095
+ // so strings ('default') are at the start before any numbers
2026
2096
  const orderedBc = keys.sort((a, b) => b - a).reverse();
2027
2097
 
2028
2098
  br = count - 1;
@@ -2035,10 +2105,9 @@ const brTable = (input, bc, returns) => {
2035
2105
  br--;
2036
2106
  }
2037
2107
 
2038
- return [
2039
- ...out,
2040
- [ Opcodes.end, 'br table end' ]
2041
- ];
2108
+ out.push([ Opcodes.end ]);
2109
+
2110
+ return out;
2042
2111
  };
2043
2112
 
2044
2113
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
@@ -2049,10 +2118,10 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2049
2118
  return bc[known] ?? bc.default;
2050
2119
  }
2051
2120
 
2052
- if (Prefs.typeswitchUseBrtable)
2121
+ if (Prefs.typeswitchBrtable)
2053
2122
  return brTable(type, bc, returns);
2054
2123
 
2055
- const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
2124
+ const tmp = localTmp(scope, '#typeswitch_tmp' + (Prefs.typeswitchUniqueTmp ? randId() : ''), Valtype.i32);
2056
2125
  const out = [
2057
2126
  ...type,
2058
2127
  [ Opcodes.local_set, tmp ],
@@ -2082,6 +2151,17 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2082
2151
  return out;
2083
2152
  };
2084
2153
 
2154
+ const typeIsOneOf = (type, types, valtype = Valtype.i32) => {
2155
+ const out = [];
2156
+
2157
+ for (let i = 0; i < types.length; i++) {
2158
+ out.push(...type, ...number(types[i], valtype), valtype === Valtype.f64 ? [ Opcodes.f64_eq ] : [ Opcodes.i32_eq ]);
2159
+ if (i !== 0) out.push([ Opcodes.i32_or ]);
2160
+ }
2161
+
2162
+ return out;
2163
+ };
2164
+
2085
2165
  const allocVar = (scope, name, global = false, type = true) => {
2086
2166
  const target = global ? globals : scope.locals;
2087
2167
 
@@ -2093,12 +2173,12 @@ const allocVar = (scope, name, global = false, type = true) => {
2093
2173
  return target[name].idx;
2094
2174
  }
2095
2175
 
2096
- let idx = global ? globalInd++ : scope.localInd++;
2176
+ let idx = global ? globals['#ind']++ : scope.localInd++;
2097
2177
  target[name] = { idx, type: valtypeBinary };
2098
2178
 
2099
2179
  if (type) {
2100
- let typeIdx = global ? globalInd++ : scope.localInd++;
2101
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2180
+ let typeIdx = global ? globals['#ind']++ : scope.localInd++;
2181
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32, name };
2102
2182
  }
2103
2183
 
2104
2184
  return idx;
@@ -2190,24 +2270,10 @@ const generateVar = (scope, decl) => {
2190
2270
  }
2191
2271
 
2192
2272
  if (x.init) {
2193
- // if (isFuncType(x.init.type)) {
2194
- // // let a = function () { ... }
2195
- // x.init.id = { name };
2196
-
2197
- // const func = generateFunc(scope, x.init);
2198
-
2199
- // out.push(
2200
- // ...number(func.index - importedFuncs.length),
2201
- // [ global ? Opcodes.global_set : Opcodes.local_set, idx ],
2202
-
2203
- // ...setType(scope, name, TYPES.function)
2204
- // );
2205
-
2206
- // continue;
2207
- // }
2273
+ const alreadyArray = scope.arrays?.get(name) != null;
2208
2274
 
2209
2275
  const generated = generate(scope, x.init, global, name);
2210
- if (scope.arrays?.get(name) != null) {
2276
+ if (!alreadyArray && scope.arrays?.get(name) != null) {
2211
2277
  // hack to set local as pointer before
2212
2278
  out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2213
2279
  if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
@@ -2259,19 +2325,12 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2259
2325
 
2260
2326
  // hack: .length setter
2261
2327
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
2262
- const name = decl.left.object.name;
2263
- const pointer = scope.arrays?.get(name);
2264
-
2265
- const aotPointer = Prefs.aotPointerOpt && pointer != null;
2266
-
2267
2328
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2268
2329
  const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
2269
2330
 
2270
2331
  return [
2271
- ...(aotPointer ? number(0, Valtype.i32) : [
2272
- ...generate(scope, decl.left.object),
2273
- Opcodes.i32_to_u
2274
- ]),
2332
+ ...generate(scope, decl.left.object),
2333
+ Opcodes.i32_to_u,
2275
2334
  ...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2276
2335
 
2277
2336
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
@@ -2282,7 +2341,7 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2282
2341
  [ Opcodes.local_tee, newValueTmp ],
2283
2342
 
2284
2343
  Opcodes.i32_to_u,
2285
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
2344
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ],
2286
2345
 
2287
2346
  [ Opcodes.local_get, newValueTmp ]
2288
2347
  ];
@@ -2290,59 +2349,38 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2290
2349
 
2291
2350
  // arr[i]
2292
2351
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2293
- const name = decl.left.object.name;
2294
- const pointer = scope.arrays?.get(name);
2295
-
2296
- const aotPointer = Prefs.aotPointerOpt && pointer != null;
2297
-
2298
2352
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2299
2353
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
2300
2354
 
2301
2355
  return [
2302
2356
  ...typeSwitch(scope, getNodeType(scope, decl.left.object), {
2303
2357
  [TYPES.array]: [
2304
- ...(aotPointer ? [] : [
2305
- ...generate(scope, decl.left.object),
2306
- Opcodes.i32_to_u
2307
- ]),
2358
+ ...generate(scope, decl.left.object),
2359
+ Opcodes.i32_to_u,
2308
2360
 
2309
2361
  // get index as valtype
2310
2362
  ...generate(scope, decl.left.property),
2311
2363
  Opcodes.i32_to_u,
2312
2364
 
2313
2365
  // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2314
- ...number(ValtypeSize[valtype], Valtype.i32),
2366
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2315
2367
  [ Opcodes.i32_mul ],
2316
- ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
2368
+ [ Opcodes.i32_add ],
2317
2369
  ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2318
2370
 
2319
2371
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2320
2372
  [ Opcodes.local_get, pointerTmp ],
2321
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2322
- ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2373
+ [ Opcodes.load, 0, ValtypeSize.i32 ]
2374
+ ], generate(scope, decl.right), [
2375
+ [ Opcodes.local_get, pointerTmp ],
2376
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 + ValtypeSize[valtype] ]
2377
+ ], getNodeType(scope, decl.right), false, name, true)),
2323
2378
  [ Opcodes.local_tee, newValueTmp ],
2324
2379
 
2325
- [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2380
+ [ Opcodes.store, 0, ValtypeSize.i32 ]
2326
2381
  ],
2327
2382
 
2328
2383
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
2329
-
2330
- // [TYPES.string]: [
2331
- // // turn into byte offset by * sizeof i16
2332
- // ...number(ValtypeSize.i16, Valtype.i32),
2333
- // [ Opcodes.i32_mul ],
2334
- // ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
2335
- // ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2336
-
2337
- // ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2338
- // [ Opcodes.local_get, pointerTmp ],
2339
- // [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2340
- // ], generate(scope, decl.right), number(TYPES.string, Valtype.i32), getNodeType(scope, decl.right))),
2341
- // [ Opcodes.local_tee, newValueTmp ],
2342
-
2343
- // Opcodes.i32_to_u,
2344
- // [ StoreOps.i16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2345
- // ]
2346
2384
  }, Blocktype.void),
2347
2385
 
2348
2386
  [ Opcodes.local_get, newValueTmp ]
@@ -2404,9 +2442,7 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2404
2442
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2405
2443
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2406
2444
 
2407
- // todo: string concat types
2408
-
2409
- ...setType(scope, name, TYPES.number)
2445
+ ...setType(scope, name, getNodeType(scope, decl))
2410
2446
  ];
2411
2447
  };
2412
2448
 
@@ -2420,7 +2456,7 @@ const generateUnary = (scope, decl) => {
2420
2456
  // * -1
2421
2457
 
2422
2458
  if (decl.prefix && decl.argument.type === 'Literal' && typeof decl.argument.value === 'number') {
2423
- // if -<N>, just return that
2459
+ // if -n, just return that as a const
2424
2460
  return number(-1 * decl.argument.value);
2425
2461
  }
2426
2462
 
@@ -2430,11 +2466,16 @@ const generateUnary = (scope, decl) => {
2430
2466
  ];
2431
2467
 
2432
2468
  case '!':
2469
+ const arg = decl.argument;
2470
+ if (arg.type === 'UnaryExpression' && arg.operator === '!') {
2471
+ // opt: !!x -> is x truthy
2472
+ return truthy(scope, generate(scope, arg.argument), getNodeType(scope, arg.argument), false, false);
2473
+ }
2474
+
2433
2475
  // !=
2434
- return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
2476
+ return falsy(scope, generate(scope, arg), getNodeType(scope, arg), false, false);
2435
2477
 
2436
2478
  case '~':
2437
- // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
2438
2479
  return [
2439
2480
  ...generate(scope, decl.argument),
2440
2481
  Opcodes.i32_to,
@@ -2499,6 +2540,7 @@ const generateUnary = (scope, decl) => {
2499
2540
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2500
2541
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2501
2542
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2543
+ [TYPES.symbol]: makeString(scope, 'symbol', false, '#typeof_result'),
2502
2544
 
2503
2545
  [TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2504
2546
 
@@ -2575,21 +2617,16 @@ const generateConditional = (scope, decl) => {
2575
2617
  out.push([ Opcodes.if, valtypeBinary ]);
2576
2618
  depth.push('if');
2577
2619
 
2578
- out.push(...generate(scope, decl.consequent));
2579
-
2580
- // note type
2581
2620
  out.push(
2582
- ...getNodeType(scope, decl.consequent),
2583
- ...setLastType(scope)
2621
+ ...generate(scope, decl.consequent),
2622
+ ...setLastType(scope, getNodeType(scope, decl.consequent))
2584
2623
  );
2585
2624
 
2586
2625
  out.push([ Opcodes.else ]);
2587
- out.push(...generate(scope, decl.alternate));
2588
2626
 
2589
- // note type
2590
2627
  out.push(
2591
- ...getNodeType(scope, decl.alternate),
2592
- ...setLastType(scope)
2628
+ ...generate(scope, decl.alternate),
2629
+ ...setLastType(scope, getNodeType(scope, decl.alternate))
2593
2630
  );
2594
2631
 
2595
2632
  out.push([ Opcodes.end ]);
@@ -2725,24 +2762,27 @@ const generateForOf = (scope, decl) => {
2725
2762
 
2726
2763
  // // todo: we should only do this for strings but we don't know at compile-time :(
2727
2764
  // hack: this is naughty and will break things!
2728
- let newOut = number(0, Valtype.f64), newPointer = -1;
2765
+ let newOut = number(0, Valtype.i32), newPointer = number(0, Valtype.i32);
2729
2766
  if (pages.hasAnyString) {
2730
2767
  // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2731
2768
  0, [ newOut, newPointer ] = makeArray(scope, {
2732
- rawElements: new Array(1)
2733
- }, isGlobal, leftName, true, 'i16');
2769
+ rawElements: new Array(0)
2770
+ }, isGlobal, leftName, true, 'i16', true);
2734
2771
  }
2735
2772
 
2736
2773
  // set type for local
2737
2774
  // todo: optimize away counter and use end pointer
2738
2775
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2739
2776
  [TYPES.array]: [
2740
- ...setType(scope, leftName, TYPES.number),
2741
-
2742
2777
  [ Opcodes.loop, Blocktype.void ],
2743
2778
 
2744
2779
  [ Opcodes.local_get, pointer ],
2745
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2780
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2781
+
2782
+ ...setType(scope, leftName, [
2783
+ [ Opcodes.local_get, pointer ],
2784
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
2785
+ ]),
2746
2786
 
2747
2787
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2748
2788
 
@@ -2751,9 +2791,9 @@ const generateForOf = (scope, decl) => {
2751
2791
  ...generate(scope, decl.body),
2752
2792
  [ Opcodes.end ],
2753
2793
 
2754
- // increment iter pointer by valtype size
2794
+ // increment iter pointer by valtype size + 1
2755
2795
  [ Opcodes.local_get, pointer ],
2756
- ...number(ValtypeSize[valtype], Valtype.i32),
2796
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2757
2797
  [ Opcodes.i32_add ],
2758
2798
  [ Opcodes.local_set, pointer ],
2759
2799
 
@@ -2774,23 +2814,28 @@ const generateForOf = (scope, decl) => {
2774
2814
  [TYPES.string]: [
2775
2815
  ...setType(scope, leftName, TYPES.string),
2776
2816
 
2777
- [ Opcodes.loop, Blocktype.void ],
2778
-
2779
2817
  // setup new/out array
2780
2818
  ...newOut,
2781
- [ Opcodes.drop ],
2782
2819
 
2783
- ...number(0, Valtype.i32), // base 0 for store after
2820
+ // set length to 1
2821
+ ...number(1, Valtype.i32),
2822
+ [ Opcodes.i32_store, 0, 0 ],
2823
+
2824
+ [ Opcodes.loop, Blocktype.void ],
2825
+
2826
+ // use as pointer for store later
2827
+ ...newPointer,
2784
2828
 
2785
2829
  // load current string ind {arg}
2786
2830
  [ Opcodes.local_get, pointer ],
2787
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2831
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
2788
2832
 
2789
2833
  // store to new string ind 0
2790
- [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2834
+ [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
2791
2835
 
2792
2836
  // return new string (page)
2793
- ...number(newPointer),
2837
+ ...newPointer,
2838
+ Opcodes.i32_from_u,
2794
2839
 
2795
2840
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2796
2841
 
@@ -2822,25 +2867,30 @@ const generateForOf = (scope, decl) => {
2822
2867
  [TYPES.bytestring]: [
2823
2868
  ...setType(scope, leftName, TYPES.bytestring),
2824
2869
 
2825
- [ Opcodes.loop, Blocktype.void ],
2826
-
2827
2870
  // setup new/out array
2828
2871
  ...newOut,
2829
- [ Opcodes.drop ],
2830
2872
 
2831
- ...number(0, Valtype.i32), // base 0 for store after
2873
+ // set length to 1
2874
+ ...number(1, Valtype.i32),
2875
+ [ Opcodes.i32_store, 0, 0 ],
2876
+
2877
+ [ Opcodes.loop, Blocktype.void ],
2878
+
2879
+ // use as pointer for store later
2880
+ ...newPointer,
2832
2881
 
2833
2882
  // load current string ind {arg}
2834
2883
  [ Opcodes.local_get, pointer ],
2835
2884
  [ Opcodes.local_get, counter ],
2836
2885
  [ Opcodes.i32_add ],
2837
- [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2886
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ],
2838
2887
 
2839
2888
  // store to new string ind 0
2840
- [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2889
+ [ Opcodes.i32_store8, 0, ValtypeSize.i32 ],
2841
2890
 
2842
2891
  // return new string (page)
2843
- ...number(newPointer),
2892
+ ...newPointer,
2893
+ Opcodes.i32_from_u,
2844
2894
 
2845
2895
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2846
2896
 
@@ -2869,6 +2919,44 @@ const generateForOf = (scope, decl) => {
2869
2919
  [ Opcodes.end ],
2870
2920
  [ Opcodes.end ]
2871
2921
  ],
2922
+ [TYPES.set]: [
2923
+ [ Opcodes.loop, Blocktype.void ],
2924
+
2925
+ [ Opcodes.local_get, pointer ],
2926
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2927
+
2928
+ ...setType(scope, leftName, [
2929
+ [ Opcodes.local_get, pointer ],
2930
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
2931
+ ]),
2932
+
2933
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2934
+
2935
+ [ Opcodes.block, Blocktype.void ],
2936
+ [ Opcodes.block, Blocktype.void ],
2937
+ ...generate(scope, decl.body),
2938
+ [ Opcodes.end ],
2939
+
2940
+ // increment iter pointer by valtype size + 1
2941
+ [ Opcodes.local_get, pointer ],
2942
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2943
+ [ Opcodes.i32_add ],
2944
+ [ Opcodes.local_set, pointer ],
2945
+
2946
+ // increment counter by 1
2947
+ [ Opcodes.local_get, counter ],
2948
+ ...number(1, Valtype.i32),
2949
+ [ Opcodes.i32_add ],
2950
+ [ Opcodes.local_tee, counter ],
2951
+
2952
+ // loop if counter != length
2953
+ [ Opcodes.local_get, length ],
2954
+ [ Opcodes.i32_ne ],
2955
+ [ Opcodes.br_if, 1 ],
2956
+
2957
+ [ Opcodes.end ],
2958
+ [ Opcodes.end ]
2959
+ ],
2872
2960
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2873
2961
  }, Blocktype.void));
2874
2962
 
@@ -2970,14 +3058,18 @@ const generateThrow = (scope, decl) => {
2970
3058
  };
2971
3059
 
2972
3060
  const generateTry = (scope, decl) => {
2973
- if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
3061
+ // todo: handle control-flow pre-exit for finally
3062
+ // "Immediately before a control-flow statement (return, throw, break, continue) is executed in the try block or catch block."
2974
3063
 
2975
3064
  const out = [];
2976
3065
 
3066
+ const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
3067
+
2977
3068
  out.push([ Opcodes.try, Blocktype.void ]);
2978
3069
  depth.push('try');
2979
3070
 
2980
3071
  out.push(...generate(scope, decl.block));
3072
+ out.push(...finalizer);
2981
3073
 
2982
3074
  if (decl.handler) {
2983
3075
  depth.pop();
@@ -2985,6 +3077,7 @@ const generateTry = (scope, decl) => {
2985
3077
 
2986
3078
  out.push([ Opcodes.catch_all ]);
2987
3079
  out.push(...generate(scope, decl.handler.body));
3080
+ out.push(...finalizer);
2988
3081
  }
2989
3082
 
2990
3083
  out.push([ Opcodes.end ]);
@@ -3012,18 +3105,6 @@ const allocPage = (scope, reason, type) => {
3012
3105
  scope.pages ??= new Map();
3013
3106
  scope.pages.set(reason, { ind, type });
3014
3107
 
3015
- if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
3016
-
3017
- return ind;
3018
- };
3019
-
3020
- // todo: add scope.pages
3021
- const freePage = reason => {
3022
- const { ind } = pages.get(reason);
3023
- pages.delete(reason);
3024
-
3025
- if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
3026
-
3027
3108
  return ind;
3028
3109
  };
3029
3110
 
@@ -3061,34 +3142,58 @@ const compileBytes = (val, itemType) => {
3061
3142
  }
3062
3143
  };
3063
3144
 
3064
- const getAllocType = itemType => {
3065
- switch (itemType) {
3066
- case 'i8': return 'bytestring';
3067
- case 'i16': return 'string';
3145
+ const makeData = (scope, elements, offset = null, itemType, initEmpty) => {
3146
+ const length = elements.length;
3147
+
3148
+ // if length is 0 memory/data will just be 0000... anyway
3149
+ if (length === 0) return false;
3150
+
3151
+ let bytes = compileBytes(length, 'i32');
3152
+
3153
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3154
+ if (elements[i] == null) continue;
3068
3155
 
3069
- default: return 'array';
3156
+ bytes.push(...compileBytes(elements[i], itemType));
3070
3157
  }
3158
+
3159
+ const obj = { bytes };
3160
+ if (offset != null) obj.offset = offset;
3161
+
3162
+ const idx = data.push(obj) - 1;
3163
+
3164
+ scope.data ??= [];
3165
+ scope.data.push(idx);
3166
+
3167
+ return { idx, size: bytes.length };
3071
3168
  };
3072
3169
 
3073
- const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
3170
+ const printStaticStr = str => {
3074
3171
  const out = [];
3075
3172
 
3076
- scope.arrays ??= new Map();
3077
-
3078
- let firstAssign = false;
3079
- if (!scope.arrays.has(name) || name === '$undeclared') {
3080
- firstAssign = true;
3173
+ for (let i = 0; i < str.length; i++) {
3174
+ out.push(
3175
+ // ...number(str.charCodeAt(i)),
3176
+ ...number(str.charCodeAt(i), Valtype.i32),
3177
+ Opcodes.i32_from_u,
3178
+ [ Opcodes.call, importedFuncs.printChar ]
3179
+ );
3180
+ }
3081
3181
 
3082
- // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
3083
- const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
3182
+ return out;
3183
+ };
3084
3184
 
3085
- if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
3086
- else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3185
+ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype, intOut = false, typed = false) => {
3186
+ if (itemType !== 'i16' && itemType !== 'i8') {
3187
+ pages.hasArray = true;
3188
+ } else {
3189
+ pages.hasAnyString = true;
3190
+ if (itemType === 'i8') pages.hasByteString = true;
3191
+ else pages.hasString = true;
3087
3192
  }
3088
3193
 
3089
- const pointer = scope.arrays.get(name);
3194
+ const out = [];
3090
3195
 
3091
- const local = global ? globals[name] : scope.locals[name];
3196
+ const uniqueName = name === '$undeclared' ? name + randId() : name;
3092
3197
 
3093
3198
  const useRawElements = !!decl.rawElements;
3094
3199
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -3096,68 +3201,167 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3096
3201
  const valtype = itemTypeToValtype[itemType];
3097
3202
  const length = elements.length;
3098
3203
 
3099
- if (firstAssign && useRawElements && !Prefs.noData) {
3100
- // if length is 0 memory/data will just be 0000... anyway
3101
- if (length !== 0) {
3102
- let bytes = compileBytes(length, 'i32');
3204
+ const allocated = allocator.alloc({ scope, pages, globals, asmFunc, funcIndex }, uniqueName, { itemType });
3205
+
3206
+ let pointer = allocated;
3207
+ if (allocator.constructor.name !== 'StaticAllocator') {
3208
+ // const tmp = localTmp(scope, '#makearray_pointer' + uniqueName, Valtype.i32);
3209
+ const tmp = localTmp(scope, '#makearray_pointer' + name, Valtype.i32);
3210
+ out.push(
3211
+ ...allocated,
3212
+ [ Opcodes.local_set, tmp ]
3213
+ );
3214
+
3215
+ if (Prefs.runtimeAllocLog) out.push(
3216
+ ...printStaticStr(`${name}: `),
3217
+
3218
+ [ Opcodes.local_get, tmp ],
3219
+ Opcodes.i32_from_u,
3220
+ [ Opcodes.call, 0 ],
3221
+
3222
+ ...number(10),
3223
+ [ Opcodes.call, 1 ]
3224
+ );
3103
3225
 
3104
- if (!initEmpty) for (let i = 0; i < length; i++) {
3105
- if (elements[i] == null) continue;
3226
+ pointer = [ [ Opcodes.local_get, tmp ] ];
3106
3227
 
3107
- bytes.push(...compileBytes(elements[i], itemType));
3228
+ if (Prefs.data && useRawElements) {
3229
+ const data = makeData(scope, elements, null, itemType, initEmpty);
3230
+ if (data) {
3231
+ // init data
3232
+ out.push(
3233
+ ...pointer,
3234
+ ...number(0, Valtype.i32),
3235
+ ...number(data.size, Valtype.i32),
3236
+ [ ...Opcodes.memory_init, ...unsignedLEB128(data.idx), 0 ]
3237
+ );
3108
3238
  }
3109
3239
 
3110
- const ind = data.push({
3111
- offset: pointer,
3112
- bytes
3113
- }) - 1;
3240
+ // return pointer in out
3241
+ out.push(
3242
+ ...pointer,
3243
+ ...(!intOut ? [ Opcodes.i32_from_u ] : [])
3244
+ );
3114
3245
 
3115
- scope.data ??= [];
3116
- scope.data.push(ind);
3246
+ return [ out, pointer ];
3117
3247
  }
3248
+ } else {
3249
+ const rawPtr = read_signedLEB128(pointer[0].slice(1));
3118
3250
 
3119
- // local value as pointer
3120
- out.push(...number(pointer));
3251
+ scope.arrays ??= new Map();
3252
+ const firstAssign = !scope.arrays.has(uniqueName);
3253
+ if (firstAssign) scope.arrays.set(uniqueName, rawPtr);
3121
3254
 
3122
- return [ out, pointer ];
3123
- }
3255
+ if (Prefs.data && firstAssign && useRawElements) {
3256
+ makeData(scope, elements, rawPtr, itemType, initEmpty);
3124
3257
 
3125
- const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
3126
- if (pointerTmp != null) {
3127
- out.push(
3128
- [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
3129
- Opcodes.i32_to_u,
3130
- [ Opcodes.local_set, pointerTmp ]
3131
- );
3258
+ // local value as pointer
3259
+ return [ number(rawPtr, intOut ? Valtype.i32 : valtypeBinary), pointer ];
3260
+ }
3261
+
3262
+ const local = global ? globals[name] : scope.locals[name];
3263
+ const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
3264
+ if (pointerTmp != null) {
3265
+ out.push(
3266
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
3267
+ Opcodes.i32_to_u,
3268
+ [ Opcodes.local_set, pointerTmp ]
3269
+ );
3270
+
3271
+ pointer = [ [ Opcodes.local_get, pointerTmp ] ];
3272
+ }
3132
3273
  }
3133
3274
 
3134
- const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3135
3275
 
3136
- // store length as 0th array
3276
+ // store length
3137
3277
  out.push(
3138
- ...pointerWasm,
3278
+ ...pointer,
3139
3279
  ...number(length, Valtype.i32),
3140
3280
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
3141
3281
  );
3142
3282
 
3143
3283
  const storeOp = StoreOps[itemType];
3144
-
3284
+ const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
3145
3285
  if (!initEmpty) for (let i = 0; i < length; i++) {
3146
3286
  if (elements[i] == null) continue;
3147
3287
 
3288
+ const offset = ValtypeSize.i32 + i * sizePerEl;
3148
3289
  out.push(
3149
- ...pointerWasm,
3290
+ ...pointer,
3150
3291
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
3151
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3292
+ [ storeOp, 0, ...unsignedLEB128(offset) ],
3293
+ ...(!typed ? [] : [ // typed presumes !useRawElements
3294
+ ...pointer,
3295
+ ...getNodeType(scope, elements[i]),
3296
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(offset + ValtypeSize[itemType]) ]
3297
+ ])
3152
3298
  );
3153
3299
  }
3154
3300
 
3155
3301
  // local value as pointer
3156
- out.push(...pointerWasm, Opcodes.i32_from_u);
3302
+ out.push(...pointer);
3303
+ if (!intOut) out.push(Opcodes.i32_from_u);
3157
3304
 
3158
3305
  return [ out, pointer ];
3159
3306
  };
3160
3307
 
3308
+ const storeArray = (scope, array, index, element) => {
3309
+ if (!Array.isArray(element)) element = generate(scope, element);
3310
+ if (typeof index === 'number') index = number(index);
3311
+
3312
+ const offset = localTmp(scope, '#storeArray_offset', Valtype.i32);
3313
+
3314
+ return [
3315
+ // calculate offset
3316
+ ...index,
3317
+ Opcodes.i32_to_u,
3318
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3319
+ [ Opcodes.i32_mul ],
3320
+
3321
+ ...array,
3322
+ Opcodes.i32_to_u,
3323
+ [ Opcodes.i32_add ],
3324
+ [ Opcodes.local_set, offset ],
3325
+
3326
+ // store value
3327
+ [ Opcodes.local_get, offset ],
3328
+ ...generate(scope, element),
3329
+ [ Opcodes.store, 0, ValtypeSize.i32 ],
3330
+
3331
+ // store type
3332
+ [ Opcodes.local_get, offset ],
3333
+ ...getNodeType(scope, element),
3334
+ [ Opcodes.i32_store8, 0, ValtypeSize.i32 + ValtypeSize[valtype] ]
3335
+ ];
3336
+ };
3337
+
3338
+ const loadArray = (scope, array, index) => {
3339
+ if (typeof index === 'number') index = number(index);
3340
+
3341
+ const offset = localTmp(scope, '#loadArray_offset', Valtype.i32);
3342
+
3343
+ return [
3344
+ // calculate offset
3345
+ ...index,
3346
+ Opcodes.i32_to_u,
3347
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3348
+ [ Opcodes.i32_mul ],
3349
+
3350
+ ...array,
3351
+ Opcodes.i32_to_u,
3352
+ [ Opcodes.i32_add ],
3353
+ [ Opcodes.local_set, offset ],
3354
+
3355
+ // load value
3356
+ [ Opcodes.local_get, offset ],
3357
+ [ Opcodes.load, 0, ValtypeSize.i32 ],
3358
+
3359
+ // load type
3360
+ [ Opcodes.local_get, offset ],
3361
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 + ValtypeSize[valtype] ]
3362
+ ];
3363
+ };
3364
+
3161
3365
  const byteStringable = str => {
3162
3366
  if (!Prefs.bytestring) return false;
3163
3367
 
@@ -3186,14 +3390,25 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
3186
3390
  };
3187
3391
 
3188
3392
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
3189
- return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
3393
+ return makeArray(scope, decl, global, name, initEmpty, valtype, false, true)[0];
3190
3394
  };
3191
3395
 
3192
- export const generateMember = (scope, decl, _global, _name) => {
3193
- const name = decl.object.name;
3194
- const pointer = scope.arrays?.get(name);
3396
+ const generateObject = (scope, decl, global = false, name = '$undeclared') => {
3397
+ if (decl.properties.length > 0) return todo(scope, 'objects are not supported yet', true);
3195
3398
 
3196
- const aotPointer = Prefs.aotPointerOpt && pointer != null;
3399
+ return [
3400
+ ...number(1),
3401
+ ...setLastType(scope, TYPES.object)
3402
+ ];
3403
+ };
3404
+
3405
+ const withType = (scope, wasm, type) => [
3406
+ ...wasm,
3407
+ ...setLastType(scope, type)
3408
+ ];
3409
+
3410
+ const generateMember = (scope, decl, _global, _name) => {
3411
+ const name = decl.object.name;
3197
3412
 
3198
3413
  // hack: .name
3199
3414
  if (decl.property.name === 'name') {
@@ -3203,9 +3418,9 @@ export const generateMember = (scope, decl, _global, _name) => {
3203
3418
  // eg: __String_prototype_toLowerCase -> toLowerCase
3204
3419
  if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
3205
3420
 
3206
- return makeString(scope, nameProp, _global, _name, true);
3421
+ return withType(scope, makeString(scope, nameProp, _global, _name, true), TYPES.bytestring);
3207
3422
  } else {
3208
- return generate(scope, DEFAULT_VALUE);
3423
+ return withType(scope, number(0), TYPES.undefined);
3209
3424
  }
3210
3425
  }
3211
3426
 
@@ -3213,9 +3428,8 @@ export const generateMember = (scope, decl, _global, _name) => {
3213
3428
  if (decl.property.name === 'length') {
3214
3429
  const func = funcs.find(x => x.name === name);
3215
3430
  if (func) {
3216
- const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3217
- const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3218
- return number(typedParams ? func.params.length / 2 : func.params.length);
3431
+ const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
3432
+ return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3219
3433
  }
3220
3434
 
3221
3435
  if (builtinFuncs[name + '$constructor']) {
@@ -3225,65 +3439,110 @@ export const generateMember = (scope, decl, _global, _name) => {
3225
3439
  const constructorFunc = builtinFuncs[name + '$constructor'];
3226
3440
  const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3227
3441
 
3228
- return number(Math.max(regularParams, constructorParams));
3442
+ return withType(scope, number(Math.max(regularParams, constructorParams)), TYPES.number);
3229
3443
  }
3230
3444
 
3231
- if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3232
- if (importedFuncs[name]) return number(importedFuncs[name].params);
3233
- if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3445
+ if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
3446
+ if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params.length ?? importedFuncs[name].params), TYPES.number);
3447
+ if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
3448
+
3449
+ if (Prefs.fastLength) {
3450
+ // presume valid length object
3451
+ return [
3452
+ ...generate(scope, decl.object),
3453
+ Opcodes.i32_to_u,
3454
+
3455
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
3456
+ Opcodes.i32_from_u
3457
+ ];
3458
+ }
3459
+
3460
+ const type = getNodeType(scope, decl.object);
3461
+ const known = knownType(scope, type);
3462
+ if (known != null) {
3463
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
3464
+ ...generate(scope, decl.object),
3465
+ Opcodes.i32_to_u,
3466
+
3467
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
3468
+ Opcodes.i32_from_u
3469
+ ];
3470
+
3471
+ return number(0);
3472
+ }
3234
3473
 
3235
3474
  return [
3236
- ...(aotPointer ? number(0, Valtype.i32) : [
3475
+ ...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
3476
+ [ Opcodes.if, valtypeBinary ],
3237
3477
  ...generate(scope, decl.object),
3238
- Opcodes.i32_to_u
3239
- ]),
3478
+ Opcodes.i32_to_u,
3479
+
3480
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
3481
+ Opcodes.i32_from_u,
3240
3482
 
3241
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128((aotPointer ? pointer : 0)) ],
3242
- Opcodes.i32_from_u
3483
+ ...setLastType(scope, TYPES.number),
3484
+ [ Opcodes.else ],
3485
+ ...number(0),
3486
+ ...setLastType(scope, TYPES.undefined),
3487
+ [ Opcodes.end ]
3243
3488
  ];
3244
3489
  }
3245
3490
 
3491
+ // todo: generate this array procedurally during builtinFuncs creation
3492
+ if (['size', 'description'].includes(decl.property.name)) {
3493
+ const bc = {};
3494
+ const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3495
+
3496
+ if (cands.length > 0) {
3497
+ for (const x of cands) {
3498
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
3499
+ if (type == null) continue;
3500
+
3501
+ bc[type] = generateCall(scope, {
3502
+ callee: {
3503
+ type: 'Identifier',
3504
+ name: x
3505
+ },
3506
+ arguments: [ decl.object ],
3507
+ _protoInternalCall: true
3508
+ });
3509
+ }
3510
+ }
3511
+
3512
+ return typeSwitch(scope, getNodeType(scope, decl.object), {
3513
+ ...bc,
3514
+ default: withType(scope, number(0), TYPES.undefined)
3515
+ }, valtypeBinary);
3516
+ }
3517
+
3246
3518
  const object = generate(scope, decl.object);
3247
3519
  const property = generate(scope, decl.property);
3248
3520
 
3249
3521
  // // todo: we should only do this for strings but we don't know at compile-time :(
3250
3522
  // hack: this is naughty and will break things!
3251
- let newOut = number(0, valtypeBinary), newPointer = -1;
3252
- if (pages.hasAnyString) {
3523
+ let newOut = number(0, Valtype.i32), newPointer = number(0, Valtype.i32);
3524
+ if (pages.hasAnyString && knownType(scope, getNodeType(scope, decl.object)) !== TYPES.array) {
3525
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
3253
3526
  0, [ newOut, newPointer ] = makeArray(scope, {
3254
- rawElements: new Array(1)
3255
- }, _global, _name, true, 'i16');
3527
+ rawElements: new Array(0)
3528
+ }, _global, _name, true, 'i16', true);
3256
3529
  }
3257
3530
 
3258
3531
  return typeSwitch(scope, getNodeType(scope, decl.object), {
3259
3532
  [TYPES.array]: [
3260
- // get index as valtype
3261
- ...property,
3262
-
3263
- // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
3264
- Opcodes.i32_to_u,
3265
- ...number(ValtypeSize[valtype], Valtype.i32),
3266
- [ Opcodes.i32_mul ],
3267
-
3268
- ...(aotPointer ? [] : [
3269
- ...object,
3270
- Opcodes.i32_to_u,
3271
- [ Opcodes.i32_add ]
3272
- ]),
3273
-
3274
- // read from memory
3275
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3276
-
3277
- ...number(TYPES.number, Valtype.i32),
3533
+ ...loadArray(scope, object, property),
3278
3534
  ...setLastType(scope)
3279
3535
  ],
3280
-
3281
3536
  [TYPES.string]: [
3282
3537
  // setup new/out array
3283
3538
  ...newOut,
3284
- [ Opcodes.drop ],
3285
3539
 
3286
- ...number(0, Valtype.i32), // base 0 for store later
3540
+ // set length to 1
3541
+ ...number(1, Valtype.i32),
3542
+ [ Opcodes.i32_store, 0, 0 ],
3543
+
3544
+ // use as pointer for store later
3545
+ ...newPointer,
3287
3546
 
3288
3547
  ...property,
3289
3548
  Opcodes.i32_to_u,
@@ -3291,58 +3550,56 @@ export const generateMember = (scope, decl, _global, _name) => {
3291
3550
  ...number(ValtypeSize.i16, Valtype.i32),
3292
3551
  [ Opcodes.i32_mul ],
3293
3552
 
3294
- ...(aotPointer ? [] : [
3295
- ...object,
3296
- Opcodes.i32_to_u,
3297
- [ Opcodes.i32_add ]
3298
- ]),
3553
+ ...object,
3554
+ Opcodes.i32_to_u,
3555
+ [ Opcodes.i32_add ],
3299
3556
 
3300
3557
  // load current string ind {arg}
3301
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3558
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
3302
3559
 
3303
3560
  // store to new string ind 0
3304
- [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3561
+ [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
3305
3562
 
3306
3563
  // return new string (page)
3307
- ...number(newPointer),
3308
-
3309
- ...number(TYPES.string, Valtype.i32),
3310
- ...setLastType(scope)
3564
+ ...newPointer,
3565
+ Opcodes.i32_from_u,
3566
+ ...setLastType(scope, TYPES.string)
3311
3567
  ],
3312
3568
  [TYPES.bytestring]: [
3313
3569
  // setup new/out array
3314
3570
  ...newOut,
3315
- [ Opcodes.drop ],
3316
3571
 
3317
- ...number(0, Valtype.i32), // base 0 for store later
3572
+ // set length to 1
3573
+ ...number(1, Valtype.i32),
3574
+ [ Opcodes.i32_store, 0, 0 ],
3575
+
3576
+ // use as pointer for store later
3577
+ ...newPointer,
3318
3578
 
3319
3579
  ...property,
3320
3580
  Opcodes.i32_to_u,
3321
3581
 
3322
- ...(aotPointer ? [] : [
3323
- ...object,
3324
- Opcodes.i32_to_u,
3325
- [ Opcodes.i32_add ]
3326
- ]),
3582
+ ...object,
3583
+ Opcodes.i32_to_u,
3584
+ [ Opcodes.i32_add ],
3327
3585
 
3328
3586
  // load current string ind {arg}
3329
- [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3587
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ],
3330
3588
 
3331
3589
  // store to new string ind 0
3332
- [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3590
+ [ Opcodes.i32_store8, 0, ValtypeSize.i32 ],
3333
3591
 
3334
3592
  // return new string (page)
3335
- ...number(newPointer),
3336
-
3337
- ...number(TYPES.bytestring, Valtype.i32),
3338
- ...setLastType(scope)
3593
+ ...newPointer,
3594
+ Opcodes.i32_from_u,
3595
+ ...setLastType(scope, TYPES.bytestring)
3339
3596
  ],
3340
3597
 
3341
3598
  default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
3342
3599
  });
3343
3600
  };
3344
3601
 
3345
- const randId = () => Math.random().toString(16).slice(0, -4);
3602
+ const randId = () => Math.random().toString(16).slice(1, -2).padEnd(12, '0');
3346
3603
 
3347
3604
  const objectHack = node => {
3348
3605
  if (!node) return node;
@@ -3360,7 +3617,7 @@ const objectHack = node => {
3360
3617
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3361
3618
 
3362
3619
  // if .name or .length, give up (hack within a hack!)
3363
- if (['name', 'length'].includes(node.property.name)) {
3620
+ if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
3364
3621
  node.object = objectHack(node.object);
3365
3622
  return;
3366
3623
  }
@@ -3394,36 +3651,59 @@ const generateFunc = (scope, decl) => {
3394
3651
  if (decl.async) return todo(scope, 'async functions are not supported');
3395
3652
  if (decl.generator) return todo(scope, 'generator functions are not supported');
3396
3653
 
3397
- const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3654
+ const name = decl.id ? decl.id.name : `anonymous${randId()}`;
3398
3655
  const params = decl.params ?? [];
3399
3656
 
3400
- // const innerScope = { ...scope };
3401
3657
  // TODO: share scope/locals between !!!
3402
- const innerScope = {
3658
+ const func = {
3403
3659
  locals: {},
3404
3660
  localInd: 0,
3405
3661
  // value, type
3406
3662
  returns: [ valtypeBinary, Valtype.i32 ],
3407
3663
  throws: false,
3408
- name
3664
+ name,
3665
+ index: currentFuncIndex++
3409
3666
  };
3410
3667
 
3668
+ funcIndex[name] = func.index;
3669
+ funcs.push(func);
3670
+
3411
3671
  if (typedInput && decl.returnType) {
3412
3672
  const { type } = extractTypeAnnotation(decl.returnType);
3413
- if (type != null && !Prefs.indirectCalls) {
3414
- innerScope.returnType = type;
3415
- innerScope.returns = [ valtypeBinary ];
3673
+ // if (type != null && !Prefs.indirectCalls) {
3674
+ if (type != null) {
3675
+ func.returnType = type;
3676
+ func.returns = [ valtypeBinary ];
3416
3677
  }
3417
3678
  }
3418
3679
 
3680
+ const defaultValues = {};
3419
3681
  for (let i = 0; i < params.length; i++) {
3420
- allocVar(innerScope, params[i].name, false);
3682
+ let name;
3683
+ const x = params[i];
3684
+ switch (x.type) {
3685
+ case 'Identifier': {
3686
+ name = x.name;
3687
+ break;
3688
+ }
3421
3689
 
3690
+ case 'AssignmentPattern': {
3691
+ name = x.left.name;
3692
+ defaultValues[name] = x.right;
3693
+ break;
3694
+ }
3695
+ }
3696
+
3697
+ // if (name == null) return todo('non-identifier args are not supported');
3698
+
3699
+ allocVar(func, name, false);
3422
3700
  if (typedInput && params[i].typeAnnotation) {
3423
- addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
3701
+ addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
3424
3702
  }
3425
3703
  }
3426
3704
 
3705
+ func.params = Object.values(func.locals).map(x => x.type);
3706
+
3427
3707
  let body = objectHack(decl.body);
3428
3708
  if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
3429
3709
  // hack: () => 0 -> () => return 0
@@ -3433,37 +3713,34 @@ const generateFunc = (scope, decl) => {
3433
3713
  };
3434
3714
  }
3435
3715
 
3436
- const wasm = generate(innerScope, body);
3437
- const func = {
3438
- name,
3439
- params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
3440
- index: currentFuncIndex++,
3441
- ...innerScope
3442
- };
3443
- funcIndex[name] = func.index;
3444
-
3445
- if (name === 'main') func.gotLastType = true;
3716
+ const prelude = [];
3717
+ for (const x in defaultValues) {
3718
+ prelude.push(
3719
+ ...getType(func, x),
3720
+ ...number(TYPES.undefined, Valtype.i32),
3721
+ [ Opcodes.i32_eq ],
3722
+ [ Opcodes.if, Blocktype.void ],
3723
+ ...generate(func, defaultValues[x], false, x),
3724
+ [ Opcodes.local_set, func.locals[x].idx ],
3446
3725
 
3447
- // quick hack fixes
3448
- for (const inst of wasm) {
3449
- if (inst[0] === Opcodes.call && inst[1] === -1) {
3450
- inst[1] = func.index;
3451
- }
3726
+ ...setType(func, x, getNodeType(scope, defaultValues[x])),
3727
+ [ Opcodes.end ]
3728
+ );
3452
3729
  }
3453
3730
 
3731
+ const wasm = func.wasm = prelude.concat(generate(func, body));
3732
+
3733
+ if (name === 'main') func.gotLastType = true;
3734
+
3454
3735
  // add end return if not found
3455
3736
  if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
3456
3737
  wasm.push(
3457
3738
  ...number(0),
3458
- ...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3739
+ ...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3459
3740
  [ Opcodes.return ]
3460
3741
  );
3461
3742
  }
3462
3743
 
3463
- func.wasm = wasm;
3464
-
3465
- funcs.push(func);
3466
-
3467
3744
  return func;
3468
3745
  };
3469
3746
 
@@ -3487,9 +3764,9 @@ const internalConstrs = {
3487
3764
 
3488
3765
  // new Array(n)
3489
3766
 
3490
- const [ , pointer ] = makeArray(scope, {
3767
+ const [ out, pointer ] = makeArray(scope, {
3491
3768
  rawElements: new Array(0)
3492
- }, global, name, true);
3769
+ }, global, name, true, undefined, true);
3493
3770
 
3494
3771
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3495
3772
 
@@ -3498,12 +3775,13 @@ const internalConstrs = {
3498
3775
  if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
3499
3776
 
3500
3777
  return [
3501
- ...number(0, Valtype.i32),
3778
+ ...out,
3502
3779
  ...generate(scope, arg, global, name),
3503
3780
  Opcodes.i32_to_u,
3504
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
3781
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ],
3505
3782
 
3506
- ...number(pointer)
3783
+ ...pointer,
3784
+ Opcodes.i32_from_u
3507
3785
  ];
3508
3786
  },
3509
3787
  type: TYPES.array,
@@ -3567,7 +3845,7 @@ const internalConstrs = {
3567
3845
  generate: (scope, decl) => {
3568
3846
  // todo: boolean object when used as constructor
3569
3847
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3570
- return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3848
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg), false, false, 'full');
3571
3849
  },
3572
3850
  type: TYPES.boolean,
3573
3851
  length: 1
@@ -3628,8 +3906,10 @@ const internalConstrs = {
3628
3906
  }),
3629
3907
 
3630
3908
  // print space
3631
- ...number(32),
3632
- [ Opcodes.call, importedFuncs.printChar ]
3909
+ ...(i !== decl.arguments.length - 1 ? [
3910
+ ...number(32),
3911
+ [ Opcodes.call, importedFuncs.printChar ]
3912
+ ] : [])
3633
3913
  );
3634
3914
  }
3635
3915
 
@@ -3639,6 +3919,8 @@ const internalConstrs = {
3639
3919
  [ Opcodes.call, importedFuncs.printChar ]
3640
3920
  );
3641
3921
 
3922
+ out.push(...number(UNDEFINED));
3923
+
3642
3924
  return out;
3643
3925
  },
3644
3926
  type: TYPES.undefined,
@@ -3648,8 +3930,9 @@ const internalConstrs = {
3648
3930
  };
3649
3931
 
3650
3932
  export default program => {
3651
- globals = {};
3652
- globalInd = 0;
3933
+ globals = {
3934
+ ['#ind']: 0
3935
+ };
3653
3936
  tags = [];
3654
3937
  exceptions = [];
3655
3938
  funcs = [];
@@ -3659,19 +3942,8 @@ export default program => {
3659
3942
  data = [];
3660
3943
  currentFuncIndex = importedFuncs.length;
3661
3944
 
3662
- globalThis.valtype = 'f64';
3663
-
3664
- const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
3665
- if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
3666
-
3667
- globalThis.valtypeBinary = Valtype[valtype];
3668
-
3669
3945
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
3670
3946
 
3671
- globalThis.pageSize = PageSize;
3672
- const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
3673
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3674
-
3675
3947
  // set generic opcodes for current valtype
3676
3948
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
3677
3949
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -3693,6 +3965,7 @@ export default program => {
3693
3965
  builtinFuncs = new BuiltinFuncs();
3694
3966
  builtinVars = new BuiltinVars();
3695
3967
  prototypeFuncs = new PrototypeFuncs();
3968
+ allocator = makeAllocator(Prefs.allocator ?? 'static');
3696
3969
 
3697
3970
  program.id = { name: 'main' };
3698
3971
 
@@ -3708,9 +3981,8 @@ export default program => {
3708
3981
 
3709
3982
  if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3710
3983
 
3711
- generateFunc(scope, program);
3984
+ const main = generateFunc(scope, program);
3712
3985
 
3713
- const main = funcs[funcs.length - 1];
3714
3986
  main.export = true;
3715
3987
  main.returns = [ valtypeBinary, Valtype.i32 ];
3716
3988
 
@@ -3736,8 +4008,10 @@ export default program => {
3736
4008
  else main.returns = [];
3737
4009
  }
3738
4010
 
4011
+ delete globals['#ind'];
4012
+
3739
4013
  // if blank main func and other exports, remove it
3740
- if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
4014
+ if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(main.index - importedFuncs.length, 1);
3741
4015
 
3742
4016
  return { funcs, globals, tags, exceptions, pages, data };
3743
4017
  };