porffor 0.0.0-8c0bdaa → 0.0.0-b5da8c4

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.
@@ -5,6 +5,7 @@ import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./bui
5
5
  import { PrototypeFuncs } from "./prototype.js";
6
6
  import { number, i32x4 } from "./embedding.js";
7
7
  import parse from "./parse.js";
8
+ import * as Rhemyn from "../rhemyn/compile.js";
8
9
 
9
10
  let globals = {};
10
11
  let globalInd = 0;
@@ -35,7 +36,14 @@ const debug = str => {
35
36
  };
36
37
 
37
38
  const todo = msg => {
38
- throw new Error(`todo: ${msg}`);
39
+ class TodoError extends Error {
40
+ constructor(message) {
41
+ super(message);
42
+ this.name = 'TodoError';
43
+ }
44
+ }
45
+
46
+ throw new TodoError(`todo: ${msg}`);
39
47
 
40
48
  const code = [];
41
49
 
@@ -101,6 +109,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
101
109
  case 'WhileStatement':
102
110
  return generateWhile(scope, decl);
103
111
 
112
+ /* case 'ForOfStatement':
113
+ return generateForOf(scope, decl); */
114
+
104
115
  case 'BreakStatement':
105
116
  return generateBreak(scope, decl);
106
117
 
@@ -164,7 +175,6 @@ const generate = (scope, decl, global = false, name = undefined) => {
164
175
  }
165
176
 
166
177
  if (asm[0] === 'memory') {
167
- scope.memory = true;
168
178
  allocPage('asm instrinsic');
169
179
  // todo: add to store/load offset insts
170
180
  continue;
@@ -278,7 +288,7 @@ const generateReturn = (scope, decl) => {
278
288
  ];
279
289
  }
280
290
 
281
- if (!scope.returnType) scope.returnType = getNodeType(scope, decl.argument);
291
+ scope.returnType = getNodeType(scope, decl.argument);
282
292
 
283
293
  return [
284
294
  ...generate(scope, decl.argument),
@@ -295,11 +305,11 @@ const localTmp = (scope, name, type = valtypeBinary) => {
295
305
  return idx;
296
306
  };
297
307
 
298
- const performLogicOp = (scope, op, left, right) => {
308
+ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
299
309
  const checks = {
300
- '||': Opcodes.eqz,
301
- '&&': [ Opcodes.i32_to ]
302
- // todo: ??
310
+ '||': falsy,
311
+ '&&': truthy,
312
+ '??': nullish
303
313
  };
304
314
 
305
315
  if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
@@ -310,7 +320,8 @@ const performLogicOp = (scope, op, left, right) => {
310
320
  return [
311
321
  ...left,
312
322
  [ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
313
- ...checks[op],
323
+ ...checks[op](scope, [], leftType),
324
+ Opcodes.i32_to,
314
325
  [ Opcodes.if, valtypeBinary ],
315
326
  ...right,
316
327
  [ Opcodes.else ],
@@ -325,15 +336,13 @@ const concatStrings = (scope, left, right, global, name, assign) => {
325
336
  // todo: optimize by looking up names in arrays and using that if exists?
326
337
  // todo: optimize this if using literals/known lengths?
327
338
 
328
- scope.memory = true;
329
-
330
- const pointer = arrays.get(name ?? '$undeclared');
331
-
332
339
  const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
333
340
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
334
341
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
335
342
 
336
343
  if (assign) {
344
+ const pointer = arrays.get(name ?? '$undeclared');
345
+
337
346
  return [
338
347
  // setup right
339
348
  ...right,
@@ -384,15 +393,12 @@ const concatStrings = (scope, left, right, global, name, assign) => {
384
393
 
385
394
  const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
386
395
 
387
- const newOut = makeArray(scope, {
396
+ // alloc/assign array
397
+ const [ , pointer ] = makeArray(scope, {
388
398
  rawElements: new Array(0)
389
399
  }, global, name, true, 'i16');
390
400
 
391
401
  return [
392
- // setup new/out array
393
- ...newOut,
394
- [ Opcodes.drop ],
395
-
396
402
  // setup left
397
403
  ...left,
398
404
  Opcodes.i32_to_u,
@@ -458,75 +464,220 @@ const concatStrings = (scope, left, right, global, name, assign) => {
458
464
  ];
459
465
  };
460
466
 
461
- const falsy = (scope, wasm, type) => {
467
+ const compareStrings = (scope, left, right) => {
468
+ // todo: this should be rewritten into a built-in/func: String.prototype.concat
469
+ // todo: convert left and right to strings if not
470
+ // todo: optimize by looking up names in arrays and using that if exists?
471
+ // todo: optimize this if using literals/known lengths?
472
+
473
+ const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
474
+ const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
475
+ const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
476
+ const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
477
+
478
+ const index = localTmp(scope, 'compare_index', Valtype.i32);
479
+ const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
480
+
481
+ return [
482
+ // setup left
483
+ ...left,
484
+ Opcodes.i32_to_u,
485
+ [ Opcodes.local_tee, leftPointer ],
486
+
487
+ // setup right
488
+ ...right,
489
+ Opcodes.i32_to_u,
490
+ [ Opcodes.local_tee, rightPointer ],
491
+
492
+ // fast path: check leftPointer == rightPointer
493
+ // use if (block) for everything after to "return" a value early
494
+ [ Opcodes.i32_ne ],
495
+ [ Opcodes.if, Valtype.i32 ],
496
+
497
+ // get lengths
498
+ [ Opcodes.local_get, leftPointer ],
499
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
500
+ [ Opcodes.local_tee, leftLength ],
501
+
502
+ [ Opcodes.local_get, rightPointer ],
503
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
504
+ [ Opcodes.local_tee, rightLength ],
505
+
506
+ // fast path: check leftLength != rightLength
507
+ [ Opcodes.i32_ne ],
508
+ [ Opcodes.if, Blocktype.void ],
509
+ ...number(0, Valtype.i32),
510
+ [ Opcodes.br, 1 ],
511
+ [ Opcodes.end ],
512
+
513
+ // no fast path for length = 0 as it would probably be slower for most of the time?
514
+
515
+ // setup index end as length * sizeof i16 (2)
516
+ // we do this instead of having to do mul/div each iter for perf™
517
+ [ Opcodes.local_get, leftLength ],
518
+ ...number(ValtypeSize.i16, Valtype.i32),
519
+ [ Opcodes.i32_mul ],
520
+ [ Opcodes.local_set, indexEnd ],
521
+
522
+ // iterate over each char and check if eq
523
+ [ Opcodes.loop, Blocktype.void ],
524
+
525
+ // fetch left
526
+ [ Opcodes.local_get, index ],
527
+ [ Opcodes.local_get, leftPointer ],
528
+ [ Opcodes.i32_add ],
529
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
530
+
531
+ // fetch right
532
+ [ Opcodes.local_get, index ],
533
+ [ Opcodes.local_get, rightPointer ],
534
+ [ Opcodes.i32_add ],
535
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
536
+
537
+ // not equal, "return" false
538
+ [ Opcodes.i32_ne ],
539
+ [ Opcodes.if, Blocktype.void ],
540
+ ...number(0, Valtype.i32),
541
+ [ Opcodes.br, 2 ],
542
+ [ Opcodes.end ],
543
+
544
+ // index += sizeof i16 (2)
545
+ [ Opcodes.local_get, index ],
546
+ ...number(ValtypeSize.i16, Valtype.i32),
547
+ [ Opcodes.i32_add ],
548
+ [ Opcodes.local_tee, index ],
549
+
550
+ // if index != index end (length * sizeof 16), loop
551
+ [ Opcodes.local_get, indexEnd ],
552
+ [ Opcodes.i32_ne ],
553
+ [ Opcodes.br_if, 0 ],
554
+ [ Opcodes.end ],
555
+
556
+ // no failed checks, so true!
557
+ ...number(1, Valtype.i32),
558
+
559
+ // pointers match, so true
560
+ [ Opcodes.else ],
561
+ ...number(1, Valtype.i32),
562
+ [ Opcodes.end ],
563
+
564
+ // convert i32 result to valtype
565
+ // do not do as automatically added by binary exp gen for equality ops
566
+ // Opcodes.i32_from_u
567
+ ];
568
+ };
569
+
570
+ const truthy = (scope, wasm, type) => {
462
571
  // arrays are always truthy
463
572
  if (type === TYPES._array) return [
464
573
  ...wasm,
465
574
  [ Opcodes.drop ],
466
- number(0)
575
+ ...number(1)
467
576
  ];
468
577
 
469
578
  if (type === TYPES.string) {
470
- // if "" (length = 0)
579
+ // if not "" (length = 0)
471
580
  return [
472
581
  // pointer
473
582
  ...wasm,
583
+ Opcodes.i32_to_u,
474
584
 
475
585
  // get length
476
586
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
477
587
 
478
- // if length == 0
479
- [ Opcodes.i32_eqz ],
588
+ // if length != 0
589
+ /* [ Opcodes.i32_eqz ],
590
+ [ Opcodes.i32_eqz ], */
480
591
  Opcodes.i32_from_u
481
592
  ]
482
593
  }
483
594
 
484
- // if = 0
595
+ // if != 0
485
596
  return [
486
597
  ...wasm,
487
598
 
488
- ...Opcodes.eqz,
489
- Opcodes.i32_from_u
599
+ /* Opcodes.eqz,
600
+ [ Opcodes.i32_eqz ],
601
+ Opcodes.i32_from */
490
602
  ];
491
603
  };
492
604
 
493
- const truthy = (scope, wasm, type) => {
605
+ const falsy = (scope, wasm, type) => {
494
606
  // arrays are always truthy
495
607
  if (type === TYPES._array) return [
496
608
  ...wasm,
497
609
  [ Opcodes.drop ],
498
- number(1)
610
+ ...number(0)
499
611
  ];
500
612
 
501
613
  if (type === TYPES.string) {
502
- // if not "" (length = 0)
614
+ // if "" (length = 0)
503
615
  return [
504
616
  // pointer
505
617
  ...wasm,
618
+ Opcodes.i32_to_u,
506
619
 
507
620
  // get length
508
621
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
509
622
 
510
- // if length != 0
511
- /* [ Opcodes.i32_eqz ],
512
- [ Opcodes.i32_eqz ], */
623
+ // if length == 0
624
+ [ Opcodes.i32_eqz ],
513
625
  Opcodes.i32_from_u
514
626
  ]
515
627
  }
516
628
 
517
- // if != 0
629
+ // if = 0
518
630
  return [
519
631
  ...wasm,
520
632
 
521
- /* Opcodes.eqz,
522
- [ Opcodes.i32_eqz ],
523
- Opcodes.i32_from */
633
+ ...Opcodes.eqz,
634
+ Opcodes.i32_from_u
524
635
  ];
525
636
  };
526
637
 
527
- const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$unspecified', assign = false) => {
638
+ const nullish = (scope, wasm, type) => {
639
+ // undefined
640
+ if (type === TYPES.undefined) return [
641
+ ...wasm,
642
+ [ Opcodes.drop ],
643
+ ...number(1)
644
+ ];
645
+
646
+ // null (if object and = "0")
647
+ if (type === TYPES.object) return [
648
+ ...wasm,
649
+ ...Opcodes.eqz,
650
+ Opcodes.i32_from_u
651
+ ];
652
+
653
+ // not
654
+ return [
655
+ ...wasm,
656
+ [ Opcodes.drop ],
657
+ ...number(0)
658
+ ];
659
+ };
660
+
661
+ const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
528
662
  if (op === '||' || op === '&&' || op === '??') {
529
- return performLogicOp(scope, op, left, right);
663
+ return performLogicOp(scope, op, left, right, leftType, rightType);
664
+ }
665
+
666
+ if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
667
+
668
+ // if strict (in)equal and known types mismatch, return false (===)/true (!==)
669
+ if ((op === '===' || op === '!==') && leftType && rightType && leftType !== rightType) {
670
+ return [
671
+ ...left,
672
+ ...right,
673
+
674
+ // drop values
675
+ [ Opcodes.drop ],
676
+ [ Opcodes.drop ],
677
+
678
+ // return false (===)/true (!==)
679
+ ...number(op === '===' ? 0 : 1, Valtype.i32)
680
+ ];
530
681
  }
531
682
 
532
683
  if (leftType === TYPES.string || rightType === TYPES.string) {
@@ -539,8 +690,20 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
539
690
  if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
540
691
 
541
692
  // else leave bool ops
542
- // todo: convert string to number if string and number or le/ge op
543
- // todo: string equality
693
+ // todo: convert string to number if string and number/bool
694
+ // todo: string (>|>=|<|<=) string
695
+
696
+ // string comparison
697
+ if (op === '===' || op === '==') {
698
+ return compareStrings(scope, left, right);
699
+ }
700
+
701
+ if (op === '!==' || op === '!=') {
702
+ return [
703
+ ...compareStrings(scope, left, right),
704
+ [ Opcodes.i32_eqz ]
705
+ ];
706
+ }
544
707
  }
545
708
 
546
709
  let ops = operatorOpcode[valtype][op];
@@ -569,17 +732,21 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
569
732
  ];
570
733
  };
571
734
 
735
+ let binaryExpDepth = 0;
572
736
  const generateBinaryExp = (scope, decl, _global, _name) => {
737
+ binaryExpDepth++;
738
+
573
739
  const out = [
574
740
  ...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
575
741
  ];
576
742
 
577
743
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
578
744
 
745
+ binaryExpDepth--;
579
746
  return out;
580
747
  };
581
748
 
582
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, memory, localNames = [], globalNames = [] }) => {
749
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
583
750
  const existing = funcs.find(x => x.name === name);
584
751
  if (existing) return existing;
585
752
 
@@ -615,7 +782,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
615
782
  returns,
616
783
  returnType: TYPES[returnType ?? 'number'],
617
784
  wasm,
618
- memory,
619
785
  internal: true,
620
786
  index: currentFuncIndex++
621
787
  };
@@ -634,7 +800,7 @@ const includeBuiltin = (scope, builtin) => {
634
800
  };
635
801
 
636
802
  const generateLogicExp = (scope, decl) => {
637
- return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
803
+ return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
638
804
  };
639
805
 
640
806
  const TYPES = {
@@ -648,7 +814,8 @@ const TYPES = {
648
814
  bigint: 0xffffffffffff7,
649
815
 
650
816
  // these are not "typeof" types but tracked internally
651
- _array: 0xffffffffffff8
817
+ _array: 0xfffffffffff0f,
818
+ _regexp: 0xfffffffffff1f
652
819
  };
653
820
 
654
821
  const TYPE_NAMES = {
@@ -682,6 +849,9 @@ const getType = (scope, _name) => {
682
849
 
683
850
  const getNodeType = (scope, node) => {
684
851
  if (node.type === 'Literal') {
852
+ if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
853
+ if (node.regex) return TYPES._regexp;
854
+
685
855
  return TYPES[typeof node.value];
686
856
  }
687
857
 
@@ -696,7 +866,7 @@ const getNodeType = (scope, node) => {
696
866
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
697
867
  const name = node.callee.name;
698
868
  const func = funcs.find(x => x.name === name);
699
- if (func) return func.returnType ?? TYPES.number;
869
+ if (func) return func.returnType;
700
870
 
701
871
  if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
702
872
  if (internalConstrs[name]) return internalConstrs[name].type;
@@ -715,15 +885,18 @@ const getNodeType = (scope, node) => {
715
885
 
716
886
  // literal.func()
717
887
  if (!name && node.callee.type === 'MemberExpression') {
888
+ if (node.callee.object.regex) {
889
+ const funcName = node.callee.property.name;
890
+ return Rhemyn[funcName] ? TYPES.boolean : TYPES.undefined;
891
+ }
892
+
718
893
  const baseType = getNodeType(scope, node.callee.object);
719
894
 
720
895
  const func = node.callee.property.name;
721
896
  protoFunc = prototypeFuncs[baseType]?.[func];
722
897
  }
723
898
 
724
- if (protoFunc) return protoFunc.returnType ?? TYPES.number;
725
-
726
- return TYPES.number;
899
+ if (protoFunc) return protoFunc.returnType;
727
900
  }
728
901
 
729
902
  if (node.type === 'ExpressionStatement') {
@@ -755,14 +928,16 @@ const getNodeType = (scope, node) => {
755
928
 
756
929
  if (objectType === TYPES.string && node.computed) return TYPES.string;
757
930
  }
758
-
759
- // default to number
760
- return TYPES.number;
761
931
  };
762
932
 
763
933
  const generateLiteral = (scope, decl, global, name) => {
764
934
  if (decl.value === null) return number(NULL);
765
935
 
936
+ if (decl.regex) {
937
+ scope.regex[name] = decl.regex;
938
+ return number(1);
939
+ }
940
+
766
941
  switch (typeof decl.value) {
767
942
  case 'number':
768
943
  return number(decl.value);
@@ -792,7 +967,7 @@ const generateLiteral = (scope, decl, global, name) => {
792
967
 
793
968
  return makeArray(scope, {
794
969
  rawElements
795
- }, global, name, false, 'i16');
970
+ }, global, name, false, 'i16')[0];
796
971
 
797
972
  default:
798
973
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -802,7 +977,8 @@ const generateLiteral = (scope, decl, global, name) => {
802
977
  const countLeftover = wasm => {
803
978
  let count = 0, depth = 0;
804
979
 
805
- for (const inst of wasm) {
980
+ for (let i = 0; i < wasm.length; i++) {
981
+ const inst = wasm[i];
806
982
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
807
983
  if (inst[0] === Opcodes.if) count--;
808
984
  if (inst[1] !== Blocktype.void) count++;
@@ -906,7 +1082,7 @@ const generateCall = (scope, decl, _global, _name) => {
906
1082
  }
907
1083
 
908
1084
  let out = [];
909
- let protoFunc, protoName, baseType, baseName = '$undeclared';
1085
+ let protoFunc, protoName, baseType, baseName;
910
1086
  // ident.func()
911
1087
  if (name && name.startsWith('__')) {
912
1088
  const spl = name.slice(2).split('_');
@@ -921,6 +1097,25 @@ const generateCall = (scope, decl, _global, _name) => {
921
1097
 
922
1098
  // literal.func()
923
1099
  if (!name && decl.callee.type === 'MemberExpression') {
1100
+ // megahack for /regex/.func()
1101
+ if (decl.callee.object.regex) {
1102
+ const funcName = decl.callee.property.name;
1103
+ const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1104
+
1105
+ funcIndex[func.name] = func.index;
1106
+ funcs.push(func);
1107
+
1108
+ return [
1109
+ // make string arg
1110
+ ...generate(scope, decl.arguments[0]),
1111
+
1112
+ // call regex func
1113
+ Opcodes.i32_to_u,
1114
+ [ Opcodes.call, func.index ],
1115
+ Opcodes.i32_from
1116
+ ];
1117
+ }
1118
+
924
1119
  baseType = getNodeType(scope, decl.callee.object);
925
1120
 
926
1121
  const func = decl.callee.property.name;
@@ -929,11 +1124,36 @@ const generateCall = (scope, decl, _global, _name) => {
929
1124
 
930
1125
  out = generate(scope, decl.callee.object);
931
1126
  out.push([ Opcodes.drop ]);
1127
+
1128
+ baseName = [...arrays.keys()].pop();
932
1129
  }
933
1130
 
934
- if (protoFunc) {
935
- scope.memory = true;
1131
+ if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
1132
+ const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
1133
+
1134
+ funcIndex[func.name] = func.index;
1135
+ funcs.push(func);
936
1136
 
1137
+ const pointer = arrays.get(baseName);
1138
+ const [ local, isGlobal ] = lookupName(scope, baseName);
1139
+
1140
+ return [
1141
+ ...out,
1142
+
1143
+ ...(pointer == null ? [
1144
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1145
+ Opcodes.i32_to_u,
1146
+ ] : [
1147
+ ...number(pointer, Valtype.i32)
1148
+ ]),
1149
+
1150
+ // call regex func
1151
+ [ Opcodes.call, func.index ],
1152
+ Opcodes.i32_from
1153
+ ];
1154
+ }
1155
+
1156
+ if (protoFunc) {
937
1157
  let pointer = arrays.get(baseName);
938
1158
 
939
1159
  if (pointer == null) {
@@ -941,10 +1161,9 @@ const generateCall = (scope, decl, _global, _name) => {
941
1161
  if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
942
1162
 
943
1163
  // register array
944
- makeArray(scope, {
1164
+ 0, [ , pointer ] = makeArray(scope, {
945
1165
  rawElements: new Array(0)
946
1166
  }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
947
- pointer = arrays.get(baseName);
948
1167
 
949
1168
  const [ local, isGlobal ] = lookupName(scope, baseName);
950
1169
 
@@ -980,10 +1199,9 @@ const generateCall = (scope, decl, _global, _name) => {
980
1199
  set: value => arrayUtil.setLength(pointer, value),
981
1200
  setI32: value => arrayUtil.setLengthI32(pointer, value)
982
1201
  }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
983
- const out = makeArray(scope, {
1202
+ return makeArray(scope, {
984
1203
  rawElements: new Array(length)
985
1204
  }, _global, _name, true, itemType);
986
- return [ out, arrays.get(_name ?? '$undeclared') ];
987
1205
  }),
988
1206
  [ Opcodes.end ]
989
1207
  ];
@@ -1041,7 +1259,6 @@ const generateCall = (scope, decl, _global, _name) => {
1041
1259
  args = args.slice(0, func.params.length);
1042
1260
  }
1043
1261
 
1044
- if (func && func.memory) scope.memory = true;
1045
1262
  if (func && func.throws) scope.throws = true;
1046
1263
 
1047
1264
  for (const arg of args) {
@@ -1057,7 +1274,7 @@ const generateNew = (scope, decl, _global, _name) => {
1057
1274
  // hack: basically treat this as a normal call for builtins for now
1058
1275
  const name = mapName(decl.callee.name);
1059
1276
  if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1060
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1277
+ if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1061
1278
 
1062
1279
  return generateCall(scope, decl, _global, _name);
1063
1280
  };
@@ -1165,8 +1382,6 @@ const generateAssign = (scope, decl) => {
1165
1382
  const name = decl.left.object.name;
1166
1383
  const pointer = arrays.get(name);
1167
1384
 
1168
- scope.memory = true;
1169
-
1170
1385
  const aotPointer = pointer != null;
1171
1386
 
1172
1387
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
@@ -1217,8 +1432,27 @@ const generateAssign = (scope, decl) => {
1217
1432
  ];
1218
1433
  }
1219
1434
 
1435
+ const op = decl.operator.slice(0, -1);
1436
+ if (op === '||' || op === '&&' || op === '??') {
1437
+ // todo: is this needed?
1438
+ // for logical assignment ops, it is not left @= right ~= left = left @ right
1439
+ // instead, left @ (left = right)
1440
+ // eg, x &&= y ~= x && (x = y)
1441
+
1442
+ return [
1443
+ ...performOp(scope, op, [
1444
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1445
+ ], [
1446
+ ...generate(scope, decl.right),
1447
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1448
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1449
+ ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1450
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1451
+ ];
1452
+ }
1453
+
1220
1454
  return [
1221
- ...performOp(scope, decl.operator.slice(0, -1), [ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ] ], generate(scope, decl.right), getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1455
+ ...performOp(scope, op, [ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ] ], generate(scope, decl.right), getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1222
1456
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1223
1457
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1224
1458
  ];
@@ -1245,13 +1479,14 @@ const generateUnary = (scope, decl) => {
1245
1479
 
1246
1480
  case '!':
1247
1481
  // !=
1248
- return falsy(scope, generate(scope, decl.argument));
1482
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
1249
1483
 
1250
1484
  case '~':
1485
+ // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
1251
1486
  return [
1252
1487
  ...generate(scope, decl.argument),
1253
1488
  Opcodes.i32_to,
1254
- [ Opcodes.i32_const, signedLEB128(-1) ],
1489
+ [ Opcodes.i32_const, ...signedLEB128(-1) ],
1255
1490
  [ Opcodes.i32_xor ],
1256
1491
  Opcodes.i32_from
1257
1492
  ];
@@ -1289,7 +1524,7 @@ const generateUnary = (scope, decl) => {
1289
1524
  return out;
1290
1525
 
1291
1526
  case 'typeof':
1292
- const type = getNodeType(scope, decl.argument);
1527
+ const type = getNodeType(scope, decl.argument) ?? TYPES.number;
1293
1528
 
1294
1529
  // for custom types, just return object
1295
1530
  if (type > 0xffffffffffff7) return number(TYPES.object);
@@ -1422,9 +1657,28 @@ const generateWhile = (scope, decl) => {
1422
1657
  return out;
1423
1658
  };
1424
1659
 
1660
+ const generateForOf = (scope, decl) => {
1661
+ const out = [];
1662
+
1663
+ out.push([ Opcodes.loop, Blocktype.void ]);
1664
+ depth.push('while');
1665
+
1666
+ out.push(...generate(scope, decl.test));
1667
+ out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1668
+ depth.push('if');
1669
+
1670
+ out.push(...generate(scope, decl.body));
1671
+
1672
+ out.push([ Opcodes.br, 1 ]);
1673
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
1674
+ depth.pop(); depth.pop();
1675
+
1676
+ return out;
1677
+ };
1678
+
1425
1679
  const getNearestLoop = () => {
1426
1680
  for (let i = depth.length - 1; i >= 0; i--) {
1427
- if (depth[i] === 'while' || depth[i] === 'for') return i;
1681
+ if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
1428
1682
  }
1429
1683
 
1430
1684
  return -1;
@@ -1508,13 +1762,22 @@ const generateAssignPat = (scope, decl) => {
1508
1762
  };
1509
1763
 
1510
1764
  let pages = new Map();
1511
- const allocPage = reason => {
1512
- if (pages.has(reason)) return pages.get(reason);
1765
+ const allocPage = (reason, type) => {
1766
+ if (pages.has(reason)) return pages.get(reason).ind;
1767
+
1768
+ const ind = pages.size;
1769
+ pages.set(reason, { ind, type });
1770
+
1771
+ if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
1772
+
1773
+ return ind;
1774
+ };
1513
1775
 
1514
- let ind = pages.size;
1515
- pages.set(reason, ind);
1776
+ const freePage = reason => {
1777
+ const { ind } = pages.get(reason);
1778
+ pages.delete(reason);
1516
1779
 
1517
- if (codeLog) log('codegen', `allocated new page of memory (${ind}) | ${reason}`);
1780
+ if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
1518
1781
 
1519
1782
  return ind;
1520
1783
  };
@@ -1543,7 +1806,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1543
1806
  if (!arrays.has(name) || name === '$undeclared') {
1544
1807
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1545
1808
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1546
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`) * pageSize);
1809
+ arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
1547
1810
  }
1548
1811
 
1549
1812
  const pointer = arrays.get(name);
@@ -1576,14 +1839,12 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1576
1839
  // local value as pointer
1577
1840
  out.push(...number(pointer));
1578
1841
 
1579
- scope.memory = true;
1580
-
1581
- return out;
1842
+ return [ out, pointer ];
1582
1843
  };
1583
1844
 
1584
1845
  let arrays = new Map();
1585
1846
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
1586
- return makeArray(scope, decl, global, name, initEmpty, valtype);
1847
+ return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
1587
1848
  };
1588
1849
 
1589
1850
  export const generateMember = (scope, decl, _global, _name) => {
@@ -1596,8 +1857,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1596
1857
  const name = decl.object.name;
1597
1858
  const pointer = arrays.get(name);
1598
1859
 
1599
- scope.memory = true;
1600
-
1601
1860
  const aotPointer = pointer != null;
1602
1861
 
1603
1862
  return [
@@ -1617,8 +1876,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1617
1876
  const name = decl.object.name;
1618
1877
  const pointer = arrays.get(name);
1619
1878
 
1620
- scope.memory = true;
1621
-
1622
1879
  const aotPointer = pointer != null;
1623
1880
 
1624
1881
  if (type === TYPES._array) {
@@ -1644,10 +1901,9 @@ export const generateMember = (scope, decl, _global, _name) => {
1644
1901
 
1645
1902
  // string
1646
1903
 
1647
- const newOut = makeArray(scope, {
1904
+ const [ newOut, newPointer ] = makeArray(scope, {
1648
1905
  rawElements: new Array(1)
1649
1906
  }, _global, _name, true, 'i16');
1650
- const newPointer = arrays.get(_name ?? '$undeclared');
1651
1907
 
1652
1908
  return [
1653
1909
  // setup new/out array
@@ -1729,7 +1985,7 @@ const generateFunc = (scope, decl) => {
1729
1985
  locals: {},
1730
1986
  localInd: 0,
1731
1987
  returns: [ valtypeBinary ],
1732
- memory: false,
1988
+ returnType: null,
1733
1989
  throws: false,
1734
1990
  name
1735
1991
  };
@@ -1753,9 +2009,8 @@ const generateFunc = (scope, decl) => {
1753
2009
  name,
1754
2010
  params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
1755
2011
  returns: innerScope.returns,
1756
- returnType: innerScope.returnType ?? TYPES.number,
2012
+ returnType: innerScope.returnType,
1757
2013
  locals: innerScope.locals,
1758
- memory: innerScope.memory,
1759
2014
  throws: innerScope.throws,
1760
2015
  index: currentFuncIndex++
1761
2016
  };
@@ -1770,6 +2025,8 @@ const generateFunc = (scope, decl) => {
1770
2025
 
1771
2026
  if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
1772
2027
  wasm.push(...number(0), [ Opcodes.return ]);
2028
+
2029
+ if (func.returnType === null) func.returnType = TYPES.undefined;
1773
2030
  }
1774
2031
 
1775
2032
  // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
@@ -1780,9 +2037,7 @@ const generateFunc = (scope, decl) => {
1780
2037
  if (local.type === Valtype.v128) {
1781
2038
  vecParams++;
1782
2039
 
1783
- /* func.memory = true; // mark func as using memory
1784
-
1785
- wasm.unshift( // add v128 load for param
2040
+ /* wasm.unshift( // add v128 load for param
1786
2041
  [ Opcodes.i32_const, 0 ],
1787
2042
  [ ...Opcodes.v128_load, 0, i * 16 ],
1788
2043
  [ Opcodes.local_set, local.idx ]
@@ -1912,10 +2167,9 @@ const internalConstrs = {
1912
2167
 
1913
2168
  // new Array(n)
1914
2169
 
1915
- makeArray(scope, {
2170
+ const [ , pointer ] = makeArray(scope, {
1916
2171
  rawElements: new Array(0)
1917
2172
  }, global, name, true);
1918
- const pointer = arrays.get(name ?? '$undeclared');
1919
2173
 
1920
2174
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
1921
2175