porffor 0.0.0-425ea20 → 0.0.0-48403fd

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.
@@ -1,10 +1,11 @@
1
1
  import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
2
- import { signedLEB128, unsignedLEB128 } from "./encoding.js";
2
+ import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
3
3
  import { operatorOpcode } from "./expression.js";
4
4
  import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
5
5
  import { PrototypeFuncs } from "./prototype.js";
6
- import { number, i32x4 } from "./embedding.js";
6
+ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enforceEightBytes } 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;
@@ -108,8 +109,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
108
109
  case 'WhileStatement':
109
110
  return generateWhile(scope, decl);
110
111
 
111
- /* case 'ForOfStatement':
112
- return generateForOf(scope, decl); */
112
+ case 'ForOfStatement':
113
+ return generateForOf(scope, decl);
113
114
 
114
115
  case 'BreakStatement':
115
116
  return generateBreak(scope, decl);
@@ -151,44 +152,65 @@ const generate = (scope, decl, global = false, name = undefined) => {
151
152
 
152
153
  return [];
153
154
 
154
- case 'TaggedTemplateExpression':
155
- // hack for inline asm
156
- if (decl.tag.name !== 'asm') return todo('tagged template expressions not implemented');
155
+ case 'TaggedTemplateExpression': {
156
+ const funcs = {
157
+ asm: str => {
158
+ let out = [];
157
159
 
158
- const str = decl.quasi.quasis[0].value.raw;
159
- let out = [];
160
+ for (const line of str.split('\n')) {
161
+ const asm = line.trim().split(';;')[0].split(' ');
162
+ if (asm[0] === '') continue; // blank
160
163
 
161
- for (const line of str.split('\n')) {
162
- const asm = line.trim().split(';;')[0].split(' ');
163
- 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
+ }
164
169
 
165
- if (asm[0] === 'local') {
166
- const [ name, idx, type ] = asm.slice(1);
167
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
168
- continue;
169
- }
170
+ if (asm[0] === 'returns') {
171
+ scope.returns = asm.slice(1).map(x => Valtype[x]);
172
+ continue;
173
+ }
170
174
 
171
- if (asm[0] === 'returns') {
172
- scope.returns = asm.slice(1).map(x => Valtype[x]);
173
- continue;
174
- }
175
+ if (asm[0] === 'memory') {
176
+ allocPage('asm instrinsic');
177
+ // todo: add to store/load offset insts
178
+ continue;
179
+ }
175
180
 
176
- if (asm[0] === 'memory') {
177
- allocPage('asm instrinsic');
178
- // todo: add to store/load offset insts
179
- continue;
180
- }
181
+ let inst = Opcodes[asm[0].replace('.', '_')];
182
+ if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
181
183
 
182
- let inst = Opcodes[asm[0].replace('.', '_')];
183
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
184
+ if (!Array.isArray(inst)) inst = [ inst ];
185
+ const immediates = asm.slice(1).map(x => parseInt(x));
184
186
 
185
- if (!Array.isArray(inst)) inst = [ inst ];
186
- const immediates = asm.slice(1).map(x => parseInt(x));
187
+ out.push([ ...inst, ...immediates ]);
188
+ }
187
189
 
188
- out.push([ ...inst, ...immediates ]);
190
+ return out;
191
+ },
192
+
193
+ __internal_print_type: str => {
194
+ const type = getType(scope, str) - TYPES.number;
195
+
196
+ return [
197
+ ...number(type),
198
+ [ Opcodes.call, importedFuncs.print ],
199
+
200
+ // newline
201
+ ...number(10),
202
+ [ Opcodes.call, importedFuncs.printChar ]
203
+ ];
204
+ }
189
205
  }
190
206
 
191
- 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
+ }
192
214
 
193
215
  default:
194
216
  return todo(`no generation for ${decl.type}!`);
@@ -278,19 +300,17 @@ const generateIdent = (scope, decl) => {
278
300
 
279
301
  const generateReturn = (scope, decl) => {
280
302
  if (decl.argument === null) {
281
- if (!scope.returnType) scope.returnType = TYPES.undefined;
282
-
283
303
  // just bare "return"
284
304
  return [
285
305
  ...number(UNDEFINED), // "undefined" if func returns
306
+ ...number(TYPES.undefined, Valtype.i32), // type undefined
286
307
  [ Opcodes.return ]
287
308
  ];
288
309
  }
289
310
 
290
- scope.returnType = getNodeType(scope, decl.argument);
291
-
292
311
  return [
293
312
  ...generate(scope, decl.argument),
313
+ ...getNodeType(scope, decl.argument),
294
314
  [ Opcodes.return ]
295
315
  ];
296
316
  };
@@ -304,6 +324,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
304
324
  return idx;
305
325
  };
306
326
 
327
+ const isIntOp = op => op[0] >= 0xb7 && op[0] <= 0xba;
328
+
307
329
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
308
330
  const checks = {
309
331
  '||': falsy,
@@ -316,15 +338,52 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
316
338
  // generic structure for {a} OP {b}
317
339
  // -->
318
340
  // _ = {a}; if (OP_CHECK) {b} else _
341
+
342
+ // if we can, use int tmp and convert at the end to help prevent unneeded conversions
343
+ // (like if we are in an if condition - very common)
344
+ const leftIsInt = isIntOp(left[left.length - 1]);
345
+ const rightIsInt = isIntOp(right[right.length - 1]);
346
+
347
+ const canInt = leftIsInt && rightIsInt;
348
+
349
+ if (canInt) {
350
+ // remove int -> float conversions from left and right
351
+ left.pop();
352
+ right.pop();
353
+
354
+ return [
355
+ ...left,
356
+ [ Opcodes.local_tee, localTmp(scope, 'logictmpi', Valtype.i32) ],
357
+ ...checks[op](scope, [], leftType, true, true),
358
+ [ Opcodes.if, Valtype.i32 ],
359
+ ...right,
360
+ // note type
361
+ ...rightType,
362
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
363
+ [ Opcodes.else ],
364
+ [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
365
+ // note type
366
+ ...leftType,
367
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
368
+ [ Opcodes.end ],
369
+ Opcodes.i32_from
370
+ ];
371
+ }
372
+
319
373
  return [
320
374
  ...left,
321
375
  [ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
322
- ...checks[op](scope, [], leftType),
323
- Opcodes.i32_to,
376
+ ...checks[op](scope, [], leftType, false, true),
324
377
  [ Opcodes.if, valtypeBinary ],
325
378
  ...right,
379
+ // note type
380
+ ...rightType,
381
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
326
382
  [ Opcodes.else ],
327
383
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
384
+ // note type
385
+ ...leftType,
386
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
328
387
  [ Opcodes.end ]
329
388
  ];
330
389
  };
@@ -339,6 +398,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
339
398
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
340
399
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
341
400
 
401
+ const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
402
+ if (aotWFA) addVarMeta(name, { wellFormed: undefined });
403
+
342
404
  if (assign) {
343
405
  const pointer = arrays.get(name ?? '$undeclared');
344
406
 
@@ -566,94 +628,106 @@ const compareStrings = (scope, left, right) => {
566
628
  ];
567
629
  };
568
630
 
569
- const truthy = (scope, wasm, type) => {
570
- // arrays are always truthy
571
- if (type === TYPES._array) return [
631
+ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
632
+ if (isIntOp(wasm[wasm.length - 1])) return [
572
633
  ...wasm,
573
- [ Opcodes.drop ],
574
- ...number(1)
634
+ ...(!intIn && intOut ? Opcodes.i32_to_u : [])
575
635
  ];
576
636
 
577
- if (type === TYPES.string) {
578
- // if not "" (length = 0)
579
- return [
580
- // pointer
581
- ...wasm,
582
- Opcodes.i32_to_u,
583
-
584
- // get length
585
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
586
-
587
- // if length != 0
588
- /* [ Opcodes.i32_eqz ],
589
- [ Opcodes.i32_eqz ], */
590
- Opcodes.i32_from_u
591
- ]
592
- }
593
-
594
- // if != 0
637
+ const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
595
638
  return [
596
639
  ...wasm,
597
-
598
- /* Opcodes.eqz,
599
- [ Opcodes.i32_eqz ],
600
- Opcodes.i32_from */
640
+ [ Opcodes.local_set, tmp ],
641
+
642
+ ...typeSwitch(scope, type, {
643
+ [TYPES._array]: [
644
+ // arrays are always truthy
645
+ ...number(1, intOut ? Valtype.i32 : valtypeBinary)
646
+ ],
647
+ [TYPES.string]: [
648
+ [ Opcodes.local_get, tmp ],
649
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
650
+
651
+ // get length
652
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
653
+
654
+ // if length != 0
655
+ /* [ Opcodes.i32_eqz ],
656
+ [ Opcodes.i32_eqz ], */
657
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
658
+ ],
659
+ default: [
660
+ // if value != 0
661
+ [ Opcodes.local_get, tmp ],
662
+
663
+ // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
664
+ ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
665
+
666
+ /* Opcodes.eqz,
667
+ [ Opcodes.i32_eqz ],
668
+ Opcodes.i32_from */
669
+ ]
670
+ }, intOut ? Valtype.i32 : valtypeBinary)
601
671
  ];
602
672
  };
603
673
 
604
- const falsy = (scope, wasm, type) => {
605
- // arrays are always truthy
606
- if (type === TYPES._array) return [
607
- ...wasm,
608
- [ Opcodes.drop ],
609
- ...number(0)
610
- ];
611
-
612
- if (type === TYPES.string) {
613
- // if "" (length = 0)
614
- return [
615
- // pointer
616
- ...wasm,
617
- Opcodes.i32_to_u,
618
-
619
- // get length
620
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
621
-
622
- // if length == 0
623
- [ Opcodes.i32_eqz ],
624
- Opcodes.i32_from_u
625
- ]
626
- }
627
-
628
- // if = 0
674
+ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
675
+ const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
629
676
  return [
630
677
  ...wasm,
631
-
632
- ...Opcodes.eqz,
633
- Opcodes.i32_from_u
678
+ [ Opcodes.local_set, tmp ],
679
+
680
+ ...typeSwitch(scope, type, {
681
+ [TYPES._array]: [
682
+ // arrays are always truthy
683
+ ...number(0, intOut ? Valtype.i32 : valtypeBinary)
684
+ ],
685
+ [TYPES.string]: [
686
+ [ Opcodes.local_get, tmp ],
687
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
688
+
689
+ // get length
690
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
691
+
692
+ // if length == 0
693
+ [ Opcodes.i32_eqz ],
694
+ [ Opcodes.i32_eqz ],
695
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
696
+ ],
697
+ default: [
698
+ // if value == 0
699
+ [ Opcodes.local_get, tmp ],
700
+
701
+ ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
702
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
703
+ ]
704
+ }, intOut ? Valtype.i32 : valtypeBinary)
634
705
  ];
635
706
  };
636
707
 
637
- const nullish = (scope, wasm, type) => {
638
- // undefined
639
- if (type === TYPES.undefined) return [
640
- ...wasm,
641
- [ Opcodes.drop ],
642
- ...number(1)
643
- ];
644
-
645
- // null (if object and = "0")
646
- if (type === TYPES.object) return [
647
- ...wasm,
648
- ...Opcodes.eqz,
649
- Opcodes.i32_from_u
650
- ];
651
-
652
- // not
708
+ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
709
+ const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
653
710
  return [
654
711
  ...wasm,
655
- [ Opcodes.drop ],
656
- ...number(0)
712
+ [ Opcodes.local_set, tmp ],
713
+
714
+ ...typeSwitch(scope, type, {
715
+ [TYPES.undefined]: [
716
+ // undefined
717
+ ...number(1, intOut ? Valtype.i32 : valtypeBinary)
718
+ ],
719
+ [TYPES.object]: [
720
+ // object, null if == 0
721
+ [ Opcodes.local_get, tmp ],
722
+
723
+ ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
724
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
725
+ ],
726
+ default: [
727
+ // not
728
+ ...number(0, intOut ? Valtype.i32 : valtypeBinary)
729
+ ]
730
+ }, intOut ? Valtype.i32 : valtypeBinary)
657
731
  ];
658
732
  };
659
733
 
@@ -662,31 +736,35 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
662
736
  return performLogicOp(scope, op, left, right, leftType, rightType);
663
737
  }
664
738
 
665
- if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
739
+ const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
666
740
 
667
- // if strict (in)equal and known types mismatch, return false (===)/true (!==)
668
- if ((op === '===' || op === '!==') && leftType && rightType && leftType !== rightType) {
669
- return [
670
- ...left,
671
- ...right,
741
+ const startOut = [], endOut = [];
742
+ const finalise = out => startOut.concat(out, endOut);
672
743
 
673
- // drop values
674
- [ Opcodes.drop ],
675
- [ Opcodes.drop ],
744
+ // if strict (in)equal check types match and...
745
+ if (op === '===' || op === '!==') {
746
+ startOut.push(
747
+ ...leftType,
748
+ ...rightType,
749
+ [ Opcodes.i32_eq ]
750
+ );
676
751
 
677
- // return false (===)/true (!==)
678
- ...number(op === '===' ? 0 : 1, Valtype.i32)
679
- ];
752
+ endOut.push(
753
+ [ Opcodes.i32_and ]
754
+ );
680
755
  }
681
756
 
757
+ // todo: if equality op and an operand is undefined, return false
758
+ // todo: niche null hell with 0
759
+
682
760
  if (leftType === TYPES.string || rightType === TYPES.string) {
683
761
  if (op === '+') {
684
762
  // string concat (a + b)
685
- return concatStrings(scope, left, right, _global, _name, assign);
763
+ return finalise(concatStrings(scope, left, right, _global, _name, assign));
686
764
  }
687
765
 
688
- // any other math op, NaN
689
- if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
766
+ // not an equality op, NaN
767
+ if (!eqOp) return finalise(number(NaN));
690
768
 
691
769
  // else leave bool ops
692
770
  // todo: convert string to number if string and number/bool
@@ -694,14 +772,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
694
772
 
695
773
  // string comparison
696
774
  if (op === '===' || op === '==') {
697
- return compareStrings(scope, left, right);
775
+ return finalise(compareStrings(scope, left, right));
698
776
  }
699
777
 
700
778
  if (op === '!==' || op === '!=') {
701
- return [
779
+ return finalise([
702
780
  ...compareStrings(scope, left, right),
703
781
  [ Opcodes.i32_eqz ]
704
- ];
782
+ ]);
705
783
  }
706
784
  }
707
785
 
@@ -713,39 +791,33 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
713
791
  includeBuiltin(scope, builtinName);
714
792
  const idx = funcIndex[builtinName];
715
793
 
716
- return [
794
+ return finalise([
717
795
  ...left,
718
796
  ...right,
719
797
  [ Opcodes.call, idx ]
720
- ];
798
+ ]);
721
799
  }
722
800
 
723
801
  if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
724
802
 
725
803
  if (!Array.isArray(ops)) ops = [ ops ];
726
804
 
727
- return [
805
+ return finalise([
728
806
  ...left,
729
807
  ...right,
730
808
  ops
731
- ];
809
+ ]);
732
810
  };
733
811
 
734
- let binaryExpDepth = 0;
735
812
  const generateBinaryExp = (scope, decl, _global, _name) => {
736
- binaryExpDepth++;
737
-
738
- const out = [
739
- ...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
740
- ];
813
+ const out = performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name);
741
814
 
742
815
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
743
816
 
744
- binaryExpDepth--;
745
817
  return out;
746
818
  };
747
819
 
748
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, memory, localNames = [], globalNames = [] }) => {
820
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
749
821
  const existing = funcs.find(x => x.name === name);
750
822
  if (existing) return existing;
751
823
 
@@ -781,7 +853,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
781
853
  returns,
782
854
  returnType: TYPES[returnType ?? 'number'],
783
855
  wasm,
784
- memory,
785
856
  internal: true,
786
857
  index: currentFuncIndex++
787
858
  };
