porffor 0.0.0-828ee15 → 0.0.0-ba812f2

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,10 +336,6 @@ 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 getLocalTmp = name => localTmp(scope, name + binaryExpDepth);
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);
@@ -457,75 +464,220 @@ const concatStrings = (scope, left, right, global, name, assign) => {
457
464
  ];
458
465
  };
459
466
 
460
- 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) => {
461
571
  // arrays are always truthy
462
572
  if (type === TYPES._array) return [
463
573
  ...wasm,
464
574
  [ Opcodes.drop ],
465
- number(0)
575
+ ...number(1)
466
576
  ];
467
577
 
468
578
  if (type === TYPES.string) {
469
- // if "" (length = 0)
579
+ // if not "" (length = 0)
470
580
  return [
471
581
  // pointer
472
582
  ...wasm,
583
+ Opcodes.i32_to_u,
473
584
 
474
585
  // get length
475
586
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
476
587
 
477
- // if length == 0
478
- [ Opcodes.i32_eqz ],
588
+ // if length != 0
589
+ /* [ Opcodes.i32_eqz ],
590
+ [ Opcodes.i32_eqz ], */
479
591
  Opcodes.i32_from_u
480
592
  ]
481
593
  }
482
594
 
483
- // if = 0
595
+ // if != 0
484
596
  return [
485
597
  ...wasm,
486
598
 
487
- ...Opcodes.eqz,
488
- Opcodes.i32_from_u
599
+ /* Opcodes.eqz,
600
+ [ Opcodes.i32_eqz ],
601
+ Opcodes.i32_from */
489
602
  ];
490
603
  };
491
604
 
492
- const truthy = (scope, wasm, type) => {
605
+ const falsy = (scope, wasm, type) => {
493
606
  // arrays are always truthy
494
607
  if (type === TYPES._array) return [
495
608
  ...wasm,
496
609
  [ Opcodes.drop ],
497
- number(1)
610
+ ...number(0)
498
611
  ];
499
612
 
500
613
  if (type === TYPES.string) {
501
- // if not "" (length = 0)
614
+ // if "" (length = 0)
502
615
  return [
503
616
  // pointer
504
617
  ...wasm,
618
+ Opcodes.i32_to_u,
505
619
 
506
620
  // get length
507
621
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
508
622
 
509
- // if length != 0
510
- /* [ Opcodes.i32_eqz ],
511
- [ Opcodes.i32_eqz ], */
623
+ // if length == 0
624
+ [ Opcodes.i32_eqz ],
512
625
  Opcodes.i32_from_u
513
626
  ]
514
627
  }
515
628
 
516
- // if != 0
629
+ // if = 0
517
630
  return [
518
631
  ...wasm,
519
632
 
520
- /* Opcodes.eqz,
521
- [ Opcodes.i32_eqz ],
522
- Opcodes.i32_from */
633
+ ...Opcodes.eqz,
634
+ Opcodes.i32_from_u
635
+ ];
636
+ };
637
+
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)
523
658
  ];
524
659
  };
525
660
 
526
661
  const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
527
662
  if (op === '||' || op === '&&' || op === '??') {
528
- 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
+ ];
529
681
  }
530
682
 
531
683
  if (leftType === TYPES.string || rightType === TYPES.string) {
@@ -538,9 +690,20 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
538
690
  if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
539
691
 
540
692
  // else leave bool ops
541
- // todo: convert string to number if string and number
693
+ // todo: convert string to number if string and number/bool
542
694
  // todo: string (>|>=|<|<=) string
543
- // todo: string equality
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];
@@ -583,7 +746,7 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
583
746
  return out;
584
747
  };
585
748
 
586
- 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 = [] }) => {
587
750
  const existing = funcs.find(x => x.name === name);
588
751
  if (existing) return existing;
589
752
 
@@ -619,7 +782,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
619
782
  returns,
620
783
  returnType: TYPES[returnType ?? 'number'],
621
784
  wasm,
622
- memory,
623
785
  internal: true,
624
786
  index: currentFuncIndex++
625
787
  };
