porffor 0.0.0-745e995 → 0.0.0-7d5ae9c

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,6 +109,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
101
109
  case 'WhileStatement':
102
110
  return generateWhile(scope, decl);
103
111
 
112
+ case 'ForOfStatement':
113
+ return generateForOf(scope, decl);
114
+
104
115
  case 'BreakStatement':
105
116
  return generateBreak(scope, decl);
106
117
 
@@ -141,45 +152,65 @@ const generate = (scope, decl, global = false, name = undefined) => {
141
152
 
142
153
  return [];
143
154
 
144
- case 'TaggedTemplateExpression':
145
- // hack for inline asm
146
- if (decl.tag.name !== 'asm') return todo('tagged template expressions not implemented');
155
+ case 'TaggedTemplateExpression': {
156
+ const funcs = {
157
+ asm: str => {
158
+ let out = [];
147
159
 
148
- const str = decl.quasi.quasis[0].value.raw;
149
- let out = [];
160
+ for (const line of str.split('\n')) {
161
+ const asm = line.trim().split(';;')[0].split(' ');
162
+ if (asm[0] === '') continue; // blank
150
163
 
151
- for (const line of str.split('\n')) {
152
- const asm = line.trim().split(';;')[0].split(' ');
153
- if (asm[0] === '') continue; // blank
164
+ if (asm[0] === 'local') {
165
+ const [ name, idx, type ] = asm.slice(1);
166
+ scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
167
+ continue;
168
+ }
154
169
 
155
- if (asm[0] === 'local') {
156
- const [ name, idx, type ] = asm.slice(1);
157
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
158
- continue;
159
- }
170
+ if (asm[0] === 'returns') {
171
+ scope.returns = asm.slice(1).map(x => Valtype[x]);
172
+ continue;
173
+ }
160
174
 
161
- if (asm[0] === 'returns') {
162
- scope.returns = asm.slice(1).map(x => Valtype[x]);
163
- continue;
164
- }
175
+ if (asm[0] === 'memory') {
176
+ allocPage('asm instrinsic');
177
+ // todo: add to store/load offset insts
178
+ continue;
179
+ }
165
180
 
166
- if (asm[0] === 'memory') {
167
- scope.memory = true;
168
- allocPage('asm instrinsic');
169
- // todo: add to store/load offset insts
170
- continue;
171
- }
181
+ let inst = Opcodes[asm[0].replace('.', '_')];
182
+ if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
183
+
184
+ if (!Array.isArray(inst)) inst = [ inst ];
185
+ const immediates = asm.slice(1).map(x => parseInt(x));
172
186
 
173
- let inst = Opcodes[asm[0].replace('.', '_')];
174
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
187
+ out.push([ ...inst, ...immediates ]);
188
+ }
189
+
190
+ return out;
191
+ },
192
+
193
+ __internal_print_type: str => {
194
+ const type = getType(scope, str) - TYPES.number;
175
195
 
176
- if (!Array.isArray(inst)) inst = [ inst ];
177
- const immediates = asm.slice(1).map(x => parseInt(x));
196
+ return [
197
+ ...number(type),
198
+ [ Opcodes.call, importedFuncs.print ],
178
199
 
179
- out.push([ ...inst, ...immediates ]);
200
+ // newline
201
+ ...number(10),
202
+ [ Opcodes.call, importedFuncs.printChar ]
203
+ ];
204
+ }
180
205
  }
181
206
 
182
- return out;
207
+ const name = decl.tag.name;
208
+ // hack for inline asm
209
+ if (!funcs[name]) return todo('tagged template expressions not implemented');
210
+
211
+ const str = decl.quasi.quasis[0].value.raw;
212
+ return funcs[name](str);
213
+ }
183
214
 
184
215
  default:
185
216
  return todo(`no generation for ${decl.type}!`);
@@ -278,7 +309,7 @@ const generateReturn = (scope, decl) => {
278
309
  ];
279
310
  }
280
311
 
281
- if (!scope.returnType) scope.returnType = getNodeType(scope, decl.argument);
312
+ scope.returnType = getNodeType(scope, decl.argument);
282
313
 
283
314
  return [
284
315
  ...generate(scope, decl.argument),
@@ -295,11 +326,13 @@ const localTmp = (scope, name, type = valtypeBinary) => {
295
326
  return idx;
296
327
  };
297
328
 
298
- const performLogicOp = (scope, op, left, right) => {
329
+ const isIntOp = op => op[0] >= 0xb7 && op[0] <= 0xba;
330
+
331
+ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
299
332
  const checks = {
300
- '||': Opcodes.eqz,
301
- '&&': [ Opcodes.i32_to ]
302
- // todo: ??
333
+ '||': falsy,
334
+ '&&': truthy,
335
+ '??': nullish
303
336
  };
304
337
 
305
338
  if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
@@ -307,10 +340,37 @@ const performLogicOp = (scope, op, left, right) => {
307
340
  // generic structure for {a} OP {b}
308
341
  // -->
309
342
  // _ = {a}; if (OP_CHECK) {b} else _
343
+
344
+ // if we can, use int tmp and convert at the end to help prevent unneeded conversions
345
+ // (like if we are in an if condition - very common)
346
+ const leftIsInt = isIntOp(left[left.length - 1]);
347
+ const rightIsInt = isIntOp(right[right.length - 1]);
348
+
349
+ const canInt = leftIsInt && rightIsInt;
350
+
351
+ if (canInt) {
352
+ // remove int -> float conversions from left and right
353
+ left.pop();
354
+ right.pop();
355
+
356
+ return [
357
+ ...left,
358
+ [ Opcodes.local_tee, localTmp(scope, 'logictmpi', Valtype.i32) ],
359
+ ...checks[op](scope, [], leftType, true),
360
+ [ Opcodes.if, Valtype.i32 ],
361
+ ...right,
362
+ [ Opcodes.else ],
363
+ [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
364
+ [ Opcodes.end ],
365
+ Opcodes.i32_from
366
+ ];
367
+ }
368
+
310
369
  return [
311
370
  ...left,
312
371
  [ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
313
- ...checks[op],
372
+ ...checks[op](scope, [], leftType),
373
+ Opcodes.i32_to,
314
374
  [ Opcodes.if, valtypeBinary ],
315
375
  ...right,
316
376
  [ Opcodes.else ],
@@ -325,12 +385,13 @@ const concatStrings = (scope, left, right, global, name, assign) => {
325
385
  // todo: optimize by looking up names in arrays and using that if exists?
326
386
  // todo: optimize this if using literals/known lengths?
327
387
 
328
- scope.memory = true;
329
-
330
388
  const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
331
389
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
332
390
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
333
391
 
392
+ const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
393
+ if (aotWFA) addVarMeta(name, { wellFormed: undefined });
394
+
334
395
  if (assign) {
335
396
  const pointer = arrays.get(name ?? '$undeclared');
336
397
 
@@ -455,38 +516,37 @@ const concatStrings = (scope, left, right, global, name, assign) => {
455
516
  ];
456
517
  };
457
518
 
458
- const compareStrings = (scope, left, right, global, name) => {
519
+ const compareStrings = (scope, left, right) => {
459
520
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
460
521
  // todo: convert left and right to strings if not
461
522
  // todo: optimize by looking up names in arrays and using that if exists?
462
523
  // todo: optimize this if using literals/known lengths?
463
524
 
464
- scope.memory = true;
525
+ const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
526
+ const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
527
+ const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
528
+ const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
465
529
 
466
- const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
467
- const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
468
- const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
469
- const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
470
-
471
- // alloc/assign array
472
- const [ , pointer ] = makeArray(scope, {
473
- rawElements: new Array(0)
474
- }, global, name, true, 'i16');
530
+ const index = localTmp(scope, 'compare_index', Valtype.i32);
531
+ const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
475
532
 
476
533
  return [
477
534
  // setup left
478
535
  ...left,
479
536
  Opcodes.i32_to_u,
480
- [ Opcodes.local_set, leftPointer ],
537
+ [ Opcodes.local_tee, leftPointer ],
481
538
 
482
539
  // setup right
483
540
  ...right,
484
541
  Opcodes.i32_to_u,
485
- [ Opcodes.local_set, rightPointer ],
542
+ [ Opcodes.local_tee, rightPointer ],
486
543
 
487
- // calculate length
488
- ...number(0, Valtype.i32), // base 0 for store later
544
+ // fast path: check leftPointer == rightPointer
545
+ // use if (block) for everything after to "return" a value early
546
+ [ Opcodes.i32_ne ],
547
+ [ Opcodes.if, Valtype.i32 ],
489
548
 
549
+ // get lengths
490
550
  [ Opcodes.local_get, leftPointer ],
491
551
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
492
552
  [ Opcodes.local_tee, leftLength ],
@@ -495,154 +555,219 @@ const compareStrings = (scope, left, right, global, name) => {
495
555
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
496
556
  [ Opcodes.local_tee, rightLength ],
497
557
 
498
- [ Opcodes.i32_add ],
558
+ // fast path: check leftLength != rightLength
559
+ [ Opcodes.i32_ne ],
560
+ [ Opcodes.if, Blocktype.void ],
561
+ ...number(0, Valtype.i32),
562
+ [ Opcodes.br, 1 ],
563
+ [ Opcodes.end ],
499
564
 
500
- // store length
501
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
565
+ // no fast path for length = 0 as it would probably be slower for most of the time?
502
566
 
503
- // copy left
504
- // dst = out pointer + length size
505
- ...number(pointer + ValtypeSize.i32, Valtype.i32),
567
+ // setup index end as length * sizeof i16 (2)
568
+ // we do this instead of having to do mul/div each iter for perf™
569
+ [ Opcodes.local_get, leftLength ],
570
+ ...number(ValtypeSize.i16, Valtype.i32),
571
+ [ Opcodes.i32_mul ],
572
+ [ Opcodes.local_set, indexEnd ],
506
573
 
507
- // src = left pointer + length size
574
+ // iterate over each char and check if eq
575
+ [ Opcodes.loop, Blocktype.void ],
576
+
577
+ // fetch left
578
+ [ Opcodes.local_get, index ],
508
579
  [ Opcodes.local_get, leftPointer ],
509
- ...number(ValtypeSize.i32, Valtype.i32),
510
580
  [ Opcodes.i32_add ],
581
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
511
582
 
512
- // size = PageSize - length size. we do not need to calculate length as init value
513
- ...number(pageSize - ValtypeSize.i32, Valtype.i32),
514
- [ ...Opcodes.memory_copy, 0x00, 0x00 ],
583
+ // fetch right
584
+ [ Opcodes.local_get, index ],
585
+ [ Opcodes.local_get, rightPointer ],
586
+ [ Opcodes.i32_add ],
587
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
515
588
 
516
- // copy right
517
- // dst = out pointer + length size + left length * i16 size
518
- ...number(pointer + ValtypeSize.i32, Valtype.i32),
589
+ // not equal, "return" false
590
+ [ Opcodes.i32_ne ],
591
+ [ Opcodes.if, Blocktype.void ],
592
+ ...number(0, Valtype.i32),
593
+ [ Opcodes.br, 2 ],
594
+ [ Opcodes.end ],
519
595
 
520
- [ Opcodes.local_get, leftLength ],
596
+ // index += sizeof i16 (2)
597
+ [ Opcodes.local_get, index ],
521
598
  ...number(ValtypeSize.i16, Valtype.i32),
522
- [ Opcodes.i32_mul ],
523
599
  [ Opcodes.i32_add ],
600
+ [ Opcodes.local_tee, index ],
524
601
 
525
- // src = right pointer + length size
526
- [ Opcodes.local_get, rightPointer ],
527
- ...number(ValtypeSize.i32, Valtype.i32),
528
- [ Opcodes.i32_add ],
602
+ // if index != index end (length * sizeof 16), loop
603
+ [ Opcodes.local_get, indexEnd ],
604
+ [ Opcodes.i32_ne ],
605
+ [ Opcodes.br_if, 0 ],
606
+ [ Opcodes.end ],
529
607
 
530
- // size = right length * i16 size
531
- [ Opcodes.local_get, rightLength ],
532
- ...number(ValtypeSize.i16, Valtype.i32),
533
- [ Opcodes.i32_mul ],
608
+ // no failed checks, so true!
609
+ ...number(1, Valtype.i32),
534
610
 
535
- [ ...Opcodes.memory_copy, 0x00, 0x00 ],
611
+ // pointers match, so true
612
+ [ Opcodes.else ],
613
+ ...number(1, Valtype.i32),
614
+ [ Opcodes.end ],
536
615
 
537
- // return new string (page)
538
- ...number(pointer)
616
+ // convert i32 result to valtype
617
+ // do not do as automatically added by binary exp gen for equality ops
618
+ // Opcodes.i32_from_u
539
619
  ];
540
620
  };
541
621
 
542
- const falsy = (scope, wasm, type) => {
622
+ const truthy = (scope, wasm, type, int = false) => {
543
623
  // arrays are always truthy
544
624
  if (type === TYPES._array) return [
545
625
  ...wasm,
546
626
  [ Opcodes.drop ],
547
- number(0)
627
+ ...number(1, int ? Valtype.i32 : valtypeBinary)
548
628
  ];
549
629
 
550
630
  if (type === TYPES.string) {
551
- // if "" (length = 0)
631
+ // if not "" (length = 0)
552
632
  return [
553
633
  // pointer
554
634
  ...wasm,
635
+ Opcodes.i32_to_u,
555
636
 
556
637
  // get length
557
638
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
558
639
 
559
- // if length == 0
560
- [ Opcodes.i32_eqz ],
561
- Opcodes.i32_from_u
562
- ]
640
+ // if length != 0
641
+ /* [ Opcodes.i32_eqz ],
642
+ [ Opcodes.i32_eqz ], */
643
+ ...(int ? [] : [ Opcodes.i32_from_u ])
644
+ ];
563
645
  }
564
646
 
565
- // if = 0
647
+ // if != 0
566
648
  return [
567
649
  ...wasm,
568
650
 
569
- ...Opcodes.eqz,
570
- Opcodes.i32_from_u
651
+ /* Opcodes.eqz,
652
+ [ Opcodes.i32_eqz ],
653
+ Opcodes.i32_from */
571
654
  ];
572
655
  };
573
656
 
574
- const truthy = (scope, wasm, type) => {
657
+ const falsy = (scope, wasm, type, int = false) => {
575
658
  // arrays are always truthy
576
659
  if (type === TYPES._array) return [
577
660
  ...wasm,
578
661
  [ Opcodes.drop ],
579
- number(1)
662
+ ...number(0, int ? Valtype.i32 : valtypeBinary)
580
663
  ];
581
664
 
582
665
  if (type === TYPES.string) {
583
- // if not "" (length = 0)
666
+ // if "" (length = 0)
584
667
  return [
585
668
  // pointer
586
669
  ...wasm,
670
+ Opcodes.i32_to_u,
587
671
 
588
672
  // get length
589
673
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
590
674
 
591
- // if length != 0
592
- /* [ Opcodes.i32_eqz ],
593
- [ Opcodes.i32_eqz ], */
594
- Opcodes.i32_from_u
675
+ // if length == 0
676
+ [ Opcodes.i32_eqz ],
677
+ ...(int ? [] : [ Opcodes.i32_from_u ])
595
678
  ]
596
679
  }
597
680
 
598
- // if != 0
681
+ // if = 0
599
682
  return [
600
683
  ...wasm,
601
684
 
602
- /* Opcodes.eqz,
603
- [ Opcodes.i32_eqz ],
604
- Opcodes.i32_from */
685
+ ...(int ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz, Opcodes.i32_from_u ])
686
+ ];
687
+ };
688
+
689
+ const nullish = (scope, wasm, type, int = false) => {
690
+ // undefined
691
+ if (type === TYPES.undefined) return [
692
+ ...wasm,
693
+ [ Opcodes.drop ],
694
+ ...number(1, int ? Valtype.i32 : valtypeBinary)
695
+ ];
696
+
697
+ // null (if object and = "0")
698
+ if (type === TYPES.object) return [
699
+ ...wasm,
700
+ ...(int ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz, Opcodes.i32_from_u ])
701
+ ];
702
+
703
+ // not
704
+ return [
705
+ ...wasm,
706
+ [ Opcodes.drop ],
707
+ ...number(0, int ? Valtype.i32 : valtypeBinary)
605
708
  ];
606
709
  };
607
710
 
608
711
  const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
609
712
  if (op === '||' || op === '&&' || op === '??') {
610
- return performLogicOp(scope, op, left, right);
713
+ return performLogicOp(scope, op, left, right, leftType, rightType);
611
714
  }
612
715
 
613
716
  if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
614
717
 
615
- // if strict (in)equal and known types mismatch, return false (===)/true (!==)
616
- if ((op === '===' || op === '!==') && leftType && rightType && leftType !== rightType) {
718
+ const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
719
+
720
+ if (leftType && rightType && (
721
+ // if strict (in)equal and known types mismatch, return false (===)/true (!==)
722
+ ((op === '===' || op === '!==') && leftType !== rightType) ||
723
+
724
+ // if equality op and an operand is undefined, return false
725
+ (eqOp && leftType === TYPES.undefined ^ rightType === TYPES.undefined)
726
+ )) {
727
+ // undefined == null
728
+ if (((leftType === TYPES.undefined && rightType === TYPES.object) || (leftType === TYPES.object && rightType === TYPES.undefined)) && (op === '==' || op === '!=')) return [
729
+ ...(leftType === TYPES.object ? left : right),
730
+ ...Opcodes.eqz,
731
+ ...(op === '!=' ? [ [ Opcodes.i32_eqz ] ] : [])
732
+ ];
733
+
617
734
  return [
618
735
  ...left,
619
- ...right,
620
-
621
- // drop values
622
736
  [ Opcodes.drop ],
737
+
738
+ ...right,
623
739
  [ Opcodes.drop ],
624
740
 
625
- // return false (===)/true (!==)
626
- ...number(op === '===' ? 0 : 1, Valtype.i32)
741
+ // return true (!=/!==) or false (else)
742
+ ...number(op === '!=' || op === '!==' ? 1 : 0, Valtype.i32)
627
743
  ];
628
744
  }
629
745
 
746
+ // todo: niche null hell with 0
747
+
630
748
  if (leftType === TYPES.string || rightType === TYPES.string) {
631
749
  if (op === '+') {
632
750
  // string concat (a + b)
633
751
  return concatStrings(scope, left, right, _global, _name, assign);
634
752
  }
635
753
 
636
- // any other math op, NaN
637
- if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
754
+ // not an equality op, NaN
755
+ if (!eqOp) return number(NaN);
638
756
 
639
757
  // else leave bool ops
640
758
  // todo: convert string to number if string and number/bool
641
759
  // todo: string (>|>=|<|<=) string
642
760
 
643
- // string equality
644
- if (op === '===') {
761
+ // string comparison
762
+ if (op === '===' || op === '==') {
763
+ return compareStrings(scope, left, right);
764
+ }
645
765
 
766
+ if (op === '!==' || op === '!=') {
767
+ return [
768
+ ...compareStrings(scope, left, right),
769
+ [ Opcodes.i32_eqz ]
770
+ ];
646
771
  }
647
772
  }
648
773
 
@@ -672,21 +797,15 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
672
797
  ];
673
798
  };
674
799
 
675
- let binaryExpDepth = 0;
676
800
  const generateBinaryExp = (scope, decl, _global, _name) => {
677
- binaryExpDepth++;
678
-
679
- const out = [
680
- ...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
681
- ];
801
+ const out = performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name);
682
802
 
683
803
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
684
804
 
685
- binaryExpDepth--;
686
805
  return out;
687
806
  };
688
807
 
689
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, memory, localNames = [], globalNames = [] }) => {
808
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
690
809
  const existing = funcs.find(x => x.name === name);
691
810
  if (existing) return existing;
692
811
 
@@ -722,7 +841,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
722
841
  returns,
723
842
  returnType: TYPES[returnType ?? 'number'],
724
843
  wasm,
725
- memory,
726
844
  internal: true,
727
845
  index: currentFuncIndex++
728
846
  };
@@ -741,7 +859,7 @@ const includeBuiltin = (scope, builtin) => {
741
859
  };
742
860
 
743
861
  const generateLogicExp = (scope, decl) => {
744
- return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
862
+ return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
745
863
  };
746
864
 
747
865
  const TYPES = {
@@ -755,7 +873,8 @@ const TYPES = {
755
873
  bigint: 0xffffffffffff7,
756
874
 
757
875
  // these are not "typeof" types but tracked internally
758
- _array: 0xffffffffffff8
876
+ _array: 0xfffffffffff0f,
877
+ _regexp: 0xfffffffffff1f
759
878
  };
760
879
 
761
880
  const TYPE_NAMES = {
@@ -789,6 +908,9 @@ const getType = (scope, _name) => {
789
908
 
790
909
  const getNodeType = (scope, node) => {
791
910
  if (node.type === 'Literal') {
911
+ if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
912
+ if (node.regex) return TYPES._regexp;
913
+
792
914
  return TYPES[typeof node.value];
793
915
  }
794
916
 
@@ -822,6 +944,11 @@ const getNodeType = (scope, node) => {
822
944
 
823
945
  // literal.func()
824
946
  if (!name && node.callee.type === 'MemberExpression') {
947
+ if (node.callee.object.regex) {
948
+ const funcName = node.callee.property.name;
949
+ return Rhemyn[funcName] ? TYPES.boolean : TYPES.undefined;
950
+ }
951
+
825
952
  const baseType = getNodeType(scope, node.callee.object);
826
953
 
827
954
  const func = node.callee.property.name;
@@ -865,6 +992,11 @@ const getNodeType = (scope, node) => {
865
992
  const generateLiteral = (scope, decl, global, name) => {
866
993
  if (decl.value === null) return number(NULL);
867
994
 
995
+ if (decl.regex) {
996
+ scope.regex[name] = decl.regex;
997
+ return number(1);
998
+ }
999
+
868
1000
  switch (typeof decl.value) {
869
1001
  case 'number':
870
1002
  return number(decl.value);
@@ -886,12 +1018,38 @@ const generateLiteral = (scope, decl, global, name) => {
886
1018
  case 'bigint': return number(TYPES.bigint);
887
1019
  }
888
1020
 
1021
+ const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
1022
+ let wellFormed = aotWFA ? true : undefined;
1023
+
889
1024
  const str = decl.value;
890
1025
  const rawElements = new Array(str.length);
1026
+ let j = 0;
891
1027
  for (let i = 0; i < str.length; i++) {
892
1028
  rawElements[i] = str.charCodeAt(i);
1029
+
1030
+ if (wellFormed) {
1031
+ // check if surrogate
1032
+ if ((str.charCodeAt(j) & 0xF800) === 0xD800) {
1033
+ // unpaired trailing surrogate
1034
+ if (str.charCodeAt(j) >= 0xDC00) {
1035
+ wellFormed = false;
1036
+ }
1037
+
1038
+ // unpaired leading surrogate
1039
+ // if (++j >= str.length || (str.charCodeAt(j) & 0xFC00) != 0xDC00) {
1040
+ if ((str.charCodeAt(++j) & 0xFC00) != 0xDC00) {
1041
+ wellFormed = false;
1042
+ }
1043
+ }
1044
+
1045
+ j++;
1046
+ }
893
1047
  }
894
1048
 
1049
+ // console.log(wellFormed, str);
1050
+
1051
+ if (aotWFA) addVarMeta(name, { wellFormed });
1052
+
895
1053
  return makeArray(scope, {
896
1054
  rawElements
897
1055
  }, global, name, false, 'i16')[0];
@@ -904,7 +1062,8 @@ const generateLiteral = (scope, decl, global, name) => {
904
1062
  const countLeftover = wasm => {
905
1063
  let count = 0, depth = 0;
906
1064
 
907
- for (const inst of wasm) {
1065
+ for (let i = 0; i < wasm.length; i++) {
1066
+ const inst = wasm[i];
908
1067
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
909
1068
  if (inst[0] === Opcodes.if) count--;
910
1069
  if (inst[1] !== Blocktype.void) count++;
@@ -925,6 +1084,8 @@ const countLeftover = wasm => {
925
1084
  } else count--;
926
1085
  if (func) count += func.returns.length;
927
1086
  } else count--;
1087
+
1088
+ // console.log(count, decompile([ inst ]).slice(0, -1));
928
1089
  }
929
1090
 
930
1091
  return count;
@@ -1008,7 +1169,7 @@ const generateCall = (scope, decl, _global, _name) => {
1008
1169
  }
1009
1170
 
1010
1171
  let out = [];
1011
- let protoFunc, protoName, baseType, baseName = '$undeclared';
1172
+ let protoFunc, protoName, baseType, baseName;
1012
1173
  // ident.func()
1013
1174
  if (name && name.startsWith('__')) {
1014
1175
  const spl = name.slice(2).split('_');
@@ -1023,6 +1184,25 @@ const generateCall = (scope, decl, _global, _name) => {
1023
1184
 
1024
1185
  // literal.func()
1025
1186
  if (!name && decl.callee.type === 'MemberExpression') {
1187
+ // megahack for /regex/.func()
1188
+ if (decl.callee.object.regex) {
1189
+ const funcName = decl.callee.property.name;
1190
+ const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1191
+
1192
+ funcIndex[func.name] = func.index;
1193
+ funcs.push(func);
1194
+
1195
+ return [
1196
+ // make string arg
1197
+ ...generate(scope, decl.arguments[0]),
1198
+
1199
+ // call regex func
1200
+ Opcodes.i32_to_u,
1201
+ [ Opcodes.call, func.index ],
1202
+ Opcodes.i32_from
1203
+ ];
1204
+ }
1205
+
1026
1206
  baseType = getNodeType(scope, decl.callee.object);
1027
1207
 
1028
1208
  const func = decl.callee.property.name;
@@ -1031,11 +1211,36 @@ const generateCall = (scope, decl, _global, _name) => {
1031
1211
 
1032
1212
  out = generate(scope, decl.callee.object);
1033
1213
  out.push([ Opcodes.drop ]);
1214
+
1215
+ baseName = [...arrays.keys()].pop();
1034
1216
  }
1035
1217
 
1036
- if (protoFunc) {
1037
- scope.memory = true;
1218
+ if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
1219
+ const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
1220
+
1221
+ funcIndex[func.name] = func.index;
1222
+ funcs.push(func);
1223
+
1224
+ const pointer = arrays.get(baseName);
1225
+ const [ local, isGlobal ] = lookupName(scope, baseName);
1226
+
1227
+ return [
1228
+ ...out,
1229
+
1230
+ ...(pointer == null ? [
1231
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1232
+ Opcodes.i32_to_u,
1233
+ ] : [
1234
+ ...number(pointer, Valtype.i32)
1235
+ ]),
1038
1236
 
1237
+ // call regex func
1238
+ [ Opcodes.call, func.index ],
1239
+ Opcodes.i32_from
1240
+ ];
1241
+ }
1242
+
1243
+ if (protoFunc) {
1039
1244
  let pointer = arrays.get(baseName);
1040
1245
 
1041
1246
  if (pointer == null) {
@@ -1043,7 +1248,7 @@ const generateCall = (scope, decl, _global, _name) => {
1043
1248
  if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
1044
1249
 
1045
1250
  // register array
1046
- const [ , pointer ] = makeArray(scope, {
1251
+ 0, [ , pointer ] = makeArray(scope, {
1047
1252
  rawElements: new Array(0)
1048
1253
  }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
1049
1254
 
@@ -1063,28 +1268,39 @@ const generateCall = (scope, decl, _global, _name) => {
1063
1268
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) return arrayUtil.getLength(pointer)
1064
1269
 
1065
1270
  let protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp`, protoFunc.local) : -1;
1271
+ let protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp2`, protoFunc.local2) : -1;
1066
1272
 
1067
1273
  // use local for cached i32 length as commonly used
1068
1274
  let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1069
1275
 
1276
+ let lengthI32CacheUsed = false;
1277
+
1278
+ const protoOut = protoFunc(pointer, {
1279
+ getCachedI32: () => {
1280
+ lengthI32CacheUsed = true;
1281
+ return [ [ Opcodes.local_get, lengthLocal ] ]
1282
+ },
1283
+ setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
1284
+ get: () => arrayUtil.getLength(pointer),
1285
+ getI32: () => arrayUtil.getLengthI32(pointer),
1286
+ set: value => arrayUtil.setLength(pointer, value),
1287
+ setI32: value => arrayUtil.setLengthI32(pointer, value)
1288
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1289
+ return makeArray(scope, {
1290
+ rawElements: new Array(length)
1291
+ }, _global, _name, true, itemType);
1292
+ }, varMetadata.get(baseName));
1293
+
1070
1294
  return [
1071
1295
  ...out,
1072
1296
 
1073
- ...arrayUtil.getLengthI32(pointer),
1074
- [ Opcodes.local_set, lengthLocal ],
1297
+ ...(!lengthI32CacheUsed ? [] : [
1298
+ ...arrayUtil.getLengthI32(pointer),
1299
+ [ Opcodes.local_set, lengthLocal ],
1300
+ ]),
1075
1301
 
1076
1302
  [ Opcodes.block, valtypeBinary ],
1077
- ...protoFunc(pointer, {
1078
- cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
1079
- get: arrayUtil.getLength(pointer),
1080
- getI32: arrayUtil.getLengthI32(pointer),
1081
- set: value => arrayUtil.setLength(pointer, value),
1082
- setI32: value => arrayUtil.setLengthI32(pointer, value)
1083
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
1084
- return makeArray(scope, {
1085
- rawElements: new Array(length)
1086
- }, _global, _name, true, itemType);
1087
- }),
1303
+ ...protoOut,
1088
1304
  [ Opcodes.end ]
1089
1305
  ];
1090
1306
  }
@@ -1141,11 +1357,10 @@ const generateCall = (scope, decl, _global, _name) => {
1141
1357
  args = args.slice(0, func.params.length);
1142
1358
  }
1143
1359
 
1144
- if (func && func.memory) scope.memory = true;
1145
1360
  if (func && func.throws) scope.throws = true;
1146
1361
 
1147
1362
  for (const arg of args) {
1148
- out.push(...generate(scope, arg));
1363
+ out = out.concat(generate(scope, arg));
1149
1364
  }
1150
1365
 
1151
1366
  out.push([ Opcodes.call, idx ]);
@@ -1156,8 +1371,8 @@ const generateCall = (scope, decl, _global, _name) => {
1156
1371
  const generateNew = (scope, decl, _global, _name) => {
1157
1372
  // hack: basically treat this as a normal call for builtins for now
1158
1373
  const name = mapName(decl.callee.name);
1159
- if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1160
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1374
+ if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1375
+ if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1161
1376
 
1162
1377
  return generateCall(scope, decl, _global, _name);
1163
1378
  };
@@ -1174,12 +1389,12 @@ const unhackName = name => {
1174
1389
  };
1175
1390
 
1176
1391
  const generateVar = (scope, decl) => {
1177
- const out = [];
1392
+ let out = [];
1178
1393
 
1179
1394
  const topLevel = scope.name === 'main';
1180
1395
 
1181
1396
  // global variable if in top scope (main) and var ..., or if wanted
1182
- const global = decl.kind === 'var';
1397
+ const global = topLevel || decl._bare; // decl.kind === 'var';
1183
1398
  const target = global ? globals : scope.locals;
1184
1399
 
1185
1400
  for (const x of decl.declarations) {
@@ -1216,7 +1431,7 @@ const generateVar = (scope, decl) => {
1216
1431
 
1217
1432
  // x.init ??= DEFAULT_VALUE;
1218
1433
  if (x.init) {
1219
- out.push(...generate(scope, x.init, global, name));
1434
+ out = out.concat(generate(scope, x.init, global, name));
1220
1435
 
1221
1436
  // if our value is the result of a function, infer the type from that func's return value
1222
1437
  if (out[out.length - 1][0] === Opcodes.call) {
@@ -1265,8 +1480,6 @@ const generateAssign = (scope, decl) => {
1265
1480
  const name = decl.left.object.name;
1266
1481
  const pointer = arrays.get(name);
1267
1482
 
1268
- scope.memory = true;
1269
-
1270
1483
  const aotPointer = pointer != null;
1271
1484
 
1272
1485
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
@@ -1287,13 +1500,60 @@ const generateAssign = (scope, decl) => {
1287
1500
  ];
1288
1501
  }
1289
1502
 
1503
+ const op = decl.operator.slice(0, -1) || '=';
1504
+
1505
+ // arr[i] | str[i]
1506
+ if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1507
+ const name = decl.left.object.name;
1508
+ const pointer = arrays.get(name);
1509
+
1510
+ const aotPointer = pointer != null;
1511
+
1512
+ const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1513
+ const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1514
+
1515
+ const parentType = getNodeType(scope, decl.left.object);
1516
+
1517
+ return [
1518
+ ...(aotPointer ? [] : [
1519
+ ...generate(scope, decl.left.object),
1520
+ Opcodes.i32_to_u
1521
+ ]),
1522
+
1523
+ // get index as valtype
1524
+ ...generate(scope, decl.left.property),
1525
+
1526
+ // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
1527
+ Opcodes.i32_to_u,
1528
+ ...number(ValtypeSize[valtype], Valtype.i32),
1529
+ [ Opcodes.i32_mul ],
1530
+ ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
1531
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1532
+
1533
+ ...(op === '=' ? generate(scope, decl.right, false, name) : performOp(scope, op, [
1534
+ [ Opcodes.local_get, pointerTmp ],
1535
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1536
+ ], generate(scope, decl.right), parentType === TYPES._array ? TYPES.number : TYPES.string, getNodeType(scope, decl.right), false, name, true)),
1537
+ [ Opcodes.local_tee, newValueTmp ],
1538
+
1539
+ ...(parentType === TYPES._array ? [
1540
+ [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1541
+ ] : [
1542
+ Opcodes.i32_to_u,
1543
+ [ StoreOps.i16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1544
+ ]),
1545
+
1546
+ [ Opcodes.local_get, newValueTmp ]
1547
+ ];
1548
+ }
1549
+
1290
1550
  const [ local, isGlobal ] = lookupName(scope, name);
1291
1551
 
1292
1552
  if (local === undefined) {
1293
- // todo: this should be a devtools/repl/??? only thing
1553
+ // todo: this should be a sloppy mode only thing
1294
1554
 
1295
1555
  // only allow = for this
1296
- if (decl.operator !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1556
+ if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1297
1557
 
1298
1558
  if (builtinVars[name]) {
1299
1559
  // just return rhs (eg `NaN = 2`)
@@ -1302,13 +1562,15 @@ const generateAssign = (scope, decl) => {
1302
1562
 
1303
1563
  // set global and return (eg a = 2)
1304
1564
  return [
1305
- ...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
1565
+ ...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
1306
1566
  [ Opcodes.global_get, globals[name].idx ]
1307
1567
  ];
1308
1568
  }
1309
1569
 
1310
- if (decl.operator === '=') {
1311
- typeStates[name] = getNodeType(scope, decl.right);
1570
+ typeStates[name] = getNodeType(scope, decl.right);
1571
+
1572
+ if (op === '=') {
1573
+ // typeStates[name] = getNodeType(scope, decl.right);
1312
1574
 
1313
1575
  return [
1314
1576
  ...generate(scope, decl.right, isGlobal, name),
@@ -1317,8 +1579,26 @@ const generateAssign = (scope, decl) => {
1317
1579
  ];
1318
1580
  }
1319
1581
 
1582
+ if (op === '||' || op === '&&' || op === '??') {
1583
+ // todo: is this needed?
1584
+ // for logical assignment ops, it is not left @= right ~= left = left @ right
1585
+ // instead, left @ (left = right)
1586
+ // eg, x &&= y ~= x && (x = y)
1587
+
1588
+ return [
1589
+ ...performOp(scope, op, [
1590
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1591
+ ], [
1592
+ ...generate(scope, decl.right),
1593
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1594
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1595
+ ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1596
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1597
+ ];
1598
+ }
1599
+
1320
1600
  return [
1321
- ...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),
1601
+ ...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),
1322
1602
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1323
1603
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1324
1604
  ];
@@ -1345,13 +1625,14 @@ const generateUnary = (scope, decl) => {
1345
1625
 
1346
1626
  case '!':
1347
1627
  // !=
1348
- return falsy(scope, generate(scope, decl.argument));
1628
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
1349
1629
 
1350
1630
  case '~':
1631
+ // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
1351
1632
  return [
1352
1633
  ...generate(scope, decl.argument),
1353
1634
  Opcodes.i32_to,
1354
- [ Opcodes.i32_const, signedLEB128(-1) ],
1635
+ [ Opcodes.i32_const, ...signedLEB128(-1) ],
1355
1636
  [ Opcodes.i32_xor ],
1356
1637
  Opcodes.i32_from
1357
1638
  ];
@@ -1432,7 +1713,7 @@ const generateUpdate = (scope, decl) => {
1432
1713
  };
1433
1714
 
1434
1715
  const generateIf = (scope, decl) => {
1435
- const out = truthy(scope, generate(scope, decl.test), decl.test);
1716
+ const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test));
1436
1717
 
1437
1718
  out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1438
1719
  depth.push('if');
@@ -1522,9 +1803,116 @@ const generateWhile = (scope, decl) => {
1522
1803
  return out;
1523
1804
  };
1524
1805
 
1806
+ const generateForOf = (scope, decl) => {
1807
+ const out = [];
1808
+
1809
+ const rightType = getNodeType(scope, decl.right);
1810
+ const valtypeSize = rightType === TYPES._array ? ValtypeSize[valtype] : ValtypeSize.i16; // presume array (:()
1811
+
1812
+ // todo: for of inside for of might fuck up?
1813
+ const pointer = localTmp(scope, 'forof_base_pointer', Valtype.i32);
1814
+ const length = localTmp(scope, 'forof_length', Valtype.i32);
1815
+ const counter = localTmp(scope, 'forof_counter', Valtype.i32);
1816
+
1817
+ out.push(
1818
+ // set pointer as right
1819
+ ...generate(scope, decl.right),
1820
+ Opcodes.i32_to_u,
1821
+ [ Opcodes.local_set, pointer ],
1822
+
1823
+ // get length
1824
+ [ Opcodes.local_get, pointer ],
1825
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
1826
+ [ Opcodes.local_set, length ]
1827
+ );
1828
+
1829
+ out.push([ Opcodes.loop, Blocktype.void ]);
1830
+ depth.push('forof');
1831
+
1832
+ // setup local for left
1833
+ generate(scope, decl.left);
1834
+
1835
+ const leftName = decl.left.declarations[0].id.name;
1836
+
1837
+ // set type for local
1838
+ typeStates[leftName] = rightType === TYPES._array ? TYPES.number : TYPES.string;
1839
+
1840
+ const [ local, isGlobal ] = lookupName(scope, leftName);
1841
+
1842
+ if (rightType === TYPES._array) { // array
1843
+ out.push(
1844
+ [ Opcodes.local_get, pointer ],
1845
+ [ Opcodes.load, Math.log2(valtypeSize) - 1, ...unsignedLEB128(ValtypeSize.i32) ]
1846
+ );
1847
+ } else { // string
1848
+ const [ newOut, newPointer ] = makeArray(scope, {
1849
+ rawElements: new Array(1)
1850
+ }, isGlobal, leftName, true, 'i16');
1851
+
1852
+ out.push(
1853
+ // setup new/out array
1854
+ ...newOut,
1855
+ [ Opcodes.drop ],
1856
+
1857
+ ...number(0, Valtype.i32), // base 0 for store after
1858
+
1859
+ // load current string ind {arg}
1860
+ [ Opcodes.local_get, pointer ],
1861
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
1862
+
1863
+ // store to new string ind 0
1864
+ [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
1865
+
1866
+ // return new string (page)
1867
+ ...number(newPointer)
1868
+ );
1869
+ }
1870
+
1871
+ // set left value
1872
+ out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ]);
1873
+
1874
+ out.push(
1875
+ [ Opcodes.block, Blocktype.void ],
1876
+ [ Opcodes.block, Blocktype.void ]
1877
+ );
1878
+ depth.push('block');
1879
+ depth.push('block');
1880
+
1881
+ out.push(
1882
+ ...generate(scope, decl.body),
1883
+ [ Opcodes.end ]
1884
+ );
1885
+ depth.pop();
1886
+
1887
+ out.push(
1888
+ // increment iter pointer by valtype size
1889
+ [ Opcodes.local_get, pointer ],
1890
+ ...number(valtypeSize, Valtype.i32),
1891
+ [ Opcodes.i32_add ],
1892
+ [ Opcodes.local_set, pointer ],
1893
+
1894
+ // increment counter by 1
1895
+ [ Opcodes.local_get, counter ],
1896
+ ...number(1, Valtype.i32),
1897
+ [ Opcodes.i32_add ],
1898
+ [ Opcodes.local_tee, counter ],
1899
+
1900
+ // loop if counter != length
1901
+ [ Opcodes.local_get, length ],
1902
+ [ Opcodes.i32_ne ],
1903
+ [ Opcodes.br_if, 1 ],
1904
+
1905
+ [ Opcodes.end ], [ Opcodes.end ]
1906
+ );
1907
+ depth.pop();
1908
+ depth.pop();
1909
+
1910
+ return out;
1911
+ };
1912
+
1525
1913
  const getNearestLoop = () => {
1526
1914
  for (let i = depth.length - 1; i >= 0; i--) {
1527
- if (depth[i] === 'while' || depth[i] === 'for') return i;
1915
+ if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
1528
1916
  }
1529
1917
 
1530
1918
  return -1;
@@ -1608,13 +1996,22 @@ const generateAssignPat = (scope, decl) => {
1608
1996
  };
1609
1997
 
1610
1998
  let pages = new Map();
1611
- const allocPage = reason => {
1612
- if (pages.has(reason)) return pages.get(reason);
1999
+ const allocPage = (reason, type) => {
2000
+ if (pages.has(reason)) return pages.get(reason).ind;
1613
2001
 
1614
- let ind = pages.size;
1615
- pages.set(reason, ind);
2002
+ const ind = pages.size;
2003
+ pages.set(reason, { ind, type });
1616
2004
 
1617
- if (codeLog) log('codegen', `allocated new page of memory (${ind}) | ${reason}`);
2005
+ if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2006
+
2007
+ return ind;
2008
+ };
2009
+
2010
+ const freePage = reason => {
2011
+ const { ind } = pages.get(reason);
2012
+ pages.delete(reason);
2013
+
2014
+ if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
1618
2015
 
1619
2016
  return ind;
1620
2017
  };
@@ -1628,7 +2025,7 @@ const itemTypeToValtype = {
1628
2025
  i16: 'i32'
1629
2026
  };
1630
2027
 
1631
- const storeOps = {
2028
+ const StoreOps = {
1632
2029
  i32: Opcodes.i32_store,
1633
2030
  i64: Opcodes.i64_store,
1634
2031
  f64: Opcodes.f64_store,
@@ -1637,13 +2034,31 @@ const storeOps = {
1637
2034
  i16: Opcodes.i32_store16
1638
2035
  };
1639
2036
 
2037
+ let data = [];
2038
+
2039
+ const compileBytes = (val, itemType, signed = true) => {
2040
+ switch (itemType) {
2041
+ case 'i8': return [ val % 256 ];
2042
+ case 'i16': return [ val % 256, Math.floor(val / 256) ];
2043
+
2044
+ case 'i32':
2045
+ case 'i64':
2046
+ return enforceFourBytes(signedLEB128(val));
2047
+
2048
+ case 'f64': return ieee754_binary64(val);
2049
+ }
2050
+ };
2051
+
1640
2052
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
1641
2053
  const out = [];
1642
2054
 
2055
+ let firstAssign = false;
1643
2056
  if (!arrays.has(name) || name === '$undeclared') {
2057
+ firstAssign = true;
2058
+
1644
2059
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1645
2060
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1646
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`) * pageSize);
2061
+ arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
1647
2062
  }
1648
2063
 
1649
2064
  const pointer = arrays.get(name);
@@ -1651,8 +2066,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1651
2066
  const useRawElements = !!decl.rawElements;
1652
2067
  const elements = useRawElements ? decl.rawElements : decl.elements;
1653
2068
 
2069
+ const valtype = itemTypeToValtype[itemType];
1654
2070
  const length = elements.length;
1655
2071
 
2072
+ if (firstAssign && useRawElements) {
2073
+ let bytes = compileBytes(length, 'i32');
2074
+
2075
+ if (!initEmpty) for (let i = 0; i < length; i++) {
2076
+ if (elements[i] == null) continue;
2077
+
2078
+ bytes.push(...compileBytes(elements[i], itemType));
2079
+ }
2080
+
2081
+ data.push({
2082
+ offset: pointer,
2083
+ bytes
2084
+ });
2085
+
2086
+ // local value as pointer
2087
+ out.push(...number(pointer));
2088
+
2089
+ return [ out, pointer ];
2090
+ }
2091
+
1656
2092
  // store length as 0th array
1657
2093
  out.push(
1658
2094
  ...number(0, Valtype.i32),
@@ -1660,8 +2096,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1660
2096
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
1661
2097
  );
1662
2098
 
1663
- const storeOp = storeOps[itemType];
1664
- const valtype = itemTypeToValtype[itemType];
2099
+ const storeOp = StoreOps[itemType];
1665
2100
 
1666
2101
  if (!initEmpty) for (let i = 0; i < length; i++) {
1667
2102
  if (elements[i] == null) continue;
@@ -1676,8 +2111,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1676
2111
  // local value as pointer
1677
2112
  out.push(...number(pointer));
1678
2113
 
1679
- scope.memory = true;
1680
-
1681
2114
  return [ out, pointer ];
1682
2115
  };
1683
2116
 
@@ -1686,6 +2119,17 @@ const generateArray = (scope, decl, global = false, name = '$undeclared', initEm
1686
2119
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
1687
2120
  };
1688
2121
 
2122
+ let varMetadata = new Map();
2123
+ const addVarMeta = (_name, obj) => {
2124
+ const name = _name ?? '$undeclared';
2125
+ if (!varMetadata.has(name)) varMetadata.set(name, {});
2126
+
2127
+ const meta = varMetadata.get(name);
2128
+ for (const k in obj) {
2129
+ meta[k] = obj[k];
2130
+ }
2131
+ };
2132
+
1689
2133
  export const generateMember = (scope, decl, _global, _name) => {
1690
2134
  const type = getNodeType(scope, decl.object);
1691
2135
 
@@ -1696,8 +2140,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1696
2140
  const name = decl.object.name;
1697
2141
  const pointer = arrays.get(name);
1698
2142
 
1699
- scope.memory = true;
1700
-
1701
2143
  const aotPointer = pointer != null;
1702
2144
 
1703
2145
  return [
@@ -1717,8 +2159,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1717
2159
  const name = decl.object.name;
1718
2160
  const pointer = arrays.get(name);
1719
2161
 
1720
- scope.memory = true;
1721
-
1722
2162
  const aotPointer = pointer != null;
1723
2163
 
1724
2164
  if (type === TYPES._array) {
@@ -1828,7 +2268,7 @@ const generateFunc = (scope, decl) => {
1828
2268
  locals: {},
1829
2269
  localInd: 0,
1830
2270
  returns: [ valtypeBinary ],
1831
- memory: false,
2271
+ returnType: null,
1832
2272
  throws: false,
1833
2273
  name
1834
2274
  };
@@ -1854,7 +2294,6 @@ const generateFunc = (scope, decl) => {
1854
2294
  returns: innerScope.returns,
1855
2295
  returnType: innerScope.returnType,
1856
2296
  locals: innerScope.locals,
1857
- memory: innerScope.memory,
1858
2297
  throws: innerScope.throws,
1859
2298
  index: currentFuncIndex++
1860
2299
  };
@@ -1869,6 +2308,8 @@ const generateFunc = (scope, decl) => {
1869
2308
 
1870
2309
  if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
1871
2310
  wasm.push(...number(0), [ Opcodes.return ]);
2311
+
2312
+ if (func.returnType === null) func.returnType = TYPES.undefined;
1872
2313
  }
1873
2314
 
1874
2315
  // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
@@ -1879,9 +2320,7 @@ const generateFunc = (scope, decl) => {
1879
2320
  if (local.type === Valtype.v128) {
1880
2321
  vecParams++;
1881
2322
 
1882
- /* func.memory = true; // mark func as using memory
1883
-
1884
- wasm.unshift( // add v128 load for param
2323
+ /* wasm.unshift( // add v128 load for param
1885
2324
  [ Opcodes.i32_const, 0 ],
1886
2325
  [ ...Opcodes.v128_load, 0, i * 16 ],
1887
2326
  [ Opcodes.local_set, local.idx ]
@@ -1992,10 +2431,10 @@ const generateFunc = (scope, decl) => {
1992
2431
  };
1993
2432
 
1994
2433
  const generateCode = (scope, decl) => {
1995
- const out = [];
2434
+ let out = [];
1996
2435
 
1997
2436
  for (const x of decl.body) {
1998
- out.push(...generate(scope, x));
2437
+ out = out.concat(generate(scope, x));
1999
2438
  }
2000
2439
 
2001
2440
  return out;
@@ -2031,6 +2470,18 @@ const internalConstrs = {
2031
2470
  ];
2032
2471
  },
2033
2472
  type: TYPES._array
2473
+ },
2474
+
2475
+ __Array_of: {
2476
+ // this is not a constructor but best fits internal structure here
2477
+ generate: (scope, decl, global, name) => {
2478
+ // Array.of(i0, i1, ...)
2479
+ return generateArray(scope, {
2480
+ elements: decl.arguments
2481
+ }, global, name);
2482
+ },
2483
+ type: TYPES._array,
2484
+ notConstr: true
2034
2485
  }
2035
2486
  };
2036
2487
 
@@ -2044,7 +2495,9 @@ export default program => {
2044
2495
  depth = [];
2045
2496
  typeStates = {};
2046
2497
  arrays = new Map();
2498
+ varMetadata = new Map();
2047
2499
  pages = new Map();
2500
+ data = [];
2048
2501
  currentFuncIndex = importedFuncs.length;
2049
2502
 
2050
2503
  globalThis.valtype = 'f64';
@@ -2121,5 +2574,5 @@ export default program => {
2121
2574
  // if blank main func and other exports, remove it
2122
2575
  if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
2123
2576
 
2124
- return { funcs, globals, tags, exceptions, pages };
2577
+ return { funcs, globals, tags, exceptions, pages, data };
2125
2578
  };