@@ -804,17 +875,18 @@ const generateLogicExp = (scope, decl) => {
804
875
  };
805
876
 
806
877
  const TYPES = {
807
- number: 0xffffffffffff0,
808
- boolean: 0xffffffffffff1,
809
- string: 0xffffffffffff2,
810
- undefined: 0xffffffffffff3,
811
- object: 0xffffffffffff4,
812
- function: 0xffffffffffff5,
813
- symbol: 0xffffffffffff6,
814
- bigint: 0xffffffffffff7,
878
+ number: 0x00,
879
+ boolean: 0x01,
880
+ string: 0x02,
881
+ undefined: 0x03,
882
+ object: 0x04,
883
+ function: 0x05,
884
+ symbol: 0x06,
885
+ bigint: 0x07,
815
886
 
816
887
  // these are not "typeof" types but tracked internally
817
- _array: 0xffffffffffff8
888
+ _array: 0x10,
889
+ _regexp: 0x11
818
890
  };
819
891
 
820
892
  const TYPE_NAMES = {
@@ -830,101 +902,156 @@ const TYPE_NAMES = {
830
902
  [TYPES._array]: 'Array'
831
903
  };
832
904
 
833
- let typeStates = {};
834
-
835
905
  const getType = (scope, _name) => {
836
906
  const name = mapName(_name);
837
- if (scope.locals[name]) return typeStates[name];
838
907
 
839
- if (builtinVars[name]) return TYPES[builtinVars[name].type ?? 'number'];
840
- if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) return TYPES.function;
841
- if (globals[name]) return typeStates[name];
908
+ if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
909
+ if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
910
+
911
+ let type = TYPES.undefined;
912
+ if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
913
+ if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
842
914
 
843
- if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)]) return TYPES.function;
844
- if (name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) return TYPES.function;
915
+ if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
916
+ name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
845
917
 
