porffor 0.0.0-44bc2d8 → 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;
@@ -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,8 +109,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
101
109
  case 'WhileStatement':
102
110
  return generateWhile(scope, decl);
103
111
 
104
- /* case 'ForOfStatement':
105
- return generateForOf(scope, decl); */
112
+ case 'ForOfStatement':
113
+ return generateForOf(scope, decl);
106
114
 
107
115
  case 'BreakStatement':
108
116
  return generateBreak(scope, decl);
@@ -144,45 +152,65 @@ const generate = (scope, decl, global = false, name = undefined) => {
144
152
 
145
153
  return [];
146
154
 
147
- case 'TaggedTemplateExpression':
148
- // hack for inline asm
149
- if (decl.tag.name !== 'asm') return todo('tagged template expressions not implemented');
155
+ case 'TaggedTemplateExpression': {
156
+ const funcs = {
157
+ asm: str => {
158
+ let out = [];
150
159
 
151
- const str = decl.quasi.quasis[0].value.raw;
152
- let out = [];
160
+ for (const line of str.split('\n')) {
161
+ const asm = line.trim().split(';;')[0].split(' ');
162
+ if (asm[0] === '') continue; // blank
153
163
 
154
- for (const line of str.split('\n')) {
155
- const asm = line.trim().split(';;')[0].split(' ');
156
- 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
+ }
157
169
 
158
- if (asm[0] === 'local') {
159
- const [ name, idx, type ] = asm.slice(1);
160
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
161
- continue;
162
- }
170
+ if (asm[0] === 'returns') {
171
+ scope.returns = asm.slice(1).map(x => Valtype[x]);
172
+ continue;
173
+ }
163
174
 
164
- if (asm[0] === 'returns') {
165
- scope.returns = asm.slice(1).map(x => Valtype[x]);
166
- continue;
167
- }
175
+ if (asm[0] === 'memory') {
176
+ allocPage('asm instrinsic');
177
+ // todo: add to store/load offset insts
178
+ continue;
179
+ }
168
180
 
169
- if (asm[0] === 'memory') {
170
- scope.memory = true;
171
- allocPage('asm instrinsic');
172
- // todo: add to store/load offset insts
173
- continue;
174
- }
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
+ }
175
189
 
176
- let inst = Opcodes[asm[0].replace('.', '_')];
177
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
190
+ return out;
191
+ },
178
192
 
179
- if (!Array.isArray(inst)) inst = [ inst ];
180
- const immediates = asm.slice(1).map(x => parseInt(x));
193
+ __internal_print_type: str => {
194
+ const type = getType(scope, str) - TYPES.number;
181
195
 
182
- 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
+ }
183
205
  }
184
206
 
185
- 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
+ }
186
214
 
187
215
  default:
188
216
  return todo(`no generation for ${decl.type}!`);
@@ -272,19 +300,17 @@ const generateIdent = (scope, decl) => {
272
300
 
273
301
  const generateReturn = (scope, decl) => {
274
302
  if (decl.argument === null) {
275
- if (!scope.returnType) scope.returnType = TYPES.undefined;
276
-
277
303
  // just bare "return"
278
304
  return [
279
305
  ...number(UNDEFINED), // "undefined" if func returns
306
+ ...number(TYPES.undefined, Valtype.i32), // type undefined
280
307
  [ Opcodes.return ]
281
308
  ];
282
309
  }
283
310
 
284
- if (!scope.returnType) scope.returnType = getNodeType(scope, decl.argument);
285
-
286
311
  return [
287
312
  ...generate(scope, decl.argument),
313
+ ...getNodeType(scope, decl.argument),
288
314
  [ Opcodes.return ]
289
315
  ];
290
316
  };
@@ -298,11 +324,13 @@ const localTmp = (scope, name, type = valtypeBinary) => {
298
324
  return idx;
299
325
  };
300
326
 
301
- const performLogicOp = (scope, op, left, right) => {
327
+ const isIntOp = op => op[0] >= 0xb7 && op[0] <= 0xba;
328
+
329
+ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
302
330
  const checks = {
303
- '||': Opcodes.eqz,
304
- '&&': [ Opcodes.i32_to ]
305
- // todo: ??
331
+ '||': falsy,
332
+ '&&': truthy,
333
+ '??': nullish
306
334
  };
307
335
 
308
336
  if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
@@ -310,14 +338,52 @@ const performLogicOp = (scope, op, left, right) => {
310
338
  // generic structure for {a} OP {b}
311
339
  // -->
312
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
+
313
373
  return [
314
374
  ...left,
315
375
  [ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
316
- ...checks[op],
376
+ ...checks[op](scope, [], leftType, false, true),
317
377
  [ Opcodes.if, valtypeBinary ],
318
378
  ...right,
379
+ // note type
380
+ ...rightType,
381
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
319
382
  [ Opcodes.else ],
320
383
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
384
+ // note type
385
+ ...leftType,
386
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
321
387
  [ Opcodes.end ]
322
388
  ];
323
389
  };
@@ -328,12 +394,13 @@ const concatStrings = (scope, left, right, global, name, assign) => {
328
394
  // todo: optimize by looking up names in arrays and using that if exists?
329
395
  // todo: optimize this if using literals/known lengths?
330
396
 
331
- scope.memory = true;
332
-
333
397
  const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
334
398
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
335
399
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
336
400
 
401
+ const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
402
+ if (aotWFA) addVarMeta(name, { wellFormed: undefined });
403
+
337
404
  if (assign) {
338
405
  const pointer = arrays.get(name ?? '$undeclared');
339
406
 
@@ -464,8 +531,6 @@ const compareStrings = (scope, left, right) => {
464
531
  // todo: optimize by looking up names in arrays and using that if exists?
465
532
  // todo: optimize this if using literals/known lengths?
466
533
 
467
- scope.memory = true;
468
-
469
534
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
470
535
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
471
536
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
@@ -563,102 +628,143 @@ const compareStrings = (scope, left, right) => {
563
628
  ];
564
629
  };
565
630
 
566
- const falsy = (scope, wasm, type) => {
567
- // arrays are always truthy
568
- if (type === TYPES._array) return [
631
+ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
632
+ if (isIntOp(wasm[wasm.length - 1])) return [
569
633
  ...wasm,
570
- [ Opcodes.drop ],
571
- number(0)
634
+ ...(!intIn && intOut ? Opcodes.i32_to_u : [])
572
635
  ];
573
636
 
574
- if (type === TYPES.string) {
575
- // if "" (length = 0)
576
- return [
577
- // pointer
578
- ...wasm,
579
-
580
- // get length
581
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
582
-
583
- // if length == 0
584
- [ Opcodes.i32_eqz ],
585
- Opcodes.i32_from_u
586
- ]
587
- }
588
-
589
- // if = 0
637
+ const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
590
638
  return [
591
639
  ...wasm,
592
-
593
- ...Opcodes.eqz,
594
- Opcodes.i32_from_u
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)
595
671
  ];
596
672
  };
597
673
 
598
- const truthy = (scope, wasm, type) => {
599
- // arrays are always truthy
600
- if (type === TYPES._array) return [
674
+ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
675
+ const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
676
+ return [
601
677
  ...wasm,
602
- [ Opcodes.drop ],
603
- number(1)
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)
604
705
  ];
706
+ };
605
707
 
606
- if (type === TYPES.string) {
607
- // if not "" (length = 0)
608
- return [
609
- // pointer
610
- ...wasm,
611
-
612
- // get length
613
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
614
-
615
- // if length != 0
616
- /* [ Opcodes.i32_eqz ],
617
- [ Opcodes.i32_eqz ], */
618
- Opcodes.i32_from_u
619
- ]
620
- }
621
-
622
- // if != 0
708
+ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
709
+ const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
623
710
  return [
624
711
  ...wasm,
625
-
626
- /* Opcodes.eqz,
627
- [ Opcodes.i32_eqz ],
628
- Opcodes.i32_from */
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)
629
731
  ];
630
732
  };
631
733
 
632
734
  const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
633
735
  if (op === '||' || op === '&&' || op === '??') {
634
- return performLogicOp(scope, op, left, right);
736
+ return performLogicOp(scope, op, left, right, leftType, rightType);
635
737
  }
636
738
 
637
- 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);
638
740
 
639
- // if strict (in)equal and known types mismatch, return false (===)/true (!==)
640
- if ((op === '===' || op === '!==') && leftType && rightType && leftType !== rightType) {
641
- return [
642
- ...left,
643
- ...right,
741
+ const startOut = [], endOut = [];
742
+ const finalise = out => startOut.concat(out, endOut);
644
743
 
645
- // drop values
646
- [ Opcodes.drop ],
647
- [ 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
+ );
648
751
 
649
- // return false (===)/true (!==)
650
- ...number(op === '===' ? 0 : 1, Valtype.i32)
651
- ];
752
+ endOut.push(
753
+ [ Opcodes.i32_and ]
754
+ );
652
755
  }
653
756
 
757
+ // todo: if equality op and an operand is undefined, return false
758
+ // todo: niche null hell with 0
759
+
654
760
  if (leftType === TYPES.string || rightType === TYPES.string) {
655
761
  if (op === '+') {
656
762
  // string concat (a + b)
657
- return concatStrings(scope, left, right, _global, _name, assign);
763
+ return finalise(concatStrings(scope, left, right, _global, _name, assign));
658
764
  }
659
765
 
660
- // any other math op, NaN
661
- if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
766
+ // not an equality op, NaN
767
+ if (!eqOp) return finalise(number(NaN));
662
768
 
663
769
  // else leave bool ops
664
770
  // todo: convert string to number if string and number/bool
@@ -666,14 +772,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
666
772
 
667
773
  // string comparison
668
774
  if (op === '===' || op === '==') {
669
- return compareStrings(scope, left, right);
775
+ return finalise(compareStrings(scope, left, right));
670
776
  }
671
777
 
672
778
  if (op === '!==' || op === '!=') {
673
- return [
779
+ return finalise([
674
780
  ...compareStrings(scope, left, right),
675
781
  [ Opcodes.i32_eqz ]
676
- ];
782
+ ]);
677
783
  }
678
784
  }
679
785
 
@@ -685,39 +791,33 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
685
791
  includeBuiltin(scope, builtinName);
686
792
  const idx = funcIndex[builtinName];
687
793
 
688
- return [
794
+ return finalise([
689
795
  ...left,
690
796
  ...right,
691
797
  [ Opcodes.call, idx ]
692
- ];
798
+ ]);
693
799
  }
694
800
 
695
801
  if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
696
802
 
697
803
  if (!Array.isArray(ops)) ops = [ ops ];
698
804
 
699
- return [
805
+ return finalise([
700
806
  ...left,
701
807
  ...right,
702
808
  ops
703
- ];
809
+ ]);
704
810
  };
705
811
 
706
- let binaryExpDepth = 0;
707
812
  const generateBinaryExp = (scope, decl, _global, _name) => {
708
- binaryExpDepth++;
709
-
710
- const out = [
711
- ...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
712
- ];
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);
713
814
 
714
815
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
715
816
 
716
- binaryExpDepth--;
717
817
  return out;
718
818
  };
