porffor 0.0.0-1989c22 → 0.0.0-1b0a5c6

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
 
@@ -141,45 +152,65 @@ const generate = (scope, decl, global = false, name = undefined) => {
141
152
 
142
153
  return [];
143
154
 
144
- case 'TaggedTemplateExpression':
145
- // hack for inline asm
146
- if (decl.tag.name !== 'asm') return todo('tagged template expressions not implemented');
155
+ case 'TaggedTemplateExpression': {
156
+ const funcs = {
157
+ asm: str => {
158
+ let out = [];
147
159
 
148
- const str = decl.quasi.quasis[0].value.raw;
149
- let out = [];
160
+ for (const line of str.split('\n')) {
161
+ const asm = line.trim().split(';;')[0].split(' ');
162
+ if (asm[0] === '') continue; // blank
150
163
 
151
- for (const line of str.split('\n')) {
152
- const asm = line.trim().split(';;')[0].split(' ');
153
- if (asm[0] === '') continue; // blank
164
+ if (asm[0] === 'local') {
165
+ const [ name, idx, type ] = asm.slice(1);
166
+ scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
167
+ continue;
168
+ }
154
169
 
155
- if (asm[0] === 'local') {
156
- const [ name, idx, type ] = asm.slice(1);
157
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
158
- continue;
159
- }
170
+ if (asm[0] === 'returns') {
171
+ scope.returns = asm.slice(1).map(x => Valtype[x]);
172
+ continue;
173
+ }
160
174
 
161
- if (asm[0] === 'returns') {
162
- scope.returns = asm.slice(1).map(x => Valtype[x]);
163
- continue;
164
- }
175
+ if (asm[0] === 'memory') {
176
+ allocPage('asm instrinsic');
177
+ // todo: add to store/load offset insts
178
+ continue;
179
+ }
165
180
 
166
- if (asm[0] === 'memory') {
167
- scope.memory = true;
168
- allocPage('asm instrinsic');
169
- // todo: add to store/load offset insts
170
- continue;
171
- }
181
+ let inst = Opcodes[asm[0].replace('.', '_')];
182
+ if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
183
+
184
+ if (!Array.isArray(inst)) inst = [ inst ];
185
+ const immediates = asm.slice(1).map(x => parseInt(x));
186
+
187
+ out.push([ ...inst, ...immediates ]);
188
+ }
172
189
 
173
- let inst = Opcodes[asm[0].replace('.', '_')];
174
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
190
+ return out;
191
+ },
175
192
 
176
- if (!Array.isArray(inst)) inst = [ inst ];
177
- const immediates = asm.slice(1).map(x => parseInt(x));
193
+ __internal_print_type: str => {
194
+ const type = getType(scope, str) - TYPES.number;
178
195
 
179
- out.push([ ...inst, ...immediates ]);
196
+ return [
197
+ ...number(type),
198
+ [ Opcodes.call, importedFuncs.print ],
199
+
200
+ // newline
201
+ ...number(10),
202
+ [ Opcodes.call, importedFuncs.printChar ]
203
+ ];
204
+ }
180
205
  }
181
206
 
182
- return out;
207
+ const name = decl.tag.name;
208
+ // hack for inline asm
209
+ if (!funcs[name]) return todo('tagged template expressions not implemented');
210
+
211
+ const str = decl.quasi.quasis[0].value.raw;
212
+ return funcs[name](str);
213
+ }
183
214
 
184
215
  default:
185
216
  return todo(`no generation for ${decl.type}!`);
@@ -278,7 +309,7 @@ const generateReturn = (scope, decl) => {
278
309
  ];
279
310
  }
280
311
 
281
- if (!scope.returnType) scope.returnType = getNodeType(scope, decl.argument);
312
+ scope.returnType = getNodeType(scope, decl.argument);
282
313
 
283
314
  return [
284
315
  ...generate(scope, decl.argument),
@@ -295,11 +326,11 @@ const localTmp = (scope, name, type = valtypeBinary) => {
295
326
  return idx;
296
327
  };
297
328
 
298
- const performLogicOp = (scope, op, left, right) => {
329
+ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
299
330
  const checks = {
300
- '||': Opcodes.eqz,
301
- '&&': [ Opcodes.i32_to ]
302
- // todo: ??
331
+ '||': falsy,
332
+ '&&': truthy,
333
+ '??': nullish
303
334
  };
304
335
 
305
336
  if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
@@ -310,7 +341,8 @@ const performLogicOp = (scope, op, left, right) => {
310
341
  return [
311
342
  ...left,
312
343
  [ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
313
- ...checks[op],
344
+ ...checks[op](scope, [], leftType),
345
+ Opcodes.i32_to,
314
346
  [ Opcodes.if, valtypeBinary ],
315
347
  ...right,
316
348
  [ Opcodes.else ],
@@ -325,8 +357,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
325
357
  // todo: optimize by looking up names in arrays and using that if exists?
326
358
  // todo: optimize this if using literals/known lengths?
327
359
 
328
- scope.memory = true;
329
-
330
360
  const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
331
361
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
332
362
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
@@ -461,8 +491,6 @@ const compareStrings = (scope, left, right) => {
461
491
  // todo: optimize by looking up names in arrays and using that if exists?
462
492
  // todo: optimize this if using literals/known lengths?
463
493
 
464
- scope.memory = true;
465
-
466
494
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
467
495
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
468
496
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
@@ -472,9 +500,6 @@ const compareStrings = (scope, left, right) => {
472
500
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
473
501
 
474
502
  return [
475
- // use block to "return" a value early
476
- [ Opcodes.block, Valtype.i32 ],
477
-
478
503
  // setup left
479
504
  ...left,
480
505
  Opcodes.i32_to_u,
@@ -486,11 +511,9 @@ const compareStrings = (scope, left, right) => {
486
511
  [ Opcodes.local_tee, rightPointer ],
487
512
 
488
513
  // fast path: check leftPointer == rightPointer
489
- [ Opcodes.i32_eq ],
490
- [ Opcodes.if, Blocktype.void ],
491
- ...number(1, Valtype.i32),
492
- [ Opcodes.br, 1 ],
493
- [ Opcodes.end ],
514
+ // use if (block) for everything after to "return" a value early
515
+ [ Opcodes.i32_ne ],
516
+ [ Opcodes.if, Valtype.i32 ],
494
517
 
495
518
  // get lengths
496
519
  [ Opcodes.local_get, leftPointer ],
@@ -553,6 +576,10 @@ const compareStrings = (scope, left, right) => {
553
576
 
554
577
  // no failed checks, so true!
555
578
  ...number(1, Valtype.i32),
579
+
580
+ // pointers match, so true
581
+ [ Opcodes.else ],
582
+ ...number(1, Valtype.i32),
556
583
  [ Opcodes.end ],
557
584
 
558
585
  // convert i32 result to valtype
@@ -561,102 +588,142 @@ const compareStrings = (scope, left, right) => {
561
588
  ];
562
589
  };
563
590
 
564
- const falsy = (scope, wasm, type) => {
591
+ const truthy = (scope, wasm, type) => {
565
592
  // arrays are always truthy
566
593
  if (type === TYPES._array) return [
567
594
  ...wasm,
568
595
  [ Opcodes.drop ],
569
- number(0)
596
+ ...number(1)
570
597
  ];
571
598
 
572
599
  if (type === TYPES.string) {
573
- // if "" (length = 0)
600
+ // if not "" (length = 0)
574
601
  return [
575
602
  // pointer
576
603
  ...wasm,
604
+ Opcodes.i32_to_u,
577
605
 
578
606
  // get length
579
607
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
580
608
 
581
- // if length == 0
582
- [ Opcodes.i32_eqz ],
609
+ // if length != 0
610
+ /* [ Opcodes.i32_eqz ],
611
+ [ Opcodes.i32_eqz ], */
583
612
  Opcodes.i32_from_u
584
613
  ]
585
614
  }
586
615
 
587
- // if = 0
616
+ // if != 0
588
617
  return [
589
618
  ...wasm,
590
619
 
591
- ...Opcodes.eqz,
592
- Opcodes.i32_from_u
620
+ /* Opcodes.eqz,
621
+ [ Opcodes.i32_eqz ],
622
+ Opcodes.i32_from */
593
623
  ];
594
624
  };
595
625
 
596
- const truthy = (scope, wasm, type) => {
626
+ const falsy = (scope, wasm, type) => {
597
627
  // arrays are always truthy
598
628
  if (type === TYPES._array) return [
599
629
  ...wasm,
600
630
  [ Opcodes.drop ],
601
- number(1)
631
+ ...number(0)
602
632
  ];
603
633
 
604
634
  if (type === TYPES.string) {
605
- // if not "" (length = 0)
635
+ // if "" (length = 0)
606
636
  return [
607
637
  // pointer
608
638
  ...wasm,
639
+ Opcodes.i32_to_u,
609
640
 
610
641
  // get length
611
642
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
612
643
 
613
- // if length != 0
614
- /* [ Opcodes.i32_eqz ],
615
- [ Opcodes.i32_eqz ], */
644
+ // if length == 0
645
+ [ Opcodes.i32_eqz ],
616
646
  Opcodes.i32_from_u
617
647
  ]
618
648
  }
619
649
 
620
- // if != 0
650
+ // if = 0
621
651
  return [
622
652
  ...wasm,
623
653
 
624
- /* Opcodes.eqz,
625
- [ Opcodes.i32_eqz ],
626
- Opcodes.i32_from */
654
+ ...Opcodes.eqz,
655
+ Opcodes.i32_from_u
656
+ ];
657
+ };
658
+
659
+ const nullish = (scope, wasm, type) => {
660
+ // undefined
661
+ if (type === TYPES.undefined) return [
662
+ ...wasm,
663
+ [ Opcodes.drop ],
664
+ ...number(1)
665
+ ];
666
+
667
+ // null (if object and = "0")
668
+ if (type === TYPES.object) return [
669
+ ...wasm,
670
+ ...Opcodes.eqz,
671
+ Opcodes.i32_from_u
672
+ ];
673
+
674
+ // not
675
+ return [
676
+ ...wasm,
677
+ [ Opcodes.drop ],
678
+ ...number(0)
627
679
  ];
628
680
  };
629
681
 
630
682
  const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
631
683
  if (op === '||' || op === '&&' || op === '??') {
632
- return performLogicOp(scope, op, left, right);
684
+ return performLogicOp(scope, op, left, right, leftType, rightType);
633
685
  }
634
686
 
635
687
  if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
636
688
 
637
- // if strict (in)equal and known types mismatch, return false (===)/true (!==)
638
- if ((op === '===' || op === '!==') && leftType && rightType && leftType !== rightType) {
689
+ const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
690
+
691
+ if (leftType && rightType && (
692
+ // if strict (in)equal and known types mismatch, return false (===)/true (!==)
693
+ ((op === '===' || op === '!==') && leftType !== rightType) ||
694
+
695
+ // if equality op and an operand is undefined, return false
696
+ (eqOp && leftType === TYPES.undefined ^ rightType === TYPES.undefined)
697
+ )) {
698
+ // undefined == null
699
+ if (((leftType === TYPES.undefined && rightType === TYPES.object) || (leftType === TYPES.object && rightType === TYPES.undefined)) && (op === '==' || op === '!=')) return [
700
+ ...(leftType === TYPES.object ? left : right),
701
+ ...Opcodes.eqz,
702
+ ...(op === '!=' ? [ [ Opcodes.i32_eqz ] ] : [])
703
+ ];
704
+
639
705
  return [
640
706
  ...left,
641
- ...right,
642
-
643
- // drop values
644
707
  [ Opcodes.drop ],
708
+
709
+ ...right,
645
710
  [ Opcodes.drop ],
646
711
 
647
- // return false (===)/true (!==)
648
- ...number(op === '===' ? 0 : 1, Valtype.i32)
712
+ // return true (!=/!==) or false (else)
713
+ ...number(op === '!=' || op === '!==' ? 1 : 0, Valtype.i32)
649
714
  ];
650
715
  }
651
716
 
717
+ // todo: niche null hell with 0
718
+
652
719
  if (leftType === TYPES.string || rightType === TYPES.string) {
653
720
  if (op === '+') {
654
721
  // string concat (a + b)
655
722
  return concatStrings(scope, left, right, _global, _name, assign);
656
723
  }
657
724
 
658
- // any other math op, NaN
659
- if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
725
+ // not an equality op, NaN
726
+ if (!eqOp) return number(NaN);
660
727
 
661
728
  // else leave bool ops
662
729
  // todo: convert string to number if string and number/bool
@@ -701,21 +768,15 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
701
768
  ];
702
769
  };
703
770
 
704
- let binaryExpDepth = 0;
705
771
  const generateBinaryExp = (scope, decl, _global, _name) => {
706
- binaryExpDepth++;
707
-
708
- const out = [
709
- ...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
710
- ];
772
+ const out = performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name);
711
773
 
712
774
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
713
775
 
714
- binaryExpDepth--;
715
776
  return out;
716
777
  };
717
778
 
718
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, memory, localNames = [], globalNames = [] }) => {
779
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
719
780
  const existing = funcs.find(x => x.name === name);
720
781
  if (existing) return existing;
721
782
 
@@ -751,7 +812,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
751
812
  returns,
752
813
  returnType: TYPES[returnType ?? 'number'],
753
814
  wasm,
754
- memory,
755
815
  internal: true,
756
816
  index: currentFuncIndex++
757
817
  };
@@ -770,7 +830,7 @@ const includeBuiltin = (scope, builtin) => {
770
830
  };
771
831
 
772
832
  const generateLogicExp = (scope, decl) => {
773
- return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
833
+ return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
774
834
  };
775
835
 
776
836
  const TYPES = {
@@ -784,7 +844,8 @@ const TYPES = {
784
844
  bigint: 0xffffffffffff7,
785
845
 
786
846
  // these are not "typeof" types but tracked internally
787
- _array: 0xffffffffffff8
847
+ _array: 0xfffffffffff0f,
848
+ _regexp: 0xfffffffffff1f
788
849
  };
789
850
 
790
851
  const TYPE_NAMES = {
@@ -819,6 +880,8 @@ const getType = (scope, _name) => {
819
880
  const getNodeType = (scope, node) => {
820
881
  if (node.type === 'Literal') {
821
882
  if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
883
+ if (node.regex) return TYPES._regexp;
884
+
822
885
  return TYPES[typeof node.value];
823
886
  }
824
887
 
@@ -852,6 +915,11 @@ const getNodeType = (scope, node) => {
852
915
 
853
916
  // literal.func()
854
917
  if (!name && node.callee.type === 'MemberExpression') {
918
+ if (node.callee.object.regex) {
919
+ const funcName = node.callee.property.name;
920
+ return Rhemyn[funcName] ? TYPES.boolean : TYPES.undefined;
921
+ }
922
+
855
923
  const baseType = getNodeType(scope, node.callee.object);
856
924
 
857
925
  const func = node.callee.property.name;
@@ -895,6 +963,11 @@ const getNodeType = (scope, node) => {
895
963
  const generateLiteral = (scope, decl, global, name) => {
896
964
  if (decl.value === null) return number(NULL);
897
965
 
966
+ if (decl.regex) {
967
+ scope.regex[name] = decl.regex;
968
+ return number(1);
969
+ }
970
+
898
971
  switch (typeof decl.value) {
899
972
  case 'number':
900
973
  return number(decl.value);
@@ -934,7 +1007,8 @@ const generateLiteral = (scope, decl, global, name) => {
934
1007
  const countLeftover = wasm => {
935
1008
  let count = 0, depth = 0;
936
1009
 
937
- for (const inst of wasm) {
1010
+ for (let i = 0; i < wasm.length; i++) {
1011
+ const inst = wasm[i];
938
1012
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
939
1013
  if (inst[0] === Opcodes.if) count--;
940
1014
  if (inst[1] !== Blocktype.void) count++;
@@ -955,6 +1029,8 @@ const countLeftover = wasm => {
955
1029
  } else count--;
956
1030
  if (func) count += func.returns.length;
957
1031
  } else count--;
1032
+
1033
+ // console.log(count, decompile([ inst ]).slice(0, -1));
958
1034
  }
959
1035
 
960
1036
  return count;
@@ -1038,7 +1114,7 @@ const generateCall = (scope, decl, _global, _name) => {
1038
1114
  }
1039
1115
 
1040
1116
  let out = [];
1041
- let protoFunc, protoName, baseType, baseName = '$undeclared';
1117
+ let protoFunc, protoName, baseType, baseName;
1042
1118
  // ident.func()
1043
1119
  if (name && name.startsWith('__')) {
1044
1120
  const spl = name.slice(2).split('_');
@@ -1053,6 +1129,25 @@ const generateCall = (scope, decl, _global, _name) => {
1053
1129
 
1054
1130
  // literal.func()
1055
1131
  if (!name && decl.callee.type === 'MemberExpression') {
1132
+ // megahack for /regex/.func()
1133
+ if (decl.callee.object.regex) {
1134
+ const funcName = decl.callee.property.name;
1135
+ const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1136
+
1137
+ funcIndex[func.name] = func.index;
1138
+ funcs.push(func);
1139
+
1140
+ return [
1141
+ // make string arg
1142
+ ...generate(scope, decl.arguments[0]),
1143
+
1144
+ // call regex func
1145
+ Opcodes.i32_to_u,
1146
+ [ Opcodes.call, func.index ],
1147
+ Opcodes.i32_from
1148
+ ];
1149
+ }
1150
+
1056
1151
  baseType = getNodeType(scope, decl.callee.object);
1057
1152
 
1058
1153
  const func = decl.callee.property.name;
@@ -1061,11 +1156,36 @@ const generateCall = (scope, decl, _global, _name) => {
1061
1156
 
1062
1157
  out = generate(scope, decl.callee.object);
1063
1158
  out.push([ Opcodes.drop ]);
1159
+
1160
+ baseName = [...arrays.keys()].pop();
1064
1161
  }
1065
1162
 
1066
- if (protoFunc) {
1067
- scope.memory = true;
1163
+ if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
1164
+ const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
1165
+
1166
+ funcIndex[func.name] = func.index;
1167
+ funcs.push(func);
1168
+
1169
+ const pointer = arrays.get(baseName);
1170
+ const [ local, isGlobal ] = lookupName(scope, baseName);
1171
+
1172
+ return [
1173
+ ...out,
1174
+
1175
+ ...(pointer == null ? [
1176
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1177
+ Opcodes.i32_to_u,
1178
+ ] : [
1179
+ ...number(pointer, Valtype.i32)
1180
+ ]),
1068
1181
 
1182
+ // call regex func
1183
+ [ Opcodes.call, func.index ],
1184
+ Opcodes.i32_from
1185
+ ];
1186
+ }
1187
+
1188
+ if (protoFunc) {
1069
1189
  let pointer = arrays.get(baseName);
1070
1190
 
1071
1191
  if (pointer == null) {
@@ -1073,7 +1193,7 @@ const generateCall = (scope, decl, _global, _name) => {
1073
1193
  if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
1074
1194
 
1075
1195
  // register array
1076
- const [ , pointer ] = makeArray(scope, {
1196
+ 0, [ , pointer ] = makeArray(scope, {
1077
1197
  rawElements: new Array(0)
1078
1198
  }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
1079
1199
 
@@ -1097,24 +1217,34 @@ const generateCall = (scope, decl, _global, _name) => {
1097
1217
  // use local for cached i32 length as commonly used
1098
1218
  let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1099
1219
 
1220
+ let lengthI32CacheUsed = false;
1221
+
1222
+ const protoOut = protoFunc(pointer, {
1223
+ getCachedI32: () => {
1224
+ lengthI32CacheUsed = true;
1225
+ return [ [ Opcodes.local_get, lengthLocal ] ]
1226
+ },
1227
+ setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
1228
+ get: () => arrayUtil.getLength(pointer),
1229
+ getI32: () => arrayUtil.getLengthI32(pointer),
1230
+ set: value => arrayUtil.setLength(pointer, value),
1231
+ setI32: value => arrayUtil.setLengthI32(pointer, value)
1232
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
1233
+ return makeArray(scope, {
1234
+ rawElements: new Array(length)
1235
+ }, _global, _name, true, itemType);
1236
+ });
1237
+
1100
1238
  return [
1101
1239
  ...out,
1102
1240
 
1103
- ...arrayUtil.getLengthI32(pointer),
1104
- [ Opcodes.local_set, lengthLocal ],
1241
+ ...(!lengthI32CacheUsed ? [] : [
1242
+ ...arrayUtil.getLengthI32(pointer),
1243
+ [ Opcodes.local_set, lengthLocal ],
1244
+ ]),
1105
1245
 
1106
1246
  [ Opcodes.block, valtypeBinary ],
1107
- ...protoFunc(pointer, {
1108
- cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
1109
- get: arrayUtil.getLength(pointer),
1110
- getI32: arrayUtil.getLengthI32(pointer),
1111
- set: value => arrayUtil.setLength(pointer, value),
1112
- setI32: value => arrayUtil.setLengthI32(pointer, value)
1113
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
1114
- return makeArray(scope, {
1115
- rawElements: new Array(length)
1116
- }, _global, _name, true, itemType);
1117
- }),
1247
+ ...protoOut,
1118
1248
  [ Opcodes.end ]
1119
1249
  ];
1120
1250
  }
@@ -1171,11 +1301,10 @@ const generateCall = (scope, decl, _global, _name) => {
1171
1301
  args = args.slice(0, func.params.length);
1172
1302
  }
1173
1303
 
1174
- if (func && func.memory) scope.memory = true;
1175
1304
  if (func && func.throws) scope.throws = true;
1176
1305
 
1177
1306
  for (const arg of args) {
1178
- out.push(...generate(scope, arg));
1307
+ out = out.concat(generate(scope, arg));
1179
1308
  }
1180
1309
 
1181
1310
  out.push([ Opcodes.call, idx ]);
@@ -1187,7 +1316,7 @@ const generateNew = (scope, decl, _global, _name) => {
1187
1316
  // hack: basically treat this as a normal call for builtins for now
1188
1317
  const name = mapName(decl.callee.name);
1189
1318
  if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1190
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1319
+ if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1191
1320
 
1192
1321
  return generateCall(scope, decl, _global, _name);
1193
1322
  };
@@ -1295,8 +1424,6 @@ const generateAssign = (scope, decl) => {
1295
1424
  const name = decl.left.object.name;
1296
1425
  const pointer = arrays.get(name);
1297
1426
 
1298
- scope.memory = true;
1299
-
1300
1427
  const aotPointer = pointer != null;
1301
1428
 
1302
1429
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
@@ -1317,13 +1444,60 @@ const generateAssign = (scope, decl) => {
1317
1444
  ];
1318
1445
  }
1319
1446
 
1447
+ const op = decl.operator.slice(0, -1) || '=';
1448
+
1449
+ // arr[i] | str[i]
1450
+ if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1451
+ const name = decl.left.object.name;
1452
+ const pointer = arrays.get(name);
1453
+
1454
+ const aotPointer = pointer != null;
1455
+
1456
+ const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1457
+ const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1458
+
1459
+ const parentType = getNodeType(scope, decl.left.object);
1460
+
1461
+ return [
1462
+ ...(aotPointer ? [] : [
1463
+ ...generate(scope, decl.left.object),
1464
+ Opcodes.i32_to_u
1465
+ ]),
1466
+
1467
+ // get index as valtype
1468
+ ...generate(scope, decl.left.property),
1469
+
1470
+ // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
1471
+ Opcodes.i32_to_u,
1472
+ ...number(ValtypeSize[valtype], Valtype.i32),
1473
+ [ Opcodes.i32_mul ],
1474
+ ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
1475
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1476
+
1477
+ ...(op === '=' ? generate(scope, decl.right, false, name) : performOp(scope, op, [
1478
+ [ Opcodes.local_get, pointerTmp ],
1479
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1480
+ ], generate(scope, decl.right), parentType === TYPES._array ? TYPES.number : TYPES.string, getNodeType(scope, decl.right), false, name, true)),
1481
+ [ Opcodes.local_tee, newValueTmp ],
1482
+
1483
+ ...(parentType === TYPES._array ? [
1484
+ [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1485
+ ] : [
1486
+ Opcodes.i32_to_u,
1487
+ [ StoreOps.i16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1488
+ ]),
1489
+
1490
+ [ Opcodes.local_get, newValueTmp ]
1491
+ ];
1492
+ }
1493
+
1320
1494
  const [ local, isGlobal ] = lookupName(scope, name);
1321
1495
 
1322
1496
  if (local === undefined) {
1323
1497
  // todo: this should be a devtools/repl/??? only thing
1324
1498
 
1325
1499
  // only allow = for this
1326
- if (decl.operator !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1500
+ if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1327
1501
 
1328
1502
  if (builtinVars[name]) {
1329
1503
  // just return rhs (eg `NaN = 2`)
@@ -1337,8 +1511,10 @@ const generateAssign = (scope, decl) => {
1337
1511
  ];
1338
1512
  }
1339
1513
 
1340
- if (decl.operator === '=') {
1341
- typeStates[name] = getNodeType(scope, decl.right);
1514
+ typeStates[name] = getNodeType(scope, decl.right);
1515
+
1516
+ if (op === '=') {
1517
+ // typeStates[name] = getNodeType(scope, decl.right);
1342
1518
 
1343
1519
  return [
1344
1520
  ...generate(scope, decl.right, isGlobal, name),
@@ -1347,8 +1523,26 @@ const generateAssign = (scope, decl) => {
1347
1523
  ];
1348
1524
  }
1349
1525
 
1526
+ if (op === '||' || op === '&&' || op === '??') {
1527
+ // todo: is this needed?
1528
+ // for logical assignment ops, it is not left @= right ~= left = left @ right
1529
+ // instead, left @ (left = right)
1530
+ // eg, x &&= y ~= x && (x = y)
1531
+
1532
+ return [
1533
+ ...performOp(scope, op, [
1534
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1535
+ ], [
1536
+ ...generate(scope, decl.right),
1537
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1538
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1539
+ ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1540
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1541
+ ];
1542
+ }
1543
+
1350
1544
  return [
1351
- ...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),
1545
+ ...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),
1352
1546
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1353
1547
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1354
1548
  ];
@@ -1375,13 +1569,14 @@ const generateUnary = (scope, decl) => {
1375
1569
 
1376
1570
  case '!':
1377
1571
  // !=
1378
- return falsy(scope, generate(scope, decl.argument));
1572
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
1379
1573
 
1380
1574
  case '~':
1575
+ // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
1381
1576
  return [
1382
1577
  ...generate(scope, decl.argument),
1383
1578
  Opcodes.i32_to,
1384
- [ Opcodes.i32_const, signedLEB128(-1) ],
1579
+ [ Opcodes.i32_const, ...signedLEB128(-1) ],
1385
1580
  [ Opcodes.i32_xor ],
1386
1581
  Opcodes.i32_from
1387
1582
  ];
@@ -1462,7 +1657,7 @@ const generateUpdate = (scope, decl) => {
1462
1657
  };
1463
1658
 
1464
1659
  const generateIf = (scope, decl) => {
1465
- const out = truthy(scope, generate(scope, decl.test), decl.test);
1660
+ const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test));
1466
1661
 
1467
1662
  out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1468
1663
  depth.push('if');
@@ -1552,6 +1747,113 @@ const generateWhile = (scope, decl) => {
1552
1747
  return out;
1553
1748
  };
1554
1749
 
1750
+ const generateForOf = (scope, decl) => {
1751
+ const out = [];
1752
+
1753
+ const rightType = getNodeType(scope, decl.right);
1754
+ const valtypeSize = rightType === TYPES._array ? ValtypeSize[valtype] : ValtypeSize.i16; // presume array (:()
1755
+
1756
+ // todo: for of inside for of might fuck up?
1757
+ const pointer = localTmp(scope, 'forof_base_pointer', Valtype.i32);
1758
+ const length = localTmp(scope, 'forof_length', Valtype.i32);
1759
+ const counter = localTmp(scope, 'forof_counter', Valtype.i32);
1760
+
1761
+ out.push(
1762
+ // set pointer as right
1763
+ ...generate(scope, decl.right),
1764
+ Opcodes.i32_to_u,
1765
+ [ Opcodes.local_set, pointer ],
1766
+
1767
+ // get length
1768
+ [ Opcodes.local_get, pointer ],
1769
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
1770
+ [ Opcodes.local_set, length ]
1771
+ );
1772
+
1773
+ out.push([ Opcodes.loop, Blocktype.void ]);
1774
+ depth.push('forof');
1775
+
1776
+ // setup local for left
1777
+ generate(scope, decl.left);
1778
+
1779
+ const leftName = decl.left.declarations[0].id.name;
1780
+
1781
+ // set type for local
1782
+ typeStates[leftName] = rightType === TYPES._array ? TYPES.number : TYPES.string;
1783
+
1784
+ const [ local, isGlobal ] = lookupName(scope, leftName);
1785
+
1786
+ if (rightType === TYPES._array) { // array
1787
+ out.push(
1788
+ [ Opcodes.local_get, pointer ],
1789
+ [ Opcodes.load, Math.log2(valtypeSize) - 1, ...unsignedLEB128(ValtypeSize.i32) ]
1790
+ );
1791
+ } else { // string
1792
+ const [ newOut, newPointer ] = makeArray(scope, {
1793
+ rawElements: new Array(1)
1794
+ }, isGlobal, leftName, true, 'i16');
1795
+
1796
+ out.push(
1797
+ // setup new/out array
1798
+ ...newOut,
1799
+ [ Opcodes.drop ],
1800
+
1801
+ ...number(0, Valtype.i32), // base 0 for store after
1802
+
1803
+ // load current string ind {arg}
1804
+ [ Opcodes.local_get, pointer ],
1805
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
1806
+
1807
+ // store to new string ind 0
1808
+ [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
1809
+
1810
+ // return new string (page)
1811
+ ...number(newPointer)
1812
+ );
1813
+ }
1814
+
1815
+ // set left value
1816
+ out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ]);
1817
+
1818
+ out.push(
1819
+ [ Opcodes.block, Blocktype.void ],
1820
+ [ Opcodes.block, Blocktype.void ]
1821
+ );
1822
+ depth.push('block');
1823
+ depth.push('block');
1824
+
1825
+ out.push(
1826
+ ...generate(scope, decl.body),
1827
+ [ Opcodes.end ]
1828
+ );
1829
+ depth.pop();
1830
+
1831
+ out.push(
1832
+ // increment iter pointer by valtype size
1833
+ [ Opcodes.local_get, pointer ],
1834
+ ...number(valtypeSize, Valtype.i32),
1835
+ [ Opcodes.i32_add ],
1836
+ [ Opcodes.local_set, pointer ],
1837
+
1838
+ // increment counter by 1
1839
+ [ Opcodes.local_get, counter ],
1840
+ ...number(1, Valtype.i32),
1841
+ [ Opcodes.i32_add ],
1842
+ [ Opcodes.local_tee, counter ],
1843
+
1844
+ // loop if counter != length
1845
+ [ Opcodes.local_get, length ],
1846
+ [ Opcodes.i32_ne ],
1847
+ [ Opcodes.br_if, 1 ],
1848
+
1849
+ [ Opcodes.end ], [ Opcodes.end ]
1850
+ );
1851
+ depth.pop();
1852
+ depth.pop();
1853
+
1854
+ return out;
1855
+ };
1856
+
1555
1857
  const getNearestLoop = () => {
1556
1858
  for (let i = depth.length - 1; i >= 0; i--) {
1557
1859
  if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
@@ -1638,13 +1940,22 @@ const generateAssignPat = (scope, decl) => {
1638
1940
  };
1639
1941
 
1640
1942
  let pages = new Map();
1641
- const allocPage = reason => {
1642
- if (pages.has(reason)) return pages.get(reason);
1943
+ const allocPage = (reason, type) => {
1944
+ if (pages.has(reason)) return pages.get(reason).ind;
1945
+
1946
+ const ind = pages.size;
1947
+ pages.set(reason, { ind, type });
1948
+
1949
+ if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
1950
+
1951
+ return ind;
1952
+ };
1643
1953
 
1644
- let ind = pages.size;
1645
- pages.set(reason, ind);
1954
+ const freePage = reason => {
1955
+ const { ind } = pages.get(reason);
1956
+ pages.delete(reason);
1646
1957
 
1647
- if (codeLog) log('codegen', `allocated new page of memory (${ind}) | ${reason}`);
1958
+ if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
1648
1959
 
1649
1960
  return ind;
1650
1961
  };
@@ -1658,7 +1969,7 @@ const itemTypeToValtype = {
1658
1969
  i16: 'i32'
1659
1970
  };
1660
1971
 
1661
- const storeOps = {
1972
+ const StoreOps = {
1662
1973
  i32: Opcodes.i32_store,
1663
1974
  i64: Opcodes.i64_store,
1664
1975
  f64: Opcodes.f64_store,
@@ -1673,7 +1984,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1673
1984
  if (!arrays.has(name) || name === '$undeclared') {
1674
1985
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1675
1986
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1676
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`) * pageSize);
1987
+ arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
1677
1988
  }
1678
1989
 
1679
1990
  const pointer = arrays.get(name);
@@ -1690,7 +2001,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1690
2001
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
1691
2002
  );
1692
2003
 
1693
- const storeOp = storeOps[itemType];
2004
+ const storeOp = StoreOps[itemType];
1694
2005
  const valtype = itemTypeToValtype[itemType];
1695
2006
 
1696
2007
  if (!initEmpty) for (let i = 0; i < length; i++) {
@@ -1706,8 +2017,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1706
2017
  // local value as pointer
1707
2018
  out.push(...number(pointer));
1708
2019
 
1709
- scope.memory = true;
1710
-
1711
2020
  return [ out, pointer ];
1712
2021
  };
1713
2022
 
@@ -1726,8 +2035,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1726
2035
  const name = decl.object.name;
1727
2036
  const pointer = arrays.get(name);
1728
2037
 
1729
- scope.memory = true;
1730
-
1731
2038
  const aotPointer = pointer != null;
1732
2039
 
1733
2040
  return [
@@ -1747,8 +2054,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1747
2054
  const name = decl.object.name;
1748
2055
  const pointer = arrays.get(name);
1749
2056
 
1750
- scope.memory = true;
1751
-
1752
2057
  const aotPointer = pointer != null;
1753
2058
 
1754
2059
  if (type === TYPES._array) {
@@ -1858,7 +2163,7 @@ const generateFunc = (scope, decl) => {
1858
2163
  locals: {},
1859
2164
  localInd: 0,
1860
2165
  returns: [ valtypeBinary ],
1861
- memory: false,
2166
+ returnType: null,
1862
2167
  throws: false,
1863
2168
  name
1864
2169
  };
@@ -1884,7 +2189,6 @@ const generateFunc = (scope, decl) => {
1884
2189
  returns: innerScope.returns,
1885
2190
  returnType: innerScope.returnType,
1886
2191
  locals: innerScope.locals,
1887
- memory: innerScope.memory,
1888
2192
  throws: innerScope.throws,
1889
2193
  index: currentFuncIndex++
1890
2194
  };
@@ -1899,6 +2203,8 @@ const generateFunc = (scope, decl) => {
1899
2203
 
1900
2204
  if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
1901
2205
  wasm.push(...number(0), [ Opcodes.return ]);
2206
+
2207
+ if (func.returnType === null) func.returnType = TYPES.undefined;
1902
2208
  }
1903
2209
 
1904
2210
  // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
@@ -1909,9 +2215,7 @@ const generateFunc = (scope, decl) => {
1909
2215
  if (local.type === Valtype.v128) {
1910
2216
  vecParams++;
1911
2217
 
1912
- /* func.memory = true; // mark func as using memory
1913
-
1914
- wasm.unshift( // add v128 load for param
2218
+ /* wasm.unshift( // add v128 load for param
1915
2219
  [ Opcodes.i32_const, 0 ],
1916
2220
  [ ...Opcodes.v128_load, 0, i * 16 ],
1917
2221
  [ Opcodes.local_set, local.idx ]
@@ -2022,10 +2326,10 @@ const generateFunc = (scope, decl) => {
2022
2326
  };
2023
2327
 
2024
2328
  const generateCode = (scope, decl) => {
2025
- const out = [];
2329
+ let out = [];
2026
2330
 
2027
2331
  for (const x of decl.body) {
2028
- out.push(...generate(scope, x));
2332
+ out = out.concat(generate(scope, x));
2029
2333
  }
2030
2334
 
2031
2335
  return out;