@@ -638,7 +800,7 @@ const includeBuiltin = (scope, builtin) => {
638
800
  };
639
801
 
640
802
  const generateLogicExp = (scope, decl) => {
641
- 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));
642
804
  };
643
805
 
644
806
  const TYPES = {
@@ -652,7 +814,8 @@ const TYPES = {
652
814
  bigint: 0xffffffffffff7,
653
815
 
654
816
  // these are not "typeof" types but tracked internally
655
- _array: 0xffffffffffff8
817
+ _array: 0xfffffffffff0f,
818
+ _regexp: 0xfffffffffff1f
656
819
  };
657
820
 
658
821
  const TYPE_NAMES = {
@@ -686,6 +849,9 @@ const getType = (scope, _name) => {
686
849
 
687
850
  const getNodeType = (scope, node) => {
688
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
+
689
855
  return TYPES[typeof node.value];
690
856
  }
691
857
 
@@ -700,7 +866,7 @@ const getNodeType = (scope, node) => {
700
866
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
701
867
  const name = node.callee.name;
702
868
  const func = funcs.find(x => x.name === name);
703
- if (func) return func.returnType ?? TYPES.number;
869
+ if (func) return func.returnType;
704
870
 
705
871
  if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
706
872
  if (internalConstrs[name]) return internalConstrs[name].type;
@@ -719,15 +885,18 @@ const getNodeType = (scope, node) => {
719
885
 
720
886
  // literal.func()
721
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
+
722
893
  const baseType = getNodeType(scope, node.callee.object);
723
894
 
724
895
  const func = node.callee.property.name;
725
896
  protoFunc = prototypeFuncs[baseType]?.[func];
726
897
  }
727
898
 
728
- if (protoFunc) return protoFunc.returnType ?? TYPES.number;
729
-
730
- return TYPES.number;
899
+ if (protoFunc) return protoFunc.returnType;
731
900
  }
732
901
 
733
902
  if (node.type === 'ExpressionStatement') {
@@ -759,14 +928,16 @@ const getNodeType = (scope, node) => {
759
928
 
760
929
  if (objectType === TYPES.string && node.computed) return TYPES.string;
761
930
  }
762
-
763
- // default to number
764
- return TYPES.number;
765
931
  };
766
932
 
767
933
  const generateLiteral = (scope, decl, global, name) => {
768
934
  if (decl.value === null) return number(NULL);
769
935
 
936
+ if (decl.regex) {
937
+ scope.regex[name] = decl.regex;
938
+ return number(1);
939
+ }
940
+
770
941
  switch (typeof decl.value) {
771
942
  case 'number':
772
943
  return number(decl.value);
@@ -806,7 +977,8 @@ const generateLiteral = (scope, decl, global, name) => {
806
977
  const countLeftover = wasm => {
807
978
  let count = 0, depth = 0;
808
979
 
809
- for (const inst of wasm) {
980
+ for (let i = 0; i < wasm.length; i++) {
981
+ const inst = wasm[i];
810
982
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
811
983
  if (inst[0] === Opcodes.if) count--;
812
984
  if (inst[1] !== Blocktype.void) count++;
@@ -910,7 +1082,7 @@ const generateCall = (scope, decl, _global, _name) => {
910
1082
  }
911
1083
 
912
1084
  let out = [];
913
- let protoFunc, protoName, baseType, baseName = '$undeclared';
1085
+ let protoFunc, protoName, baseType, baseName;
914
1086
  // ident.func()
915
1087
  if (name && name.startsWith('__')) {
916
1088
  const spl = name.slice(2).split('_');
@@ -925,6 +1097,25 @@ const generateCall = (scope, decl, _global, _name) => {
925
1097
 
926
1098
  // literal.func()
927
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
+
928
1119
  baseType = getNodeType(scope, decl.callee.object);
929
1120
 
930
1121
  const func = decl.callee.property.name;
@@ -933,11 +1124,36 @@ const generateCall = (scope, decl, _global, _name) => {
933
1124
 
934
1125
  out = generate(scope, decl.callee.object);
935
1126
  out.push([ Opcodes.drop ]);
1127
+
1128
+ baseName = [...arrays.keys()].pop();
936
1129
  }
937
1130
 
938
- if (protoFunc) {
939
- 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);
1136
+
1137
+ const pointer = arrays.get(baseName);
1138
+ const [ local, isGlobal ] = lookupName(scope, baseName);
940
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) {
941
1157
  let pointer = arrays.get(baseName);
942
1158
 
943
1159
  if (pointer == null) {
@@ -945,7 +1161,7 @@ const generateCall = (scope, decl, _global, _name) => {
945
1161
  if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
946
1162
 
947
1163
  // register array
948
- const [ , pointer ] = makeArray(scope, {
1164
+ 0, [ , pointer ] = makeArray(scope, {
949
1165
  rawElements: new Array(0)
950
1166
  }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
951
1167
 
@@ -1043,7 +1259,6 @@ const generateCall = (scope, decl, _global, _name) => {
1043
1259
  args = args.slice(0, func.params.length);
1044
1260
  }
1045
1261
 
1046
- if (func && func.memory) scope.memory = true;
1047
1262
  if (func && func.throws) scope.throws = true;
1048
1263
 
1049
1264
  for (const arg of args) {
@@ -1059,7 +1274,7 @@ const generateNew = (scope, decl, _global, _name) => {
1059
1274
  // hack: basically treat this as a normal call for builtins for now
1060
1275
  const name = mapName(decl.callee.name);
1061
1276
  if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1062
- 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)})`);
1063
1278
 
1064
1279
  return generateCall(scope, decl, _global, _name);
1065
1280
  };
@@ -1167,8 +1382,6 @@ const generateAssign = (scope, decl) => {
1167
1382
  const name = decl.left.object.name;
1168
1383
  const pointer = arrays.get(name);
1169
1384
 
1170
- scope.memory = true;
1171
-
1172
1385
  const aotPointer = pointer != null;
1173
1386
 
1174
1387
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
@@ -1219,8 +1432,27 @@ const generateAssign = (scope, decl) => {
1219
1432
  ];
1220
1433
  }
1221
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
+
1222
1454
  return [
1223
- ...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),
1224
1456
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1225
1457
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1226
1458
  ];
@@ -1247,13 +1479,14 @@ const generateUnary = (scope, decl) => {
1247
1479
 
1248
1480
  case '!':
1249
1481
  // !=
1250
- return falsy(scope, generate(scope, decl.argument));
1482
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
1251
1483
 
1252
1484
  case '~':
1485
+ // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
1253
1486
  return [
1254
1487
  ...generate(scope, decl.argument),
1255
1488
  Opcodes.i32_to,
1256
- [ Opcodes.i32_const, signedLEB128(-1) ],
1489
+ [ Opcodes.i32_const, ...signedLEB128(-1) ],
1257
1490
  [ Opcodes.i32_xor ],
1258
1491
  Opcodes.i32_from
1259
1492
  ];
@@ -1291,7 +1524,7 @@ const generateUnary = (scope, decl) => {
1291
1524
  return out;
1292
1525
 
1293
1526
  case 'typeof':
1294
- const type = getNodeType(scope, decl.argument);
1527
+ const type = getNodeType(scope, decl.argument) ?? TYPES.number;
1295
1528
 
1296
1529
  // for custom types, just return object
1297
1530
  if (type > 0xffffffffffff7) return number(TYPES.object);
@@ -1424,9 +1657,28 @@ const generateWhile = (scope, decl) => {
1424
1657
  return out;
1425
1658
  };
1426
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
+
1427
1679
  const getNearestLoop = () => {
1428
1680
  for (let i = depth.length - 1; i >= 0; i--) {
1429
- if (depth[i] === 'while' || depth[i] === 'for') return i;
1681
+ if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
1430
1682
  }
1431
1683
 
1432
1684
  return -1;
@@ -1510,13 +1762,22 @@ const generateAssignPat = (scope, decl) => {
1510
1762
  };
1511
1763
 
1512
1764
  let pages = new Map();
1513
- const allocPage = reason => {
1514
- 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
+ };
1515
1775
 
1516
- let ind = pages.size;
1517
- pages.set(reason, ind);
1776
+ const freePage = reason => {
1777
+ const { ind } = pages.get(reason);
1778
+ pages.delete(reason);
1518
1779
 
1519
- if (codeLog) log('codegen', `allocated new page of memory (${ind}) | ${reason}`);
1780
+ if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
1520
1781
 
1521
1782
  return ind;
1522
1783
  };
@@ -1545,7 +1806,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1545
1806
  if (!arrays.has(name) || name === '$undeclared') {
1546
1807
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1547
1808
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1548
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`) * pageSize);
1809
+ arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
1549
1810
  }
1550
1811
 
1551
1812
  const pointer = arrays.get(name);
@@ -1578,8 +1839,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1578
1839
  // local value as pointer
1579
1840
  out.push(...number(pointer));
1580
1841
 
1581
- scope.memory = true;
1582
-
1583
1842
  return [ out, pointer ];
1584
1843
  };
1585
1844
 
@@ -1598,8 +1857,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1598
1857
  const name = decl.object.name;
1599
1858
  const pointer = arrays.get(name);
1600
1859
 
1601
- scope.memory = true;
1602
-
1603
1860
  const aotPointer = pointer != null;
1604
1861
 
1605
1862
  return [
@@ -1619,8 +1876,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1619
1876
  const name = decl.object.name;
1620
1877
  const pointer = arrays.get(name);
1621
1878
 
1622
- scope.memory = true;
1623
-
1624
1879
  const aotPointer = pointer != null;
1625
1880
 
1626
1881
  if (type === TYPES._array) {
@@ -1730,7 +1985,7 @@ const generateFunc = (scope, decl) => {
1730
1985
  locals: {},
1731
1986
  localInd: 0,
1732
1987
  returns: [ valtypeBinary ],
1733
- memory: false,
1988
+ returnType: null,
1734
1989
  throws: false,
1735
1990
  name
1736
1991
  };
@@ -1754,9 +2009,8 @@ const generateFunc = (scope, decl) => {
1754
2009
  name,
1755
2010
  params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
1756
2011
  returns: innerScope.returns,
1757
- returnType: innerScope.returnType ?? TYPES.number,
2012
+ returnType: innerScope.returnType,
1758
2013
  locals: innerScope.locals,
1759
- memory: innerScope.memory,
1760
2014
  throws: innerScope.throws,
1761
2015
  index: currentFuncIndex++
1762
2016
  };
@@ -1771,6 +2025,8 @@ const generateFunc = (scope, decl) => {
1771
2025
 
1772
2026
  if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
1773
2027
  wasm.push(...number(0), [ Opcodes.return ]);
2028
+
2029
+ if (func.returnType === null) func.returnType = TYPES.undefined;
1774
2030
  }
1775
2031
 
1776
2032
  // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
@@ -1781,9 +2037,7 @@ const generateFunc = (scope, decl) => {
1781
2037
  if (local.type === Valtype.v128) {
1782
2038
  vecParams++;
1783
2039
 
1784
- /* func.memory = true; // mark func as using memory
1785
-
1786
- wasm.unshift( // add v128 load for param
2040
+ /* wasm.unshift( // add v128 load for param
1787
2041
  [ Opcodes.i32_const, 0 ],
1788
2042
  [ ...Opcodes.v128_load, 0, i * 16 ],
1789
2043
  [ Opcodes.local_set, local.idx ]