719
819
 
720
- 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 = [] }) => {
721
821
  const existing = funcs.find(x => x.name === name);
722
822
  if (existing) return existing;
723
823
 
@@ -753,7 +853,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
753
853
  returns,
754
854
  returnType: TYPES[returnType ?? 'number'],
755
855
  wasm,
756
- memory,
757
856
  internal: true,
758
857
  index: currentFuncIndex++
759
858
  };
@@ -772,21 +871,22 @@ const includeBuiltin = (scope, builtin) => {
772
871
  };
773
872
 
774
873
  const generateLogicExp = (scope, decl) => {
775
- return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
874
+ return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
776
875
  };
777
876
 
778
877
  const TYPES = {
779
- number: 0xffffffffffff0,
780
- boolean: 0xffffffffffff1,
781
- string: 0xffffffffffff2,
782
- undefined: 0xffffffffffff3,
783
- object: 0xffffffffffff4,
784
- function: 0xffffffffffff5,
785
- symbol: 0xffffffffffff6,
786
- 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,
787
886
 
788
887
  // these are not "typeof" types but tracked internally
789
- _array: 0xffffffffffff8
888
+ _array: 0x10,
889
+ _regexp: 0x11
790
890
  };
791
891
 
792
892
  const TYPE_NAMES = {
@@ -802,101 +902,156 @@ const TYPE_NAMES = {
802
902
  [TYPES._array]: 'Array'
803
903
  };
804
904
 
805
- let typeStates = {};
806
-
807
905
  const getType = (scope, _name) => {
808
906
  const name = mapName(_name);
809
- if (scope.locals[name]) return typeStates[name];
810
907
 
811
- if (builtinVars[name]) return TYPES[builtinVars[name].type ?? 'number'];
812
- if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) return TYPES.function;
813
- 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;
814
914
 
815
- if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)]) return TYPES.function;
816
- 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;
817
917
 