846
- return TYPES.undefined;
918
+ return number(type, Valtype.i32);
847
919
  };
848
920
 
921
+ const setType = (scope, _name, type) => {
922
+ const name = mapName(_name);
923
+
924
+ const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
925
+
926
+ if (scope.locals[name]) return [
927
+ ...out,
928
+ [ Opcodes.local_set, scope.locals[name + '#type'].idx ]
929
+ ];
930
+
931
+ if (globals[name]) return [
932
+ ...out,
933
+ [ Opcodes.global_set, globals[name + '#type'].idx ]
934
+ ];
935
+
936
+ throw new Error('could not find var');
937
+ }
938
+
849
939
  const getNodeType = (scope, node) => {
850
- if (node.type === 'Literal') {
851
- if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
852
- return TYPES[typeof node.value];
853
- }
940
+ // console.trace(node);
854
941
 
855
- if (isFuncType(node.type)) {
856
- return TYPES.function;
857
- }
942
+ const inner = () => {
943
+ if (node.type === 'Literal') {
944
+ // if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
945
+ if (node.regex) return TYPES._regexp;
858
946
 
859
- if (node.type === 'Identifier') {
860
- return getType(scope, node.name);
861
- }
947
+ return TYPES[typeof node.value];
948
+ }
862
949
 
863
- if (node.type === 'CallExpression' || node.type === 'NewExpression') {
864
- const name = node.callee.name;
865
- const func = funcs.find(x => x.name === name);
866
- if (func) return func.returnType;
950
+ if (isFuncType(node.type)) {
951
+ return TYPES.function;
952
+ }
953
+
954
+ if (node.type === 'Identifier') {
955
+ return getType(scope, node.name);
956
+ }
957
+
958
+ if (node.type === 'CallExpression' || node.type === 'NewExpression') {
959
+ const name = node.callee.name;
960
+ const func = funcs.find(x => x.name === name);
961
+
962
+ if (func) {
963
+ // console.log(scope, func, func.returnType);
964
+ if (func.returnType) return func.returnType;
965
+
966
+ if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
967
+
968
+ // presume
969
+ // todo: warn here?
970
+ return TYPES.number;
971
+ }
867
972
 
868
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
869
- if (internalConstrs[name]) return internalConstrs[name].type;
973
+ if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
974
+ if (internalConstrs[name]) return internalConstrs[name].type;
870
975
 
871
- let protoFunc;
872
- // ident.func()
873
- if (name && name.startsWith('__')) {
874
- const spl = name.slice(2).split('_');
976
+ let protoFunc;
977
+ // ident.func()
978
+ if (name && name.startsWith('__')) {
979
+ const spl = name.slice(2).split('_');
875
980
 
876
- const baseName = spl.slice(0, -1).join('_');
877
- const baseType = getType(scope, baseName);
981
+ const baseName = spl.slice(0, -1).join('_');
982
+ const baseType = getType(scope, baseName);
878
983
 
879
- const func = spl[spl.length - 1];
880
- protoFunc = prototypeFuncs[baseType]?.[func];
984
+ const func = spl[spl.length - 1];
985
+ protoFunc = prototypeFuncs[baseType]?.[func];
986
+ }
987
+
988
+ // literal.func()
989
+ if (!name && node.callee.type === 'MemberExpression') {
990
+ if (node.callee.object.regex) {
991
+ const funcName = node.callee.property.name;
992
+ return Rhemyn[funcName] ? TYPES.boolean : TYPES.undefined;
993
+ }
994
+
995
+ const baseType = getNodeType(scope, node.callee.object);
996
+
997
+ const func = node.callee.property.name;
998
+ protoFunc = prototypeFuncs[baseType]?.[func];
999
+ }
1000
+
1001
+ if (protoFunc) return protoFunc.returnType;
881
1002
  }
882
1003
 
883
- // literal.func()
884
- if (!name && node.callee.type === 'MemberExpression') {
885
- const baseType = getNodeType(scope, node.callee.object);
1004
+ if (node.type === 'ExpressionStatement') {
1005
+ return getNodeType(scope, node.expression);
1006
+ }
886
1007
 
887
- const func = node.callee.property.name;
888
- protoFunc = prototypeFuncs[baseType]?.[func];
1008
+ if (node.type === 'AssignmentExpression') {
1009
+ return getNodeType(scope, node.right);
889
1010
  }
890
1011
 
891
- if (protoFunc) return protoFunc.returnType;
892
- }
1012
+ if (node.type === 'ArrayExpression') {
1013
+ return TYPES._array;
1014
+ }
893
1015
 
894
- if (node.type === 'ExpressionStatement') {
895
- return getNodeType(scope, node.expression);
896
- }
1016
+ if (node.type === 'BinaryExpression') {
1017
+ if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
897
1018
 
898
- if (node.type === 'AssignmentExpression') {
899
- return getNodeType(scope, node.right);
900
- }
1019
+ if (node.operator === '+' && (getNodeType(scope, node.left) === TYPES.string || getNodeType(scope, node.right) === TYPES.string)) return TYPES.string;
1020
+ }
901
1021
 
902
- if (node.type === 'ArrayExpression') {
903
- return TYPES._array;
904
- }
1022
+ if (node.type === 'UnaryExpression') {
1023
+ if (node.operator === '!') return TYPES.boolean;
1024
+ if (node.operator === 'void') return TYPES.undefined;
1025
+ if (node.operator === 'delete') return TYPES.boolean;
1026
+ if (node.operator === 'typeof') return TYPES.string;
1027
+ }
905
1028
 
906
- if (node.type === 'BinaryExpression') {
907
- if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1029
+ if (node.type === 'MemberExpression') {
1030
+ const objectType = getNodeType(scope, node.object);
908
1031
 
909
- if (node.operator === '+' && (getNodeType(scope, node.left) === TYPES.string || getNodeType(scope, node.right) === TYPES.string)) return TYPES.string;
910
- }
1032
+ if (objectType === TYPES.string && node.computed) return TYPES.string;
1033
+ }
911
1034
 
912
- if (node.type === 'UnaryExpression') {
913
- if (node.operator === '!') return TYPES.boolean;
914
- if (node.operator === 'void') return TYPES.undefined;
915
- if (node.operator === 'delete') return TYPES.boolean;
916
- }
1035
+ if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
917
1036
 
918
- if (node.type === 'MemberExpression') {
919
- const objectType = getNodeType(scope, node.object);
1037
+ // presume
1038
+ // todo: warn here?
1039
+ return TYPES.number;
1040
+ };
920
1041
 
921
- if (objectType === TYPES.string && node.computed) return TYPES.string;
922
- }
1042
+ const ret = inner();
1043
+ if (typeof ret === 'number') return number(ret, Valtype.i32);
1044
+ return ret;
923
1045
  };
924
1046
 
925
1047
  const generateLiteral = (scope, decl, global, name) => {
926
1048
  if (decl.value === null) return number(NULL);
927
1049
 
1050
+ if (decl.regex) {
1051
+ scope.regex[name] = decl.regex;
1052
+ return number(1);
1053
+ }
1054
+
928
1055
  switch (typeof decl.value) {
929
1056
  case 'number':
930
1057
  return number(decl.value);
@@ -935,23 +1062,49 @@ const generateLiteral = (scope, decl, global, name) => {
935
1062
 
936
1063
  case 'string':
937
1064
  // this is a terrible hack which changes type strings ("number" etc) to known const number values
938
- switch (decl.value) {
939
- case 'number': return number(TYPES.number);
940
- case 'boolean': return number(TYPES.boolean);
941
- case 'string': return number(TYPES.string);
942
- case 'undefined': return number(TYPES.undefined);
943
- case 'object': return number(TYPES.object);
944
- case 'function': return number(TYPES.function);
945
- case 'symbol': return number(TYPES.symbol);
946
- case 'bigint': return number(TYPES.bigint);
947
- }
1065
+ // switch (decl.value) {
1066
+ // case 'number': return number(TYPES.number);
1067
+ // case 'boolean': return number(TYPES.boolean);
1068
+ // case 'string': return number(TYPES.string);
1069
+ // case 'undefined': return number(TYPES.undefined);
1070
+ // case 'object': return number(TYPES.object);
1071
+ // case 'function': return number(TYPES.function);
1072
+ // case 'symbol': return number(TYPES.symbol);
1073
+ // case 'bigint': return number(TYPES.bigint);
1074
+ // }
1075
+
1076
+ const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
1077
+ let wellFormed = aotWFA ? true : undefined;
948
1078
 
949
1079
  const str = decl.value;
950
1080
  const rawElements = new Array(str.length);
1081
+ let j = 0;
951
1082
  for (let i = 0; i < str.length; i++) {
952
1083
  rawElements[i] = str.charCodeAt(i);
1084
+
1085
+ if (wellFormed) {
1086
+ // check if surrogate
1087
+ if ((str.charCodeAt(j) & 0xF800) === 0xD800) {
1088
+ // unpaired trailing surrogate
1089
+ if (str.charCodeAt(j) >= 0xDC00) {
1090
+ wellFormed = false;
1091
+ }
1092
+
1093
+ // unpaired leading surrogate
1094
+ // if (++j >= str.length || (str.charCodeAt(j) & 0xFC00) != 0xDC00) {
1095
+ if ((str.charCodeAt(++j) & 0xFC00) != 0xDC00) {
1096
+ wellFormed = false;
1097
+ }
1098
+ }
1099
+
1100
+ j++;
1101
+ }
953
1102
  }
954
1103
 
1104
+ // console.log(wellFormed, str);
1105
+
1106
+ if (aotWFA) addVarMeta(name, { wellFormed });
1107
+
955
1108
  return makeArray(scope, {
956
1109
  rawElements
957
1110
  }, global, name, false, 'i16')[0];
@@ -964,6 +1117,8 @@ const generateLiteral = (scope, decl, global, name) => {
964
1117
  const countLeftover = wasm => {
965
1118
  let count = 0, depth = 0;
966
1119
 
1120
+ // console.trace(wasm.length);
1121
+
967
1122
  for (let i = 0; i < wasm.length; i++) {
968
1123
  const inst = wasm[i];
969
1124
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
@@ -974,11 +1129,12 @@ const countLeftover = wasm => {
974
1129
  if (inst[0] === Opcodes.end) depth--;
975
1130
 
976
1131
  if (depth === 0)
977
- if ([Opcodes.throw, Opcodes.return, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1132
+ if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
978
1133
  else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
979
1134
  else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
980
1135
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
981
1136
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1137
+ else if (inst[0] === Opcodes.return) count = 0;
982
1138
  else if (inst[0] === Opcodes.call) {
983
1139
  let func = funcs.find(x => x.index === inst[1]);
984
1140
  if (func) {
@@ -986,6 +1142,8 @@ const countLeftover = wasm => {
986
1142
  } else count--;
987
1143
  if (func) count += func.returns.length;
988
1144
  } else count--;
1145
+
1146
+ // console.log(count, decompile([ inst ]).slice(0, -1));
989
1147
  }
990
1148
 
991
1149
  return count;
@@ -997,6 +1155,13 @@ const disposeLeftover = wasm => {
997
1155
  for (let i = 0; i < leftover; i++) wasm.push([ Opcodes.drop ]);
998
1156
  };
999
1157
 
1158
+ const disposeType = (scope, wasm) => {
1159
+ const lastOp = wasm[wasm.length - 1];
1160
+ if (lastOp[0] === Opcodes.i32_const || (lastOp[0] === Opcodes.local_get && lastOp[1] === scope.locals['#last_type']))
1161
+
1162
+ wasm.push([ Opcodes.drop ]);
1163
+ };
1164
+
1000
1165
  const generateExp = (scope, decl) => {
1001
1166
  const expression = decl.expression;
1002
1167
 
@@ -1084,6 +1249,25 @@ const generateCall = (scope, decl, _global, _name) => {
1084
1249
 
1085
1250
  // literal.func()
1086
1251
  if (!name && decl.callee.type === 'MemberExpression') {
1252
+ // megahack for /regex/.func()
1253
+ if (decl.callee.object.regex) {
1254
+ const funcName = decl.callee.property.name;
1255
+ const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1256
+
1257
+ funcIndex[func.name] = func.index;
1258
+ funcs.push(func);
1259
+
1260
+ return [
1261
+ // make string arg
1262
+ ...generate(scope, decl.arguments[0]),
1263
+
1264
+ // call regex func
1265
+ Opcodes.i32_to_u,
1266
+ [ Opcodes.call, func.index ],
1267
+ Opcodes.i32_from
1268
+ ];
1269
+ }
1270
+
1087
1271
  baseType = getNodeType(scope, decl.callee.object);
1088
1272
 
1089
1273
  const func = decl.callee.property.name;
@@ -1096,6 +1280,31 @@ const generateCall = (scope, decl, _global, _name) => {
1096
1280
  baseName = [...arrays.keys()].pop();
1097
1281
  }
1098
1282
 
1283
+ if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
1284
+ const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
1285
+
1286
+ funcIndex[func.name] = func.index;
1287
+ funcs.push(func);
1288
+
1289
+ const pointer = arrays.get(baseName);
1290
+ const [ local, isGlobal ] = lookupName(scope, baseName);
1291
+
1292
+ return [
1293
+ ...out,
1294
+
1295
+ ...(pointer == null ? [
1296
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1297
+ Opcodes.i32_to_u,
1298
+ ] : [
1299
+ ...number(pointer, Valtype.i32)
1300
+ ]),
1301
+
1302
+ // call regex func
1303
+ [ Opcodes.call, func.index ],
1304
+ Opcodes.i32_from
1305
+ ];
1306
+ }
1307
+
1099
1308
  if (protoFunc) {
1100
1309
  let pointer = arrays.get(baseName);
1101
1310
 
@@ -1124,28 +1333,39 @@ const generateCall = (scope, decl, _global, _name) => {
1124
1333
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) return arrayUtil.getLength(pointer)
1125
1334
 
1126
1335
  let protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp`, protoFunc.local) : -1;
1336
+ let protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp2`, protoFunc.local2) : -1;
1127
1337
 
1128
1338
  // use local for cached i32 length as commonly used
1129
1339
  let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1130
1340
 
1341
+ let lengthI32CacheUsed = false;
1342
+
1343
+ const protoOut = protoFunc(pointer, {
1344
+ getCachedI32: () => {
1345
+ lengthI32CacheUsed = true;
1346
+ return [ [ Opcodes.local_get, lengthLocal ] ]
1347
+ },
1348
+ setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
1349
+ get: () => arrayUtil.getLength(pointer),
1350
+ getI32: () => arrayUtil.getLengthI32(pointer),
1351
+ set: value => arrayUtil.setLength(pointer, value),
1352
+ setI32: value => arrayUtil.setLengthI32(pointer, value)
1353
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1354
+ return makeArray(scope, {
1355
+ rawElements: new Array(length)
1356
+ }, _global, _name, true, itemType);
1357
+ }, varMetadata.get(baseName));
1358
+
1131
1359
  return [
1132
1360
  ...out,
1133
1361
 
1134
- ...arrayUtil.getLengthI32(pointer),
1135
- [ Opcodes.local_set, lengthLocal ],
1362
+ ...(!lengthI32CacheUsed ? [] : [
1363
+ ...arrayUtil.getLengthI32(pointer),
1364
+ [ Opcodes.local_set, lengthLocal ],
1365
+ ]),
1136
1366
 
1137
1367
  [ Opcodes.block, valtypeBinary ],
1138
- ...protoFunc(pointer, {
1139
- cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
1140
- get: arrayUtil.getLength(pointer),
1141
- getI32: arrayUtil.getLengthI32(pointer),
1142
- set: value => arrayUtil.setLength(pointer, value),
1143
- setI32: value => arrayUtil.setLengthI32(pointer, value)
1144
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
1145
- return makeArray(scope, {
1146
- rawElements: new Array(length)
1147
- }, _global, _name, true, itemType);
1148
- }),
1368
+ ...protoOut,
1149
1369
  [ Opcodes.end ]
1150
1370
  ];
1151
1371
  }
@@ -1191,32 +1411,39 @@ const generateCall = (scope, decl, _global, _name) => {
1191
1411
 
1192
1412
  const func = funcs.find(x => x.index === idx);
1193
1413
 
1414
+ const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1415
+ const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
1416
+
1194
1417
  let args = decl.arguments;
1195
- if (func && args.length < func.params.length) {
1418
+ if (func && args.length < paramCount) {
1196
1419
  // too little args, push undefineds
1197
- args = args.concat(new Array(func.params.length - args.length).fill(DEFAULT_VALUE));
1420
+ args = args.concat(new Array(paramCount - args.length).fill(DEFAULT_VALUE));
1198
1421
  }
1199
1422
 
1200
- if (func && args.length > func.params.length) {
1423
+ if (func && args.length > paramCount) {
1201
1424
  // too many args, slice extras off
1202
- args = args.slice(0, func.params.length);
1425
+ args = args.slice(0, paramCount);
1203
1426
  }
1204
1427
 
1205
1428
  if (func && func.throws) scope.throws = true;
1206
1429
 
1207
1430
  for (const arg of args) {
1208
- out.push(...generate(scope, arg));
1431
+ out = out.concat(generate(scope, arg));
1432
+ if (userFunc) out = out.concat(getNodeType(scope, arg));
1209
1433
  }
1210
1434
 
1211
1435
  out.push([ Opcodes.call, idx ]);
1212
1436
 
1437
+ // if user func, they return [ value, type ]
1438
+ if (userFunc) out.push([ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]);
1439
+
1213
1440
  return out;
1214
1441
  };
1215
1442
 
1216
1443
  const generateNew = (scope, decl, _global, _name) => {
1217
1444
  // hack: basically treat this as a normal call for builtins for now
1218
1445
  const name = mapName(decl.callee.name);
1219
- if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1446
+ if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1220
1447
  if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1221
1448
 
1222
1449
  return generateCall(scope, decl, _global, _name);
@@ -1233,13 +1460,67 @@ const unhackName = name => {
1233
1460
  return name;
1234
1461
  };
1235
1462
 
1463
+ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1464
+ const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1465
+
1466
+ const out = [
1467
+ ...type,
1468
+ [ Opcodes.local_set, tmp ],
1469
+ [ Opcodes.block, returns ]
1470
+ ];
1471
+
1472
+ // todo: use br_table?
1473
+
1474
+ for (const x in bc) {
1475
+ if (x === 'default') continue;
1476
+
1477
+ // if type == x
1478
+ out.push([ Opcodes.local_get, tmp ]);
1479
+ out.push(...number(x, Valtype.i32));
1480
+ out.push([ Opcodes.i32_eq ]);
1481
+
1482
+ out.push([ Opcodes.if, Blocktype.void, `TYPESWITCH|${TYPE_NAMES[x]}` ]);
1483
+ out.push(...bc[x]);
1484
+ out.push([ Opcodes.br, 0 ]);
1485
+ out.push([ Opcodes.end ]);
1486
+ }
1487
+
1488
+ // default
1489
+ if (bc.default) out.push(...bc.default);
1490
+ else out.push(...number(0, returns));
1491
+
1492
+ out.push([ Opcodes.end, `TYPESWITCH_end` ]);
1493
+
1494
+ return out;
1495
+ };
1496
+
1497
+ const allocVar = (scope, name, global = false) => {
1498
+ const target = global ? globals : scope.locals;
1499
+
1500
+ // already declared
1501
+ if (target[name]) {
1502
+ // parser should catch this but sanity check anyway
1503
+ if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
1504
+
1505
+ return target[name].idx;
1506
+ }
1507
+
1508
+ let idx = global ? globalInd++ : scope.localInd++;
1509
+ target[name] = { idx, type: valtypeBinary };
1510
+
1511
+ let typeIdx = global ? globalInd++ : scope.localInd++;
1512
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
1513
+
1514
+ return idx;
1515
+ };
1516
+
1236
1517
  const generateVar = (scope, decl) => {
1237
- const out = [];
1518
+ let out = [];
1238
1519
 
1239
1520
  const topLevel = scope.name === 'main';
1240
1521
 
1241
1522
  // global variable if in top scope (main) and var ..., or if wanted
1242
- const global = decl.kind === 'var';
1523
+ const global = topLevel || decl._bare; // decl.kind === 'var';
1243
1524
  const target = global ? globals : scope.locals;
1244
1525
 
1245
1526
  for (const x of decl.declarations) {
@@ -1260,43 +1541,13 @@ const generateVar = (scope, decl) => {
1260
1541
  continue; // always ignore
1261
1542
  }
1262
1543
 
1263
- let idx;
1264
- // already declared
1265
- if (target[name]) {
1266
- // parser should catch this but sanity check anyway
1267
- if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
1544
+ let idx = allocVar(scope, name, global);
1268
1545
 
1269
- idx = target[name].idx;
1270
- } else {
1271
- idx = global ? globalInd++ : scope.localInd++;
1272
- target[name] = { idx, type: valtypeBinary };
1273
- }
1274
-
1275
- typeStates[name] = x.init ? getNodeType(scope, x.init) : TYPES.undefined;
1546
+ out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
1276
1547
 
1277
1548
  // x.init ??= DEFAULT_VALUE;
1278
1549
  if (x.init) {
1279
- out.push(...generate(scope, x.init, global, name));
1280
-
1281
- // if our value is the result of a function, infer the type from that func's return value
1282
- if (out[out.length - 1][0] === Opcodes.call) {
1283
- const ind = out[out.length - 1][1];
1284
- if (ind >= importedFuncs.length) { // not an imported func
1285
- const func = funcs.find(x => x.index === ind);
1286
- if (!func) throw new Error('could not find func being called as var value to infer type'); // sanity check
1287
-
1288
- const returns = func.returns;
1289
- if (returns.length > 1) throw new Error('func returning >1 value being set as 1 local'); // sanity check
1290
-
1291
- target[name].type = func.returns[0];
1292
- if (target[name].type === Valtype.v128) {
1293
- // specify vec subtype inferred from first vec type in function name
1294
- target[name].vecType = func.name.split('_').find(x => x.includes('x'));
1295
- }
1296
- } else {
1297
- // we do not have imports that return yet, ignore for now
1298
- }
1299
- }
1550
+ out = out.concat(generate(scope, x.init, global, name));
1300
1551
 
1301
1552
  out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
1302
1553
  }
@@ -1345,13 +1596,60 @@ const generateAssign = (scope, decl) => {
1345
1596
  ];
1346
1597
  }
1347
1598
 
1599
+ const op = decl.operator.slice(0, -1) || '=';
1600
+
1601
+ // arr[i] | str[i]
1602
+ if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1603
+ const name = decl.left.object.name;
1604
+ const pointer = arrays.get(name);
1605
+
1606
+ const aotPointer = pointer != null;
1607
+
1608
+ const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1609
+ const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1610
+
1611
+ const parentType = getNodeType(scope, decl.left.object);
1612
+
1613
+ return [
1614
+ ...(aotPointer ? [] : [
1615
+ ...generate(scope, decl.left.object),
1616
+ Opcodes.i32_to_u
1617
+ ]),
1618
+
1619
+ // get index as valtype
1620
+ ...generate(scope, decl.left.property),
1621
+
1622
+ // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
1623
+ Opcodes.i32_to_u,
1624
+ ...number(ValtypeSize[valtype], Valtype.i32),
1625
+ [ Opcodes.i32_mul ],
1626
+ ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
1627
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1628
+
1629
+ ...(op === '=' ? generate(scope, decl.right, false, name) : performOp(scope, op, [
1630
+ [ Opcodes.local_get, pointerTmp ],
1631
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1632
+ ], generate(scope, decl.right), parentType === TYPES._array ? TYPES.number : TYPES.string, getNodeType(scope, decl.right), false, name, true)),
1633
+ [ Opcodes.local_tee, newValueTmp ],
1634
+
1635
+ ...(parentType === TYPES._array ? [
1636
+ [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1637
+ ] : [
1638
+ Opcodes.i32_to_u,
1639
+ [ StoreOps.i16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1640
+ ]),
1641
+
1642
+ [ Opcodes.local_get, newValueTmp ]
1643
+ ];
1644
+ }
1645
+
1348
1646
  const [ local, isGlobal ] = lookupName(scope, name);
1349
1647
 
1350
1648
  if (local === undefined) {
1351
- // todo: this should be a devtools/repl/??? only thing
1649
+ // todo: this should be a sloppy mode only thing
1352
1650
 
1353
1651
  // only allow = for this
1354
- if (decl.operator !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1652
+ if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1355
1653
 
1356
1654
  if (builtinVars[name]) {
1357
1655
  // just return rhs (eg `NaN = 2`)
@@ -1360,14 +1658,12 @@ const generateAssign = (scope, decl) => {
1360
1658
 
1361
1659
  // set global and return (eg a = 2)
1362
1660
  return [
1363
- ...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
1661
+ ...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
1364
1662
  [ Opcodes.global_get, globals[name].idx ]
1365
1663
  ];
1366
1664
  }
1367
1665
 
1368
- if (decl.operator === '=') {
1369
- typeStates[name] = getNodeType(scope, decl.right);
1370
-
1666
+ if (op === '=') {
1371
1667
  return [
1372
1668
  ...generate(scope, decl.right, isGlobal, name),
1373
1669
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
@@ -1375,7 +1671,6 @@ const generateAssign = (scope, decl) => {
1375
1671
  ];
1376
1672
  }
1377
1673
 
1378
- const op = decl.operator.slice(0, -1);
1379
1674
  if (op === '||' || op === '&&' || op === '??') {
1380
1675
  // todo: is this needed?
1381
1676
  // for logical assignment ops, it is not left @= right ~= left = left @ right
@@ -1422,7 +1717,7 @@ const generateUnary = (scope, decl) => {
1422
1717
 
1423
1718
  case '!':
1424
1719
  // !=
1425
- return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
1720
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
1426
1721
 
1427
1722
  case '~':
1428
1723
  // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
@@ -1467,11 +1762,16 @@ const generateUnary = (scope, decl) => {
1467
1762
  return out;
1468
1763
 
1469
1764
  case 'typeof':
1470
- const type = getNodeType(scope, decl.argument) ?? TYPES.number;
1471
-
1472
- // for custom types, just return object
1473
- if (type > 0xffffffffffff7) return number(TYPES.object);
1474
- return number(type);
1765
+ // todo: do not use typeswitch, use custom bc instead
1766
+ // todo: _array & _regexp -> object. others when impl'd
1767
+ return typeSwitch(scope, getNodeType(scope, decl.argument), {
1768
+ [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
1769
+ [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
1770
+ [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
1771
+ [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
1772
+ [TYPES.object]: makeString(scope, 'object', false, '#typeof_result'),
1773
+ [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
1774
+ });
1475
1775
 
1476
1776
  default:
1477
1777
  return todo(`unary operator ${decl.operator} not implemented yet`);
@@ -1510,9 +1810,9 @@ const generateUpdate = (scope, decl) => {
1510
1810
  };
1511
1811
 
1512
1812
  const generateIf = (scope, decl) => {
1513
- const out = truthy(scope, generate(scope, decl.test), decl.test);
1813
+ const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test), false, true);
1514
1814
 
1515
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1815
+ out.push([ Opcodes.if, Blocktype.void ]);
1516
1816
  depth.push('if');
1517
1817
 
1518
1818
  const consOut = generate(scope, decl.consequent);
@@ -1541,9 +1841,21 @@ const generateConditional = (scope, decl) => {
1541
1841
 
1542
1842
  out.push(...generate(scope, decl.consequent));
1543
1843
 
1844
+ // note type
1845
+ out.push(
1846
+ ...getNodeType(scope, decl.consequent),
1847
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1848
+ );
1849
+
1544
1850
  out.push([ Opcodes.else ]);
1545
1851
  out.push(...generate(scope, decl.alternate));
1546
1852
 
1853
+ // note type
1854
+ out.push(
1855
+ ...getNodeType(scope, decl.alternate),
1856
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1857
+ );
1858
+
1547
1859
  out.push([ Opcodes.end ]);
1548
1860
  depth.pop();
1549
1861
 
@@ -1603,18 +1915,106 @@ const generateWhile = (scope, decl) => {
1603
1915
  const generateForOf = (scope, decl) => {
1604
1916
  const out = [];
1605
1917
 
1918
+ const rightType = getNodeType(scope, decl.right);
1919
+ const valtypeSize = rightType === TYPES._array ? ValtypeSize[valtype] : ValtypeSize.i16; // presume array (:()
1920
+
1921
+ // todo: for of inside for of might fuck up?
1922
+ const pointer = localTmp(scope, 'forof_base_pointer', Valtype.i32);
1923
+ const length = localTmp(scope, 'forof_length', Valtype.i32);
1924
+ const counter = localTmp(scope, 'forof_counter', Valtype.i32);
1925
+
1926
+ out.push(
1927
+ // set pointer as right
1928
+ ...generate(scope, decl.right),
1929
+ Opcodes.i32_to_u,
1930
+ [ Opcodes.local_set, pointer ],
1931
+
1932
+ // get length
1933
+ [ Opcodes.local_get, pointer ],
1934
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
1935
+ [ Opcodes.local_set, length ]
1936
+ );
1937
+
1606
1938
  out.push([ Opcodes.loop, Blocktype.void ]);
1607
- depth.push('while');
1939
+ depth.push('forof');
1608
1940
 
1609
- out.push(...generate(scope, decl.test));
1610
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1611
- depth.push('if');
1941
+ // setup local for left
1942
+ generate(scope, decl.left);
1612
1943
 
1613
- out.push(...generate(scope, decl.body));
1944
+ const leftName = decl.left.declarations[0].id.name;
1614
1945
 
1615
- out.push([ Opcodes.br, 1 ]);
1616
- out.push([ Opcodes.end ], [ Opcodes.end ]);
1617
- depth.pop(); depth.pop();
1946
+ // set type for local
1947
+ out.push(...setType(scope, leftName, rightType === TYPES._array ? TYPES.number : TYPES.string));
1948
+
1949
+ const [ local, isGlobal ] = lookupName(scope, leftName);
1950
+
1951
+ if (rightType === TYPES._array) { // array
1952
+ out.push(
1953
+ [ Opcodes.local_get, pointer ],
1954
+ [ Opcodes.load, Math.log2(valtypeSize) - 1, ...unsignedLEB128(ValtypeSize.i32) ]
1955
+ );
1956
+ } else { // string
1957
+ const [ newOut, newPointer ] = makeArray(scope, {
1958
+ rawElements: new Array(1)
1959
+ }, isGlobal, leftName, true, 'i16');
1960
+
1961
+ out.push(
1962
+ // setup new/out array
1963
+ ...newOut,
1964
+ [ Opcodes.drop ],
1965
+
1966
+ ...number(0, Valtype.i32), // base 0 for store after
1967
+
1968
+ // load current string ind {arg}
1969
+ [ Opcodes.local_get, pointer ],
1970
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
1971
+
1972
+ // store to new string ind 0
1973
+ [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
1974
+
1975
+ // return new string (page)
1976
+ ...number(newPointer)
1977
+ );
1978
+ }
1979
+
1980
+ // set left value
1981
+ out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ]);
1982
+
1983
+ out.push(
1984
+ [ Opcodes.block, Blocktype.void ],
1985
+ [ Opcodes.block, Blocktype.void ]
1986
+ );
1987
+ depth.push('block');
1988
+ depth.push('block');
1989
+
1990
+ out.push(
1991
+ ...generate(scope, decl.body),
1992
+ [ Opcodes.end ]
1993
+ );
1994
+ depth.pop();
1995
+
1996
+ out.push(
1997
+ // increment iter pointer by valtype size
1998
+ [ Opcodes.local_get, pointer ],
1999
+ ...number(valtypeSize, Valtype.i32),
2000
+ [ Opcodes.i32_add ],
2001
+ [ Opcodes.local_set, pointer ],
2002
+
2003
+ // increment counter by 1
2004
+ [ Opcodes.local_get, counter ],
2005
+ ...number(1, Valtype.i32),
2006
+ [ Opcodes.i32_add ],
2007
+ [ Opcodes.local_tee, counter ],
2008
+
2009
+ // loop if counter != length
2010
+ [ Opcodes.local_get, length ],
2011
+ [ Opcodes.i32_ne ],
2012
+ [ Opcodes.br_if, 1 ],
2013
+
2014
+ [ Opcodes.end ], [ Opcodes.end ]
2015
+ );
2016
+ depth.pop();
2017
+ depth.pop();
1618
2018
 
1619
2019
  return out;
1620
2020
  };
@@ -1705,19 +2105,19 @@ const generateAssignPat = (scope, decl) => {
1705
2105
  };
1706
2106
 
1707
2107
  let pages = new Map();
1708
- const allocPage = reason => {
1709
- if (pages.has(reason)) return pages.get(reason);
2108
+ const allocPage = (reason, type) => {
2109
+ if (pages.has(reason)) return pages.get(reason).ind;
1710
2110
 
1711
- let ind = pages.size;
1712
- pages.set(reason, ind);
2111
+ const ind = pages.size;
2112
+ pages.set(reason, { ind, type });
1713
2113
 
1714
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason}`);
2114
+ if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
1715
2115
 
1716
2116
  return ind;
1717
2117
  };
1718
2118
 
1719
2119
  const freePage = reason => {
1720
- let ind = pages.get(reason);
2120
+ const { ind } = pages.get(reason);
1721
2121
  pages.delete(reason);
1722
2122
 
1723
2123
  if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
@@ -1734,7 +2134,7 @@ const itemTypeToValtype = {
1734
2134
  i16: 'i32'
1735
2135
  };
1736
2136
 
1737
- const storeOps = {
2137
+ const StoreOps = {
1738
2138
  i32: Opcodes.i32_store,
1739
2139
  i64: Opcodes.i64_store,
1740
2140
  f64: Opcodes.f64_store,
@@ -1743,13 +2143,31 @@ const storeOps = {
1743
2143
  i16: Opcodes.i32_store16
1744
2144
  };
1745
2145
 
2146
+ let data = [];
2147
+
2148
+ const compileBytes = (val, itemType, signed = true) => {
2149
+ switch (itemType) {
2150
+ case 'i8': return [ val % 256 ];
2151
+ case 'i16': return [ val % 256, Math.floor(val / 256) ];
2152
+
2153
+ case 'i32':
2154
+ case 'i64':
2155
+ return enforceFourBytes(signedLEB128(val));
2156
+
2157
+ case 'f64': return ieee754_binary64(val);
2158
+ }
2159
+ };
2160
+
1746
2161
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
1747
2162
  const out = [];
1748
2163
 
2164
+ let firstAssign = false;
1749
2165
  if (!arrays.has(name) || name === '$undeclared') {
2166
+ firstAssign = true;
2167
+
1750
2168
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1751
2169
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1752
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`) * pageSize);
2170
+ arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
1753
2171
  }
1754
2172
 
1755
2173
  const pointer = arrays.get(name);
@@ -1757,8 +2175,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1757
2175
  const useRawElements = !!decl.rawElements;
1758
2176
  const elements = useRawElements ? decl.rawElements : decl.elements;
1759
2177
 
2178
+ const valtype = itemTypeToValtype[itemType];
1760
2179
  const length = elements.length;
1761
2180
 
2181
+ if (firstAssign && useRawElements) {
2182
+ let bytes = compileBytes(length, 'i32');
2183
+
2184
+ if (!initEmpty) for (let i = 0; i < length; i++) {
2185
+ if (elements[i] == null) continue;
2186
+
2187
+ bytes.push(...compileBytes(elements[i], itemType));
2188
+ }
2189
+
2190
+ data.push({
2191
+ offset: pointer,
2192
+ bytes
2193
+ });
2194
+
2195
+ // local value as pointer
2196
+ out.push(...number(pointer));
2197
+
2198
+ return [ out, pointer ];
2199
+ }
2200
+
1762
2201
  // store length as 0th array
1763
2202
  out.push(
1764
2203
  ...number(0, Valtype.i32),
@@ -1766,8 +2205,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1766
2205
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
1767
2206
  );
1768
2207
 
1769
- const storeOp = storeOps[itemType];
1770
- const valtype = itemTypeToValtype[itemType];
2208
+ const storeOp = StoreOps[itemType];
1771
2209
 
1772
2210
  if (!initEmpty) for (let i = 0; i < length; i++) {
1773
2211
  if (elements[i] == null) continue;
@@ -1785,11 +2223,33 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1785
2223
  return [ out, pointer ];
1786
2224
  };
1787
2225
 
2226
+ const makeString = (scope, str, global = false, name = '$undeclared') => {
2227
+ const rawElements = new Array(str.length);
2228
+ for (let i = 0; i < str.length; i++) {
2229
+ rawElements[i] = str.charCodeAt(i);
2230
+ }
2231
+
2232
+ return makeArray(scope, {
2233
+ rawElements
2234
+ }, global, name, false, 'i16')[0];
2235
+ };
2236
+
1788
2237
  let arrays = new Map();
1789
2238
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
1790
2239
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
1791
2240
  };
1792
2241
 
2242
+ let varMetadata = new Map();
2243
+ const addVarMeta = (_name, obj) => {
2244
+ const name = _name ?? '$undeclared';
2245
+ if (!varMetadata.has(name)) varMetadata.set(name, {});
2246
+
2247
+ const meta = varMetadata.get(name);
2248
+ for (const k in obj) {
2249
+ meta[k] = obj[k];
2250
+ }
2251
+ };
2252
+
1793
2253
  export const generateMember = (scope, decl, _global, _name) => {
1794
2254
  const type = getNodeType(scope, decl.object);
1795
2255
 
@@ -1927,16 +2387,14 @@ const generateFunc = (scope, decl) => {
1927
2387
  const innerScope = {
1928
2388
  locals: {},
1929
2389
  localInd: 0,
1930
- returns: [ valtypeBinary ],
1931
- returnType: null,
1932
- memory: false,
2390
+ // value, type
2391
+ returns: [ valtypeBinary, Valtype.i32 ],
1933
2392
  throws: false,
1934
2393
  name
1935
2394
  };
1936
2395
 
1937
2396
  for (let i = 0; i < params.length; i++) {
1938
- const param = params[i];
1939
- innerScope.locals[param] = { idx: innerScope.localInd++, type: valtypeBinary };
2397
+ allocVar(innerScope, params[i], false);
1940
2398
  }
1941
2399
 
1942
2400
  let body = objectHack(decl.body);
@@ -1951,9 +2409,8 @@ const generateFunc = (scope, decl) => {
1951
2409
  const wasm = generate(innerScope, body);
1952
2410
  const func = {
1953
2411
  name,
1954
- params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
2412
+ params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
1955
2413
  returns: innerScope.returns,
1956
- returnType: innerScope.returnType,
1957
2414
  locals: innerScope.locals,
1958
2415
  throws: innerScope.throws,
1959
2416
  index: currentFuncIndex++
@@ -1967,10 +2424,13 @@ const generateFunc = (scope, decl) => {
1967
2424
  }
1968
2425
  }
1969
2426
 
1970
- if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
1971
- wasm.push(...number(0), [ Opcodes.return ]);
1972
-
1973
- if (func.returnType === null) func.returnType = TYPES.undefined;
2427
+ // add end return if not found
2428
+ if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
2429
+ wasm.push(
2430
+ ...number(0),
2431
+ ...number(TYPES.undefined, Valtype.i32),
2432
+ [ Opcodes.return ]
2433
+ );
1974
2434
  }
1975
2435
 
1976
2436
  // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
@@ -2092,10 +2552,10 @@ const generateFunc = (scope, decl) => {
2092
2552
  };
2093
2553
 
2094
2554
  const generateCode = (scope, decl) => {
2095
- const out = [];
2555
+ let out = [];
2096
2556
 
2097
2557
  for (const x of decl.body) {
2098
- out.push(...generate(scope, x));
2558
+ out = out.concat(generate(scope, x));
2099
2559
  }
2100
2560
 
2101
2561
  return out;
@@ -2131,6 +2591,18 @@ const internalConstrs = {
2131
2591
  ];
2132
2592
  },
2133
2593
  type: TYPES._array
2594
+ },
2595
+
2596
+ __Array_of: {
2597
+ // this is not a constructor but best fits internal structure here
2598
+ generate: (scope, decl, global, name) => {
2599
+ // Array.of(i0, i1, ...)
2600
+ return generateArray(scope, {
2601
+ elements: decl.arguments
2602
+ }, global, name);
2603
+ },
2604
+ type: TYPES._array,
2605
+ notConstr: true
2134
2606
  }
2135
2607
  };
2136
2608
 
@@ -2142,9 +2614,10 @@ export default program => {
2142
2614
  funcs = [];
2143
2615
  funcIndex = {};
2144
2616
  depth = [];
2145
- typeStates = {};
2146
2617
  arrays = new Map();
2618
+ varMetadata = new Map();
2147
2619
  pages = new Map();
2620
+ data = [];
2148
2621
  currentFuncIndex = importedFuncs.length;
2149
2622
 
2150
2623
  globalThis.valtype = 'f64';
@@ -2194,18 +2667,23 @@ export default program => {
2194
2667
  body: program.body
2195
2668
  };
2196
2669
 
2670
+ if (process.argv.includes('-ast-log')) console.log(program.body.body);
2671
+
2197
2672
  generateFunc(scope, program);
2198
2673
 
2199
2674
  const main = funcs[funcs.length - 1];
2200
2675
  main.export = true;
2201
- main.returns = [ valtypeBinary ];
2676
+ main.returns = [ valtypeBinary, Valtype.i32 ];
2202
2677
 
2203
2678
  const lastInst = main.wasm[main.wasm.length - 1] ?? [ Opcodes.end ];
2204
2679
  if (lastInst[0] === Opcodes.drop) {
2205
2680
  main.wasm.splice(main.wasm.length - 1, 1);
2681
+ // main.wasm.splice(main.wasm.length - 2, 2);
2206
2682
 
2207
2683
  const finalStatement = program.body.body[program.body.body.length - 1];
2208
- main.returnType = getNodeType(main, finalStatement);
2684
+ // main.returnType = getNodeType(main, finalStatement);
2685
+
2686
+ main.wasm.push(...getNodeType(main, finalStatement));
2209
2687
  }
2210
2688
 
2211
2689
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
@@ -2221,5 +2699,5 @@ export default program => {
2221
2699
  // if blank main func and other exports, remove it
2222
2700
  if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
2223
2701
 
2224
- return { funcs, globals, tags, exceptions, pages };
2702
+ return { funcs, globals, tags, exceptions, pages, data };
2225
2703
  };