818
- return TYPES.undefined;
918
+ return number(type, Valtype.i32);
819
919
  };
820
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
+
821
939
  const getNodeType = (scope, node) => {
822
- if (node.type === 'Literal') {
823
- if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
824
- return TYPES[typeof node.value];
825
- }
940
+ // console.trace(node);
826
941
 
827
- if (isFuncType(node.type)) {
828
- return TYPES.function;
829
- }
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;
830
946
 
831
- if (node.type === 'Identifier') {
832
- return getType(scope, node.name);
833
- }
947
+ return TYPES[typeof node.value];
948
+ }
834
949
 
835
- if (node.type === 'CallExpression' || node.type === 'NewExpression') {
836
- const name = node.callee.name;
837
- const func = funcs.find(x => x.name === name);
838
- 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
+ }
839
972
 
840
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
841
- 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;
842
975
 
843
- let protoFunc;
844
- // ident.func()
845
- if (name && name.startsWith('__')) {
846
- const spl = name.slice(2).split('_');
976
+ let protoFunc;
977
+ // ident.func()
978
+ if (name && name.startsWith('__')) {
979
+ const spl = name.slice(2).split('_');
847
980
 
848
- const baseName = spl.slice(0, -1).join('_');
849
- const baseType = getType(scope, baseName);
981
+ const baseName = spl.slice(0, -1).join('_');
982
+ const baseType = getType(scope, baseName);
850
983
 
851
- const func = spl[spl.length - 1];
852
- 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;
853
1002
  }
854
1003
 
855
- // literal.func()
856
- if (!name && node.callee.type === 'MemberExpression') {
857
- const baseType = getNodeType(scope, node.callee.object);
1004
+ if (node.type === 'ExpressionStatement') {
1005
+ return getNodeType(scope, node.expression);
1006
+ }
858
1007
 
859
- const func = node.callee.property.name;
860
- protoFunc = prototypeFuncs[baseType]?.[func];
1008
+ if (node.type === 'AssignmentExpression') {
1009
+ return getNodeType(scope, node.right);
861
1010
  }
862
1011
 
863
- if (protoFunc) return protoFunc.returnType;
864
- }
1012
+ if (node.type === 'ArrayExpression') {
1013
+ return TYPES._array;
1014
+ }
865
1015
 
866
- if (node.type === 'ExpressionStatement') {
867
- return getNodeType(scope, node.expression);
868
- }
1016
+ if (node.type === 'BinaryExpression') {
1017
+ if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
869
1018
 
870
- if (node.type === 'AssignmentExpression') {
871
- return getNodeType(scope, node.right);
872
- }
1019
+ if (node.operator === '+' && (getNodeType(scope, node.left) === TYPES.string || getNodeType(scope, node.right) === TYPES.string)) return TYPES.string;
1020
+ }
873
1021
 
874
- if (node.type === 'ArrayExpression') {
875
- return TYPES._array;
876
- }
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
+ }
877
1028
 
878
- if (node.type === 'BinaryExpression') {
879
- if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1029
+ if (node.type === 'MemberExpression') {
1030
+ const objectType = getNodeType(scope, node.object);
880
1031
 
881
- if (node.operator === '+' && (getNodeType(scope, node.left) === TYPES.string || getNodeType(scope, node.right) === TYPES.string)) return TYPES.string;
882
- }
1032
+ if (objectType === TYPES.string && node.computed) return TYPES.string;
1033
+ }
883
1034
 
884
- if (node.type === 'UnaryExpression') {
885
- if (node.operator === '!') return TYPES.boolean;
886
- if (node.operator === 'void') return TYPES.undefined;
887
- if (node.operator === 'delete') return TYPES.boolean;
888
- }
1035
+ if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
889
1036
 
890
- if (node.type === 'MemberExpression') {
891
- const objectType = getNodeType(scope, node.object);
1037
+ // presume
1038
+ // todo: warn here?
1039
+ return TYPES.number;
1040
+ };
892
1041
 
893
- if (objectType === TYPES.string && node.computed) return TYPES.string;
894
- }
1042
+ const ret = inner();
1043
+ if (typeof ret === 'number') return number(ret, Valtype.i32);
1044
+ return ret;
895
1045
  };
896
1046
 
897
1047
  const generateLiteral = (scope, decl, global, name) => {
898
1048
  if (decl.value === null) return number(NULL);
899
1049
 
1050
+ if (decl.regex) {
1051
+ scope.regex[name] = decl.regex;
1052
+ return number(1);
1053
+ }
1054
+
900
1055
  switch (typeof decl.value) {
901
1056
  case 'number':
902
1057
  return number(decl.value);
@@ -907,23 +1062,49 @@ const generateLiteral = (scope, decl, global, name) => {
907
1062
 
908
1063
  case 'string':
909
1064
  // this is a terrible hack which changes type strings ("number" etc) to known const number values
910
- switch (decl.value) {
911
- case 'number': return number(TYPES.number);
912
- case 'boolean': return number(TYPES.boolean);
913
- case 'string': return number(TYPES.string);
914
- case 'undefined': return number(TYPES.undefined);
915
- case 'object': return number(TYPES.object);
916
- case 'function': return number(TYPES.function);
917
- case 'symbol': return number(TYPES.symbol);
918
- case 'bigint': return number(TYPES.bigint);
919
- }
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;
920
1078
 
921
1079
  const str = decl.value;
922
1080
  const rawElements = new Array(str.length);
1081
+ let j = 0;
923
1082
  for (let i = 0; i < str.length; i++) {
924
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
+ }
925
1102
  }
926
1103
 
1104
+ // console.log(wellFormed, str);
1105
+
1106
+ if (aotWFA) addVarMeta(name, { wellFormed });
1107
+
927
1108
  return makeArray(scope, {
928
1109
  rawElements
929
1110
  }, global, name, false, 'i16')[0];
@@ -936,7 +1117,10 @@ const generateLiteral = (scope, decl, global, name) => {
936
1117
  const countLeftover = wasm => {
937
1118
  let count = 0, depth = 0;
938
1119
 
939
- for (const inst of wasm) {
1120
+ // console.trace(wasm.length);
1121
+
1122
+ for (let i = 0; i < wasm.length; i++) {
1123
+ const inst = wasm[i];
940
1124
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
941
1125
  if (inst[0] === Opcodes.if) count--;
942
1126
  if (inst[1] !== Blocktype.void) count++;
@@ -945,11 +1129,12 @@ const countLeftover = wasm => {
945
1129
  if (inst[0] === Opcodes.end) depth--;
946
1130
 
947
1131
  if (depth === 0)
948
- 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--;
949
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)) {}
950
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++;
951
1135
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
952
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;
953
1138
  else if (inst[0] === Opcodes.call) {
954
1139
  let func = funcs.find(x => x.index === inst[1]);
955
1140
  if (func) {
@@ -957,6 +1142,8 @@ const countLeftover = wasm => {
957
1142
  } else count--;
958
1143
  if (func) count += func.returns.length;
959
1144
  } else count--;
1145
+
1146
+ // console.log(count, decompile([ inst ]).slice(0, -1));
960
1147
  }
961
1148
 
962
1149
  return count;
@@ -968,6 +1155,13 @@ const disposeLeftover = wasm => {
968
1155
  for (let i = 0; i < leftover; i++) wasm.push([ Opcodes.drop ]);
969
1156
  };
970
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
+
971
1165
  const generateExp = (scope, decl) => {
972
1166
  const expression = decl.expression;
973
1167
 
@@ -1040,7 +1234,7 @@ const generateCall = (scope, decl, _global, _name) => {
1040
1234
  }
1041
1235
 
1042
1236
  let out = [];
1043
- let protoFunc, protoName, baseType, baseName = '$undeclared';
1237
+ let protoFunc, protoName, baseType, baseName;
1044
1238
  // ident.func()
1045
1239
  if (name && name.startsWith('__')) {
1046
1240
  const spl = name.slice(2).split('_');
@@ -1055,6 +1249,25 @@ const generateCall = (scope, decl, _global, _name) => {
1055
1249
 
1056
1250
  // literal.func()
1057
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
+
1058
1271
  baseType = getNodeType(scope, decl.callee.object);
1059
1272
 
1060
1273
  const func = decl.callee.property.name;
@@ -1063,11 +1276,36 @@ const generateCall = (scope, decl, _global, _name) => {
1063
1276
 
1064
1277
  out = generate(scope, decl.callee.object);
1065
1278
  out.push([ Opcodes.drop ]);
1279
+
1280
+ baseName = [...arrays.keys()].pop();
1066
1281
  }
1067
1282
 
1068
- if (protoFunc) {
1069
- scope.memory = true;
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);
1070
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
+
1308
+ if (protoFunc) {
1071
1309
  let pointer = arrays.get(baseName);
1072
1310
 
1073
1311
  if (pointer == null) {
@@ -1075,7 +1313,7 @@ const generateCall = (scope, decl, _global, _name) => {
1075
1313
  if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
1076
1314
 
1077
1315
  // register array
1078
- const [ , pointer ] = makeArray(scope, {
1316
+ 0, [ , pointer ] = makeArray(scope, {
1079
1317
  rawElements: new Array(0)
1080
1318
  }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
1081
1319
 
@@ -1095,28 +1333,39 @@ const generateCall = (scope, decl, _global, _name) => {
1095
1333
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) return arrayUtil.getLength(pointer)
1096
1334
 
1097
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;
1098
1337
 
1099
1338
  // use local for cached i32 length as commonly used
1100
1339
  let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1101
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
+
1102
1359
  return [
1103
1360
  ...out,
1104
1361
 
1105
- ...arrayUtil.getLengthI32(pointer),
1106
- [ Opcodes.local_set, lengthLocal ],
1362
+ ...(!lengthI32CacheUsed ? [] : [
1363
+ ...arrayUtil.getLengthI32(pointer),
1364
+ [ Opcodes.local_set, lengthLocal ],
1365
+ ]),
1107
1366
 
1108
1367
  [ Opcodes.block, valtypeBinary ],
1109
- ...protoFunc(pointer, {
1110
- cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
1111
- get: arrayUtil.getLength(pointer),
1112
- getI32: arrayUtil.getLengthI32(pointer),
1113
- set: value => arrayUtil.setLength(pointer, value),
1114
- setI32: value => arrayUtil.setLengthI32(pointer, value)
1115
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
1116
- return makeArray(scope, {
1117
- rawElements: new Array(length)
1118
- }, _global, _name, true, itemType);
1119
- }),
1368
+ ...protoOut,
1120
1369
  [ Opcodes.end ]
1121
1370
  ];
1122
1371
  }
@@ -1162,34 +1411,40 @@ const generateCall = (scope, decl, _global, _name) => {
1162
1411
 
1163
1412
  const func = funcs.find(x => x.index === idx);
1164
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
+
1165
1417
  let args = decl.arguments;
1166
- if (func && args.length < func.params.length) {
1418
+ if (func && args.length < paramCount) {
1167
1419
  // too little args, push undefineds
1168
- 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));
1169
1421
  }
1170
1422
 
1171
- if (func && args.length > func.params.length) {
1423
+ if (func && args.length > paramCount) {
1172
1424
  // too many args, slice extras off
1173
- args = args.slice(0, func.params.length);
1425
+ args = args.slice(0, paramCount);
1174
1426
  }
1175
1427
 
1176
- if (func && func.memory) scope.memory = true;
1177
1428
  if (func && func.throws) scope.throws = true;
1178
1429
 
1179
1430
  for (const arg of args) {
1180
- out.push(...generate(scope, arg));
1431
+ out = out.concat(generate(scope, arg));
1432
+ if (userFunc) out = out.concat(getNodeType(scope, arg));
1181
1433
  }
1182
1434
 
1183
1435
  out.push([ Opcodes.call, idx ]);
1184
1436
 
1437
+ // if user func, they return [ value, type ]
1438
+ if (userFunc) out.push([ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]);
1439
+
1185
1440
  return out;
1186
1441
  };
1187
1442
 
1188
1443
  const generateNew = (scope, decl, _global, _name) => {
1189
1444
  // hack: basically treat this as a normal call for builtins for now
1190
1445
  const name = mapName(decl.callee.name);
1191
- if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1192
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1446
+ if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1447
+ if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1193
1448
 
1194
1449
  return generateCall(scope, decl, _global, _name);
1195
1450
  };
@@ -1205,13 +1460,67 @@ const unhackName = name => {
1205
1460
  return name;
1206
1461
  };
1207
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
+
1208
1517
  const generateVar = (scope, decl) => {
1209
- const out = [];
1518
+ let out = [];
1210
1519
 
1211
1520
  const topLevel = scope.name === 'main';
1212
1521
 
1213
1522
  // global variable if in top scope (main) and var ..., or if wanted
1214
- const global = decl.kind === 'var';
1523
+ const global = topLevel || decl._bare; // decl.kind === 'var';
1215
1524
  const target = global ? globals : scope.locals;
1216
1525
 
1217
1526
  for (const x of decl.declarations) {
@@ -1232,43 +1541,13 @@ const generateVar = (scope, decl) => {
1232
1541
  continue; // always ignore
1233
1542
  }
1234
1543
 
1235
- let idx;
1236
- // already declared
1237
- if (target[name]) {
1238
- // parser should catch this but sanity check anyway
1239
- if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
1544
+ let idx = allocVar(scope, name, global);
1240
1545
 
1241
- idx = target[name].idx;
1242
- } else {
1243
- idx = global ? globalInd++ : scope.localInd++;
1244
- target[name] = { idx, type: valtypeBinary };
1245
- }
1246
-
1247
- typeStates[name] = x.init ? getNodeType(scope, x.init) : TYPES.undefined;
1546
+ out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
1248
1547
 
1249
1548
  // x.init ??= DEFAULT_VALUE;
1250
1549
  if (x.init) {
1251
- out.push(...generate(scope, x.init, global, name));
1252
-
1253
- // if our value is the result of a function, infer the type from that func's return value
1254
- if (out[out.length - 1][0] === Opcodes.call) {
1255
- const ind = out[out.length - 1][1];
1256
- if (ind >= importedFuncs.length) { // not an imported func
1257
- const func = funcs.find(x => x.index === ind);
1258
- if (!func) throw new Error('could not find func being called as var value to infer type'); // sanity check
1259
-
1260
- const returns = func.returns;
1261
- if (returns.length > 1) throw new Error('func returning >1 value being set as 1 local'); // sanity check
1262
-
1263
- target[name].type = func.returns[0];
1264
- if (target[name].type === Valtype.v128) {
1265
- // specify vec subtype inferred from first vec type in function name
1266
- target[name].vecType = func.name.split('_').find(x => x.includes('x'));
1267
- }
1268
- } else {
1269
- // we do not have imports that return yet, ignore for now
1270
- }
1271
- }
1550
+ out = out.concat(generate(scope, x.init, global, name));
1272
1551
 
1273
1552
  out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
1274
1553
  }
@@ -1297,8 +1576,6 @@ const generateAssign = (scope, decl) => {
1297
1576
  const name = decl.left.object.name;
1298
1577
  const pointer = arrays.get(name);
1299
1578
 
1300
- scope.memory = true;
1301
-
1302
1579
  const aotPointer = pointer != null;
1303
1580
 
1304
1581
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
@@ -1319,13 +1596,60 @@ const generateAssign = (scope, decl) => {
1319
1596
  ];
1320
1597
  }
1321
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
+
1322
1646
  const [ local, isGlobal ] = lookupName(scope, name);
1323
1647
 
1324
1648
  if (local === undefined) {
1325
- // todo: this should be a devtools/repl/??? only thing
1649
+ // todo: this should be a sloppy mode only thing
1326
1650
 
1327
1651
  // only allow = for this
1328
- if (decl.operator !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1652
+ if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1329
1653
 
1330
1654
  if (builtinVars[name]) {
1331
1655
  // just return rhs (eg `NaN = 2`)
@@ -1334,14 +1658,12 @@ const generateAssign = (scope, decl) => {
1334
1658
 
1335
1659
  // set global and return (eg a = 2)
1336
1660
  return [
1337
- ...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
1661
+ ...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
1338
1662
  [ Opcodes.global_get, globals[name].idx ]
1339
1663
  ];
1340
1664
  }
1341
1665
 
1342
- if (decl.operator === '=') {
1343
- typeStates[name] = getNodeType(scope, decl.right);
1344
-
1666
+ if (op === '=') {
1345
1667
  return [
1346
1668
  ...generate(scope, decl.right, isGlobal, name),
1347
1669
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
@@ -1349,8 +1671,26 @@ const generateAssign = (scope, decl) => {
1349
1671
  ];
1350
1672
  }
1351
1673
 
1674
+ if (op === '||' || op === '&&' || op === '??') {
1675
+ // todo: is this needed?
1676
+ // for logical assignment ops, it is not left @= right ~= left = left @ right
1677
+ // instead, left @ (left = right)
1678
+ // eg, x &&= y ~= x && (x = y)
1679
+
1680
+ return [
1681
+ ...performOp(scope, op, [
1682
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1683
+ ], [
1684
+ ...generate(scope, decl.right),
1685
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1686
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1687
+ ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1688
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1689
+ ];
1690
+ }
1691
+
1352
1692
  return [
1353
- ...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),
1693
+ ...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),
1354
1694
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1355
1695
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1356
1696
  ];
@@ -1377,13 +1717,14 @@ const generateUnary = (scope, decl) => {
1377
1717
 
1378
1718
  case '!':
1379
1719
  // !=
1380
- return falsy(scope, generate(scope, decl.argument));
1720
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
1381
1721
 
1382
1722
  case '~':
1723
+ // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
1383
1724
  return [
1384
1725
  ...generate(scope, decl.argument),
1385
1726
  Opcodes.i32_to,
1386
- [ Opcodes.i32_const, signedLEB128(-1) ],
1727
+ [ Opcodes.i32_const, ...signedLEB128(-1) ],
1387
1728
  [ Opcodes.i32_xor ],
1388
1729
  Opcodes.i32_from
1389
1730
  ];
@@ -1421,11 +1762,16 @@ const generateUnary = (scope, decl) => {
1421
1762
  return out;
1422
1763
 
1423
1764
  case 'typeof':
1424
- const type = getNodeType(scope, decl.argument) ?? TYPES.number;
1425
-
1426
- // for custom types, just return object
1427
- if (type > 0xffffffffffff7) return number(TYPES.object);
1428
- 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
+ });
1429
1775
 
1430
1776
  default:
1431
1777
  return todo(`unary operator ${decl.operator} not implemented yet`);
@@ -1464,9 +1810,9 @@ const generateUpdate = (scope, decl) => {
1464
1810
  };
1465
1811
 
1466
1812
  const generateIf = (scope, decl) => {
1467
- 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);
1468
1814
 
1469
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1815
+ out.push([ Opcodes.if, Blocktype.void ]);
1470
1816
  depth.push('if');
1471
1817
 
1472
1818
  const consOut = generate(scope, decl.consequent);
@@ -1495,9 +1841,21 @@ const generateConditional = (scope, decl) => {
1495
1841
 
1496
1842
  out.push(...generate(scope, decl.consequent));
1497
1843
 
1844
+ // note type
1845
+ out.push(
1846
+ ...getNodeType(scope, decl.consequent),
1847
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1848
+ );
1849
+
1498
1850
  out.push([ Opcodes.else ]);
1499
1851
  out.push(...generate(scope, decl.alternate));
1500
1852
 
1853
+ // note type
1854
+ out.push(
1855
+ ...getNodeType(scope, decl.alternate),
1856
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1857
+ );
1858
+
1501
1859
  out.push([ Opcodes.end ]);
1502
1860
  depth.pop();
1503
1861
 
@@ -1557,18 +1915,106 @@ const generateWhile = (scope, decl) => {
1557
1915
  const generateForOf = (scope, decl) => {
1558
1916
  const out = [];
1559
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
+
1560
1938
  out.push([ Opcodes.loop, Blocktype.void ]);
1561
- depth.push('while');
1939
+ depth.push('forof');
1562
1940
 
1563
- out.push(...generate(scope, decl.test));
1564
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1565
- depth.push('if');
1941
+ // setup local for left
1942
+ generate(scope, decl.left);
1566
1943
 
1567
- out.push(...generate(scope, decl.body));
1944
+ const leftName = decl.left.declarations[0].id.name;
1568
1945
 
1569
- out.push([ Opcodes.br, 1 ]);
1570
- out.push([ Opcodes.end ], [ Opcodes.end ]);
1571
- 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();
1572
2018
 
1573
2019
  return out;
1574
2020
  };
@@ -1659,13 +2105,22 @@ const generateAssignPat = (scope, decl) => {
1659
2105
  };
1660
2106
 
1661
2107
  let pages = new Map();
1662
- const allocPage = reason => {
1663
- if (pages.has(reason)) return pages.get(reason);
2108
+ const allocPage = (reason, type) => {
2109
+ if (pages.has(reason)) return pages.get(reason).ind;
2110
+
2111
+ const ind = pages.size;
2112
+ pages.set(reason, { ind, type });
2113
+
2114
+ if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2115
+
2116
+ return ind;
2117
+ };
1664
2118
 
1665
- let ind = pages.size;
1666
- pages.set(reason, ind);
2119
+ const freePage = reason => {
2120
+ const { ind } = pages.get(reason);
2121
+ pages.delete(reason);
1667
2122
 
1668
- if (codeLog) log('codegen', `allocated new page of memory (${ind}) | ${reason}`);
2123
+ if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
1669
2124
 
1670
2125
  return ind;
1671
2126
  };
@@ -1679,7 +2134,7 @@ const itemTypeToValtype = {
1679
2134
  i16: 'i32'
1680
2135
  };
1681
2136
 
1682
- const storeOps = {
2137
+ const StoreOps = {
1683
2138
  i32: Opcodes.i32_store,
1684
2139
  i64: Opcodes.i64_store,
1685
2140
  f64: Opcodes.f64_store,
@@ -1688,13 +2143,31 @@ const storeOps = {
1688
2143
  i16: Opcodes.i32_store16
1689
2144
  };
1690
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
+
1691
2161
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
1692
2162
  const out = [];
1693
2163
 
2164
+ let firstAssign = false;
1694
2165
  if (!arrays.has(name) || name === '$undeclared') {
2166
+ firstAssign = true;
2167
+
1695
2168
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1696
2169
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1697
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`) * pageSize);
2170
+ arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
1698
2171
  }
1699
2172
 
1700
2173
  const pointer = arrays.get(name);
@@ -1702,8 +2175,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1702
2175
  const useRawElements = !!decl.rawElements;
1703
2176
  const elements = useRawElements ? decl.rawElements : decl.elements;
1704
2177
 
2178
+ const valtype = itemTypeToValtype[itemType];
1705
2179
  const length = elements.length;
1706
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
+
1707
2201
  // store length as 0th array
1708
2202
  out.push(
1709
2203
  ...number(0, Valtype.i32),
@@ -1711,8 +2205,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1711
2205
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
1712
2206
  );
1713
2207
 
1714
- const storeOp = storeOps[itemType];
1715
- const valtype = itemTypeToValtype[itemType];
2208
+ const storeOp = StoreOps[itemType];
1716
2209
 
1717
2210
  if (!initEmpty) for (let i = 0; i < length; i++) {
1718
2211
  if (elements[i] == null) continue;
@@ -1727,16 +2220,36 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1727
2220
  // local value as pointer
1728
2221
  out.push(...number(pointer));
1729
2222
 
1730
- scope.memory = true;
1731
-
1732
2223
  return [ out, pointer ];
1733
2224
  };
1734
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
+
1735
2237
  let arrays = new Map();
1736
2238
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
1737
2239
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
1738
2240
  };
1739
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
+
1740
2253
  export const generateMember = (scope, decl, _global, _name) => {
1741
2254
  const type = getNodeType(scope, decl.object);
1742
2255
 
@@ -1747,8 +2260,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1747
2260
  const name = decl.object.name;
1748
2261
  const pointer = arrays.get(name);
1749
2262
 
1750
- scope.memory = true;
1751
-
1752
2263
  const aotPointer = pointer != null;
1753
2264
 
1754
2265
  return [
@@ -1768,8 +2279,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1768
2279
  const name = decl.object.name;
1769
2280
  const pointer = arrays.get(name);
1770
2281
 
1771
- scope.memory = true;
1772
-
1773
2282
  const aotPointer = pointer != null;
1774
2283
 
1775
2284
  if (type === TYPES._array) {
@@ -1878,15 +2387,14 @@ const generateFunc = (scope, decl) => {
1878
2387
  const innerScope = {
1879
2388
  locals: {},
1880
2389
  localInd: 0,
1881
- returns: [ valtypeBinary ],
1882
- memory: false,
2390
+ // value, type
2391
+ returns: [ valtypeBinary, Valtype.i32 ],
1883
2392
  throws: false,
1884
2393
  name
1885
2394
  };
1886
2395
 
1887
2396
  for (let i = 0; i < params.length; i++) {
1888
- const param = params[i];
1889
- innerScope.locals[param] = { idx: innerScope.localInd++, type: valtypeBinary };
2397
+ allocVar(innerScope, params[i], false);
1890
2398
  }
1891
2399
 
1892
2400
  let body = objectHack(decl.body);
@@ -1901,11 +2409,9 @@ const generateFunc = (scope, decl) => {
1901
2409
  const wasm = generate(innerScope, body);
1902
2410
  const func = {
1903
2411
  name,
1904
- 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),
1905
2413
  returns: innerScope.returns,
1906
- returnType: innerScope.returnType,
1907
2414
  locals: innerScope.locals,
1908
- memory: innerScope.memory,
1909
2415
  throws: innerScope.throws,
1910
2416
  index: currentFuncIndex++
1911
2417
  };
@@ -1918,8 +2424,13 @@ const generateFunc = (scope, decl) => {
1918
2424
  }
1919
2425
  }
1920
2426
 
1921
- if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
1922
- wasm.push(...number(0), [ Opcodes.return ]);
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
+ );
1923
2434
  }
1924
2435
 
1925
2436
  // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
@@ -1930,9 +2441,7 @@ const generateFunc = (scope, decl) => {
1930
2441
  if (local.type === Valtype.v128) {
1931
2442
  vecParams++;
1932
2443
 
1933
- /* func.memory = true; // mark func as using memory
1934
-
1935
- wasm.unshift( // add v128 load for param
2444
+ /* wasm.unshift( // add v128 load for param
1936
2445
  [ Opcodes.i32_const, 0 ],
1937
2446
  [ ...Opcodes.v128_load, 0, i * 16 ],
1938
2447
  [ Opcodes.local_set, local.idx ]
@@ -2043,10 +2552,10 @@ const generateFunc = (scope, decl) => {
2043
2552
  };
2044
2553
 
2045
2554
  const generateCode = (scope, decl) => {
2046
- const out = [];
2555
+ let out = [];
2047
2556
 
2048
2557
  for (const x of decl.body) {
2049
- out.push(...generate(scope, x));
2558
+ out = out.concat(generate(scope, x));
2050
2559
  }
2051
2560
 
2052
2561
  return out;
@@ -2082,6 +2591,18 @@ const internalConstrs = {
2082
2591
  ];
2083
2592
  },
2084
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
2085
2606
  }
2086
2607
  };
2087
2608
 
@@ -2093,9 +2614,10 @@ export default program => {
2093
2614
  funcs = [];
2094
2615
  funcIndex = {};
2095
2616
  depth = [];
2096
- typeStates = {};
2097
2617
  arrays = new Map();
2618
+ varMetadata = new Map();
2098
2619
  pages = new Map();
2620
+ data = [];
2099
2621
  currentFuncIndex = importedFuncs.length;
2100
2622
 
2101
2623
  globalThis.valtype = 'f64';
@@ -2145,18 +2667,23 @@ export default program => {
2145
2667
  body: program.body
2146
2668
  };
2147
2669
 
2670
+ if (process.argv.includes('-ast-log')) console.log(program.body.body);
2671
+
2148
2672
  generateFunc(scope, program);
2149
2673
 
2150
2674
  const main = funcs[funcs.length - 1];
2151
2675
  main.export = true;
2152
- main.returns = [ valtypeBinary ];
2676
+ main.returns = [ valtypeBinary, Valtype.i32 ];
2153
2677
 
2154
2678
  const lastInst = main.wasm[main.wasm.length - 1] ?? [ Opcodes.end ];
2155
2679
  if (lastInst[0] === Opcodes.drop) {
2156
2680
  main.wasm.splice(main.wasm.length - 1, 1);
2681
+ // main.wasm.splice(main.wasm.length - 2, 2);
2157
2682
 
2158
2683
  const finalStatement = program.body.body[program.body.body.length - 1];
2159
- main.returnType = getNodeType(main, finalStatement);
2684
+ // main.returnType = getNodeType(main, finalStatement);
2685
+
2686
+ main.wasm.push(...getNodeType(main, finalStatement));
2160
2687
  }
2161
2688
 
2162
2689
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
@@ -2172,5 +2699,5 @@ export default program => {
2172
2699
  // if blank main func and other exports, remove it
2173
2700
  if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
2174
2701
 
2175
- return { funcs, globals, tags, exceptions, pages };
2702
+ return { funcs, globals, tags, exceptions, pages, data };
2176
2703
  };