porffor 0.0.0-828ee15 → 0.0.0-a2afb57

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`);
172
183
 
173
- let inst = Opcodes[asm[0].replace('.', '_')];
174
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
184
+ if (!Array.isArray(inst)) inst = [ inst ];
185
+ const immediates = asm.slice(1).map(x => parseInt(x));
186
+
187
+ out.push([ ...inst, ...immediates ]);
188
+ }
175
189
 
176
- if (!Array.isArray(inst)) inst = [ inst ];
177
- const immediates = asm.slice(1).map(x => parseInt(x));
190
+ return out;
191
+ },
178
192
 
179
- out.push([ ...inst, ...immediates ]);
193
+ __internal_print_type: str => {
194
+ const type = getType(scope, str) - TYPES.number;
195
+
196
+ return [
197
+ ...number(type),
198
+ [ Opcodes.call, importedFuncs.print ],
199
+
200
+ // newline
201
+ ...number(10),
202
+ [ Opcodes.call, importedFuncs.printChar ]
203
+ ];
204
+ }
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,14 +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
- const getLocalTmp = name => localTmp(scope, name + binaryExpDepth);
331
-
332
388
  const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
333
389
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
334
390
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
335
391
 
392
+ const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
393
+ if (aotWFA) addVarMeta(name, { wellFormed: undefined });
394
+
336
395
  if (assign) {
337
396
  const pointer = arrays.get(name ?? '$undeclared');
338
397
 
@@ -457,90 +516,259 @@ const concatStrings = (scope, left, right, global, name, assign) => {
457
516
  ];
458
517
  };
459
518
 
460
- const falsy = (scope, wasm, type) => {
519
+ const compareStrings = (scope, left, right) => {
520
+ // todo: this should be rewritten into a built-in/func: String.prototype.concat
521
+ // todo: convert left and right to strings if not
522
+ // todo: optimize by looking up names in arrays and using that if exists?
523
+ // todo: optimize this if using literals/known lengths?
524
+
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);
529
+
530
+ const index = localTmp(scope, 'compare_index', Valtype.i32);
531
+ const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
532
+
533
+ return [
534
+ // setup left
535
+ ...left,
536
+ Opcodes.i32_to_u,
537
+ [ Opcodes.local_tee, leftPointer ],
538
+
539
+ // setup right
540
+ ...right,
541
+ Opcodes.i32_to_u,
542
+ [ Opcodes.local_tee, rightPointer ],
543
+
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 ],
548
+
549
+ // get lengths
550
+ [ Opcodes.local_get, leftPointer ],
551
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
552
+ [ Opcodes.local_tee, leftLength ],
553
+
554
+ [ Opcodes.local_get, rightPointer ],
555
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
556
+ [ Opcodes.local_tee, rightLength ],
557
+
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 ],
564
+
565
+ // no fast path for length = 0 as it would probably be slower for most of the time?
566
+
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 ],
573
+
574
+ // iterate over each char and check if eq
575
+ [ Opcodes.loop, Blocktype.void ],
576
+
577
+ // fetch left
578
+ [ Opcodes.local_get, index ],
579
+ [ Opcodes.local_get, leftPointer ],
580
+ [ Opcodes.i32_add ],
581
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
582
+
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) ],
588
+
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 ],
595
+
596
+ // index += sizeof i16 (2)
597
+ [ Opcodes.local_get, index ],
598
+ ...number(ValtypeSize.i16, Valtype.i32),
599
+ [ Opcodes.i32_add ],
600
+ [ Opcodes.local_tee, index ],
601
+
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 ],
607
+
608
+ // no failed checks, so true!
609
+ ...number(1, Valtype.i32),
610
+
611
+ // pointers match, so true
612
+ [ Opcodes.else ],
613
+ ...number(1, Valtype.i32),
614
+ [ Opcodes.end ],
615
+
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
619
+ ];
620
+ };
621
+
622
+ const truthy = (scope, wasm, type, int = false) => {
461
623
  // arrays are always truthy
462
624
  if (type === TYPES._array) return [
463
625
  ...wasm,
464
626
  [ Opcodes.drop ],
465
- number(0)
627
+ ...number(1, int ? Valtype.i32 : valtypeBinary)
466
628
  ];
467
629
 
468
630
  if (type === TYPES.string) {
469
- // if "" (length = 0)
631
+ // if not "" (length = 0)
470
632
  return [
471
633
  // pointer
472
634
  ...wasm,
635
+ Opcodes.i32_to_u,
473
636
 
474
637
  // get length
475
638
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
476
639
 
477
- // if length == 0
478
- [ Opcodes.i32_eqz ],
479
- Opcodes.i32_from_u
480
- ]
640
+ // if length != 0
641
+ /* [ Opcodes.i32_eqz ],
642
+ [ Opcodes.i32_eqz ], */
643
+ ...(int ? [] : [ Opcodes.i32_from_u ])
644
+ ];
481
645
  }
482
646
 
483
- // if = 0
647
+ // if != 0
484
648
  return [
485
649
  ...wasm,
486
650
 
487
- ...Opcodes.eqz,
488
- Opcodes.i32_from_u
651
+ /* Opcodes.eqz,
652
+ [ Opcodes.i32_eqz ],
653
+ Opcodes.i32_from */
489
654
  ];
490
655
  };
491
656
 
492
- const truthy = (scope, wasm, type) => {
657
+ const falsy = (scope, wasm, type, int = false) => {
493
658
  // arrays are always truthy
494
659
  if (type === TYPES._array) return [
495
660
  ...wasm,
496
661
  [ Opcodes.drop ],
497
- number(1)
662
+ ...number(0, int ? Valtype.i32 : valtypeBinary)
498
663
  ];
499
664
 
500
665
  if (type === TYPES.string) {
501
- // if not "" (length = 0)
666
+ // if "" (length = 0)
502
667
  return [
503
668
  // pointer
504
669
  ...wasm,
670
+ Opcodes.i32_to_u,
505
671
 
506
672
  // get length
507
673
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
508
674
 
509
- // if length != 0
510
- /* [ Opcodes.i32_eqz ],
511
- [ Opcodes.i32_eqz ], */
512
- Opcodes.i32_from_u
675
+ // if length == 0
676
+ [ Opcodes.i32_eqz ],
677
+ ...(int ? [] : [ Opcodes.i32_from_u ])
513
678
  ]
514
679
  }
515
680
 
516
- // if != 0
681
+ // if = 0
517
682
  return [
518
683
  ...wasm,
519
684
 
520
- /* Opcodes.eqz,
521
- [ Opcodes.i32_eqz ],
522
- 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)
523
708
  ];
524
709
  };
525
710
 
526
711
  const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
527
712
  if (op === '||' || op === '&&' || op === '??') {
528
- return performLogicOp(scope, op, left, right);
713
+ return performLogicOp(scope, op, left, right, leftType, rightType);
714
+ }
715
+
716
+ if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
717
+
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
+
734
+ return [
735
+ ...left,
736
+ [ Opcodes.drop ],
737
+
738
+ ...right,
739
+ [ Opcodes.drop ],
740
+
741
+ // return true (!=/!==) or false (else)
742
+ ...number(op === '!=' || op === '!==' ? 1 : 0, Valtype.i32)
743
+ ];
529
744
  }
530
745
 
746
+ // todo: niche null hell with 0
747
+
531
748
  if (leftType === TYPES.string || rightType === TYPES.string) {
532
749
  if (op === '+') {
533
750
  // string concat (a + b)
534
751
  return concatStrings(scope, left, right, _global, _name, assign);
535
752
  }
536
753
 
537
- // any other math op, NaN
538
- if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
754
+ // not an equality op, NaN
755
+ if (!eqOp) return number(NaN);
539
756
 
540
757
  // else leave bool ops
541
- // todo: convert string to number if string and number
758
+ // todo: convert string to number if string and number/bool
542
759
  // todo: string (>|>=|<|<=) string
543
- // todo: string equality
760
+
761
+ // string comparison
762
+ if (op === '===' || op === '==') {
763
+ return compareStrings(scope, left, right);
764
+ }
765
+
766
+ if (op === '!==' || op === '!=') {
767
+ return [
768
+ ...compareStrings(scope, left, right),
769
+ [ Opcodes.i32_eqz ]
770
+ ];
771
+ }
544
772
  }
545
773
 
546
774
  let ops = operatorOpcode[valtype][op];
@@ -569,21 +797,15 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
569
797
  ];
570
798
  };
571
799
 
572
- let binaryExpDepth = 0;
573
800
  const generateBinaryExp = (scope, decl, _global, _name) => {
574
- binaryExpDepth++;
575
-
576
- const out = [
577
- ...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
578
- ];
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);
579
802
 
580
803
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
581
804
 
582
- binaryExpDepth--;
583
805
  return out;
584
806
  };
585
807
 
586
- 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 = [] }) => {
587
809
  const existing = funcs.find(x => x.name === name);
588
810
  if (existing) return existing;
589
811
 
@@ -619,7 +841,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
619
841
  returns,
620
842
  returnType: TYPES[returnType ?? 'number'],
621
843
  wasm,
622
- memory,
623
844
  internal: true,
624
845
  index: currentFuncIndex++
625
846
  };
@@ -638,7 +859,7 @@ const includeBuiltin = (scope, builtin) => {
638
859
  };
639
860
 
640
861
  const generateLogicExp = (scope, decl) => {
641
- 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));
642
863
  };
643
864
 
644
865
  const TYPES = {
@@ -652,7 +873,8 @@ const TYPES = {
652
873
  bigint: 0xffffffffffff7,
653
874
 
654
875
  // these are not "typeof" types but tracked internally
655
- _array: 0xffffffffffff8
876
+ _array: 0xfffffffffff0f,
877
+ _regexp: 0xfffffffffff1f
656
878
  };
657
879
 
658
880
  const TYPE_NAMES = {
@@ -686,6 +908,9 @@ const getType = (scope, _name) => {
686
908
 
687
909
  const getNodeType = (scope, node) => {
688
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
+
689
914
  return TYPES[typeof node.value];
690
915
  }
691
916
 
@@ -700,7 +925,7 @@ const getNodeType = (scope, node) => {
700
925
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
701
926
  const name = node.callee.name;
702
927
  const func = funcs.find(x => x.name === name);
703
- if (func) return func.returnType ?? TYPES.number;
928
+ if (func) return func.returnType;
704
929
 
705
930
  if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
706
931
  if (internalConstrs[name]) return internalConstrs[name].type;
@@ -719,15 +944,18 @@ const getNodeType = (scope, node) => {
719
944
 
720
945
  // literal.func()
721
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
+
722
952
  const baseType = getNodeType(scope, node.callee.object);
723
953
 
724
954
  const func = node.callee.property.name;
725
955
  protoFunc = prototypeFuncs[baseType]?.[func];
726
956
  }
727
957
 
728
- if (protoFunc) return protoFunc.returnType ?? TYPES.number;
729
-
730
- return TYPES.number;
958
+ if (protoFunc) return protoFunc.returnType;
731
959
  }
732
960
 
733
961
  if (node.type === 'ExpressionStatement') {
@@ -759,14 +987,16 @@ const getNodeType = (scope, node) => {
759
987
 
760
988
  if (objectType === TYPES.string && node.computed) return TYPES.string;
761
989
  }
762
-
763
- // default to number
764
- return TYPES.number;
765
990
  };
766
991
 
767
992
  const generateLiteral = (scope, decl, global, name) => {
768
993
  if (decl.value === null) return number(NULL);
769
994
 
995
+ if (decl.regex) {
996
+ scope.regex[name] = decl.regex;
997
+ return number(1);
998
+ }
999
+
770
1000
  switch (typeof decl.value) {
771
1001
  case 'number':
772
1002
  return number(decl.value);
@@ -788,12 +1018,38 @@ const generateLiteral = (scope, decl, global, name) => {
788
1018
  case 'bigint': return number(TYPES.bigint);
789
1019
  }
790
1020
 
1021
+ const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
1022
+ let wellFormed = aotWFA ? true : undefined;
1023
+
791
1024
  const str = decl.value;
792
1025
  const rawElements = new Array(str.length);
1026
+ let j = 0;
793
1027
  for (let i = 0; i < str.length; i++) {
794
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
+ }
795
1047
  }
796
1048
 
1049
+ // console.log(wellFormed, str);
1050
+
1051
+ if (aotWFA) addVarMeta(name, { wellFormed });
1052
+
797
1053
  return makeArray(scope, {
798
1054
  rawElements
799
1055
  }, global, name, false, 'i16')[0];
@@ -806,7 +1062,8 @@ const generateLiteral = (scope, decl, global, name) => {
806
1062
  const countLeftover = wasm => {
807
1063
  let count = 0, depth = 0;
808
1064
 
809
- for (const inst of wasm) {
1065
+ for (let i = 0; i < wasm.length; i++) {
1066
+ const inst = wasm[i];
810
1067
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
811
1068
  if (inst[0] === Opcodes.if) count--;
812
1069
  if (inst[1] !== Blocktype.void) count++;
@@ -827,6 +1084,8 @@ const countLeftover = wasm => {
827
1084
  } else count--;
828
1085
  if (func) count += func.returns.length;
829
1086
  } else count--;
1087
+
1088
+ // console.log(count, decompile([ inst ]).slice(0, -1));
830
1089
  }
831
1090
 
832
1091
  return count;
@@ -910,7 +1169,7 @@ const generateCall = (scope, decl, _global, _name) => {
910
1169
  }
911
1170
 
912
1171
  let out = [];
913
- let protoFunc, protoName, baseType, baseName = '$undeclared';
1172
+ let protoFunc, protoName, baseType, baseName;
914
1173
  // ident.func()
915
1174
  if (name && name.startsWith('__')) {
916
1175
  const spl = name.slice(2).split('_');
@@ -925,6 +1184,25 @@ const generateCall = (scope, decl, _global, _name) => {
925
1184
 
926
1185
  // literal.func()
927
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
+
928
1206
  baseType = getNodeType(scope, decl.callee.object);
929
1207
 
930
1208
  const func = decl.callee.property.name;
@@ -933,11 +1211,36 @@ const generateCall = (scope, decl, _global, _name) => {
933
1211
 
934
1212
  out = generate(scope, decl.callee.object);
935
1213
  out.push([ Opcodes.drop ]);
1214
+
1215
+ baseName = [...arrays.keys()].pop();
936
1216
  }
937
1217
 
938
- if (protoFunc) {
939
- 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);
940
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
+ ]),
1236
+
1237
+ // call regex func
1238
+ [ Opcodes.call, func.index ],
1239
+ Opcodes.i32_from
1240
+ ];
1241
+ }
1242
+
1243
+ if (protoFunc) {
941
1244
  let pointer = arrays.get(baseName);
942
1245
 
943
1246
  if (pointer == null) {
@@ -945,7 +1248,7 @@ const generateCall = (scope, decl, _global, _name) => {
945
1248
  if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
946
1249
 
947
1250
  // register array
948
- const [ , pointer ] = makeArray(scope, {
1251
+ 0, [ , pointer ] = makeArray(scope, {
949
1252
  rawElements: new Array(0)
950
1253
  }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
951
1254
 
@@ -965,28 +1268,39 @@ const generateCall = (scope, decl, _global, _name) => {
965
1268
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) return arrayUtil.getLength(pointer)
966
1269
 
967
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;
968
1272
 
969
1273
  // use local for cached i32 length as commonly used
970
1274
  let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
971
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
+
972
1294
  return [
973
1295
  ...out,
974
1296
 
975
- ...arrayUtil.getLengthI32(pointer),
976
- [ Opcodes.local_set, lengthLocal ],
1297
+ ...(!lengthI32CacheUsed ? [] : [
1298
+ ...arrayUtil.getLengthI32(pointer),
1299
+ [ Opcodes.local_set, lengthLocal ],
1300
+ ]),
977
1301
 
978
1302
  [ Opcodes.block, valtypeBinary ],
979
- ...protoFunc(pointer, {
980
- cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
981
- get: arrayUtil.getLength(pointer),
982
- getI32: arrayUtil.getLengthI32(pointer),
983
- set: value => arrayUtil.setLength(pointer, value),
984
- setI32: value => arrayUtil.setLengthI32(pointer, value)
985
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
986
- return makeArray(scope, {
987
- rawElements: new Array(length)
988
- }, _global, _name, true, itemType);
989
- }),
1303
+ ...protoOut,
990
1304
  [ Opcodes.end ]
991
1305
  ];
992
1306
  }
@@ -1043,11 +1357,10 @@ const generateCall = (scope, decl, _global, _name) => {
1043
1357
  args = args.slice(0, func.params.length);
1044
1358
  }
1045
1359
 
1046
- if (func && func.memory) scope.memory = true;
1047
1360
  if (func && func.throws) scope.throws = true;
1048
1361
 
1049
1362
  for (const arg of args) {
1050
- out.push(...generate(scope, arg));
1363
+ out = out.concat(generate(scope, arg));
1051
1364
  }
1052
1365
 
1053
1366
  out.push([ Opcodes.call, idx ]);
@@ -1058,8 +1371,8 @@ const generateCall = (scope, decl, _global, _name) => {
1058
1371
  const generateNew = (scope, decl, _global, _name) => {
1059
1372
  // hack: basically treat this as a normal call for builtins for now
1060
1373
  const name = mapName(decl.callee.name);
1061
- if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1062
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet (new ${unhackName(name)})`);
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)})`);
1063
1376
 
1064
1377
  return generateCall(scope, decl, _global, _name);
1065
1378
  };
@@ -1076,12 +1389,12 @@ const unhackName = name => {
1076
1389
  };
1077
1390
 
1078
1391
  const generateVar = (scope, decl) => {
1079
- const out = [];
1392
+ let out = [];
1080
1393
 
1081
1394
  const topLevel = scope.name === 'main';
1082
1395
 
1083
1396
  // global variable if in top scope (main) and var ..., or if wanted
1084
- const global = decl.kind === 'var';
1397
+ const global = topLevel || decl._bare; // decl.kind === 'var';
1085
1398
  const target = global ? globals : scope.locals;
1086
1399
 
1087
1400
  for (const x of decl.declarations) {
@@ -1118,7 +1431,7 @@ const generateVar = (scope, decl) => {
1118
1431
 
1119
1432
  // x.init ??= DEFAULT_VALUE;
1120
1433
  if (x.init) {
1121
- out.push(...generate(scope, x.init, global, name));
1434
+ out = out.concat(generate(scope, x.init, global, name));
1122
1435
 
1123
1436
  // if our value is the result of a function, infer the type from that func's return value
1124
1437
  if (out[out.length - 1][0] === Opcodes.call) {
@@ -1167,8 +1480,6 @@ const generateAssign = (scope, decl) => {
1167
1480
  const name = decl.left.object.name;
1168
1481
  const pointer = arrays.get(name);
1169
1482
 
1170
- scope.memory = true;
1171
-
1172
1483
  const aotPointer = pointer != null;
1173
1484
 
1174
1485
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
@@ -1189,13 +1500,60 @@ const generateAssign = (scope, decl) => {
1189
1500
  ];
1190
1501
  }
1191
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
+
1192
1550
  const [ local, isGlobal ] = lookupName(scope, name);
1193
1551
 
1194
1552
  if (local === undefined) {
1195
- // todo: this should be a devtools/repl/??? only thing
1553
+ // todo: this should be a sloppy mode only thing
1196
1554
 
1197
1555
  // only allow = for this
1198
- if (decl.operator !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1556
+ if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1199
1557
 
1200
1558
  if (builtinVars[name]) {
1201
1559
  // just return rhs (eg `NaN = 2`)
@@ -1204,13 +1562,15 @@ const generateAssign = (scope, decl) => {
1204
1562
 
1205
1563
  // set global and return (eg a = 2)
1206
1564
  return [
1207
- ...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
1565
+ ...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
1208
1566
  [ Opcodes.global_get, globals[name].idx ]
1209
1567
  ];
1210
1568
  }
1211
1569
 
1212
- if (decl.operator === '=') {
1213
- typeStates[name] = getNodeType(scope, decl.right);
1570
+ typeStates[name] = getNodeType(scope, decl.right);
1571
+
1572
+ if (op === '=') {
1573
+ // typeStates[name] = getNodeType(scope, decl.right);
1214
1574
 
1215
1575
  return [
1216
1576
  ...generate(scope, decl.right, isGlobal, name),
@@ -1219,8 +1579,26 @@ const generateAssign = (scope, decl) => {
1219
1579
  ];
1220
1580
  }
1221
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
+
1222
1600
  return [
1223
- ...performOp(scope, decl.operator.slice(0, -1), [ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ] ], generate(scope, decl.right), getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
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),
1224
1602
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1225
1603
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1226
1604
  ];
@@ -1247,13 +1625,14 @@ const generateUnary = (scope, decl) => {
1247
1625
 
1248
1626
  case '!':
1249
1627
  // !=
1250
- return falsy(scope, generate(scope, decl.argument));
1628
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
1251
1629
 
1252
1630
  case '~':
1631
+ // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
1253
1632
  return [
1254
1633
  ...generate(scope, decl.argument),
1255
1634
  Opcodes.i32_to,
1256
- [ Opcodes.i32_const, signedLEB128(-1) ],
1635
+ [ Opcodes.i32_const, ...signedLEB128(-1) ],
1257
1636
  [ Opcodes.i32_xor ],
1258
1637
  Opcodes.i32_from
1259
1638
  ];
@@ -1291,7 +1670,7 @@ const generateUnary = (scope, decl) => {
1291
1670
  return out;
1292
1671
 
1293
1672
  case 'typeof':
1294
- const type = getNodeType(scope, decl.argument);
1673
+ const type = getNodeType(scope, decl.argument) ?? TYPES.number;
1295
1674
 
1296
1675
  // for custom types, just return object
1297
1676
  if (type > 0xffffffffffff7) return number(TYPES.object);
@@ -1334,7 +1713,7 @@ const generateUpdate = (scope, decl) => {
1334
1713
  };
1335
1714
 
1336
1715
  const generateIf = (scope, decl) => {
1337
- const out = truthy(scope, generate(scope, decl.test), decl.test);
1716
+ const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test));
1338
1717
 
1339
1718
  out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1340
1719
  depth.push('if');
@@ -1424,9 +1803,116 @@ const generateWhile = (scope, decl) => {
1424
1803
  return out;
1425
1804
  };
1426
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
+
1427
1913
  const getNearestLoop = () => {
1428
1914
  for (let i = depth.length - 1; i >= 0; i--) {
1429
- if (depth[i] === 'while' || depth[i] === 'for') return i;
1915
+ if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
1430
1916
  }
1431
1917
 
1432
1918
  return -1;
@@ -1510,13 +1996,22 @@ const generateAssignPat = (scope, decl) => {
1510
1996
  };
1511
1997
 
1512
1998
  let pages = new Map();
1513
- const allocPage = reason => {
1514
- if (pages.has(reason)) return pages.get(reason);
1999
+ const allocPage = (reason, type) => {
2000
+ if (pages.has(reason)) return pages.get(reason).ind;
2001
+
2002
+ const ind = pages.size;
2003
+ pages.set(reason, { ind, type });
2004
+
2005
+ if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2006
+
2007
+ return ind;
2008
+ };
1515
2009
 
1516
- let ind = pages.size;
1517
- pages.set(reason, ind);
2010
+ const freePage = reason => {
2011
+ const { ind } = pages.get(reason);
2012
+ pages.delete(reason);
1518
2013
 
1519
- if (codeLog) log('codegen', `allocated new page of memory (${ind}) | ${reason}`);
2014
+ if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
1520
2015
 
1521
2016
  return ind;
1522
2017
  };
@@ -1530,7 +2025,7 @@ const itemTypeToValtype = {
1530
2025
  i16: 'i32'
1531
2026
  };
1532
2027
 
1533
- const storeOps = {
2028
+ const StoreOps = {
1534
2029
  i32: Opcodes.i32_store,
1535
2030
  i64: Opcodes.i64_store,
1536
2031
  f64: Opcodes.f64_store,
@@ -1539,13 +2034,31 @@ const storeOps = {
1539
2034
  i16: Opcodes.i32_store16
1540
2035
  };
1541
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
+
1542
2052
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
1543
2053
  const out = [];
1544
2054
 
2055
+ let firstAssign = false;
1545
2056
  if (!arrays.has(name) || name === '$undeclared') {
2057
+ firstAssign = true;
2058
+
1546
2059
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1547
2060
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1548
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`) * pageSize);
2061
+ arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
1549
2062
  }
1550
2063
 
1551
2064
  const pointer = arrays.get(name);
@@ -1553,8 +2066,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1553
2066
  const useRawElements = !!decl.rawElements;
1554
2067
  const elements = useRawElements ? decl.rawElements : decl.elements;
1555
2068
 
2069
+ const valtype = itemTypeToValtype[itemType];
1556
2070
  const length = elements.length;
1557
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
+
1558
2092
  // store length as 0th array
1559
2093
  out.push(
1560
2094
  ...number(0, Valtype.i32),
@@ -1562,8 +2096,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1562
2096
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
1563
2097
  );
1564
2098
 
1565
- const storeOp = storeOps[itemType];
1566
- const valtype = itemTypeToValtype[itemType];
2099
+ const storeOp = StoreOps[itemType];
1567
2100
 
1568
2101
  if (!initEmpty) for (let i = 0; i < length; i++) {
1569
2102
  if (elements[i] == null) continue;
@@ -1578,8 +2111,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1578
2111
  // local value as pointer
1579
2112
  out.push(...number(pointer));
1580
2113
 
1581
- scope.memory = true;
1582
-
1583
2114
  return [ out, pointer ];
1584
2115
  };
1585
2116
 
@@ -1588,6 +2119,17 @@ const generateArray = (scope, decl, global = false, name = '$undeclared', initEm
1588
2119
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
1589
2120
  };
1590
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
+
1591
2133
  export const generateMember = (scope, decl, _global, _name) => {
1592
2134
  const type = getNodeType(scope, decl.object);
1593
2135
 
@@ -1598,8 +2140,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1598
2140
  const name = decl.object.name;
1599
2141
  const pointer = arrays.get(name);
1600
2142
 
1601
- scope.memory = true;
1602
-
1603
2143
  const aotPointer = pointer != null;
1604
2144
 
1605
2145
  return [
@@ -1619,8 +2159,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1619
2159
  const name = decl.object.name;
1620
2160
  const pointer = arrays.get(name);
1621
2161
 
1622
- scope.memory = true;
1623
-
1624
2162
  const aotPointer = pointer != null;
1625
2163
 
1626
2164
  if (type === TYPES._array) {
@@ -1730,7 +2268,7 @@ const generateFunc = (scope, decl) => {
1730
2268
  locals: {},
1731
2269
  localInd: 0,
1732
2270
  returns: [ valtypeBinary ],
1733
- memory: false,
2271
+ returnType: null,
1734
2272
  throws: false,
1735
2273
  name
1736
2274
  };
@@ -1754,9 +2292,8 @@ const generateFunc = (scope, decl) => {
1754
2292
  name,
1755
2293
  params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
1756
2294
  returns: innerScope.returns,
1757
- returnType: innerScope.returnType ?? TYPES.number,
2295
+ returnType: innerScope.returnType,
1758
2296
  locals: innerScope.locals,
1759
- memory: innerScope.memory,
1760
2297
  throws: innerScope.throws,
1761
2298
  index: currentFuncIndex++
1762
2299
  };
@@ -1771,6 +2308,8 @@ const generateFunc = (scope, decl) => {
1771
2308
 
1772
2309
  if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
1773
2310
  wasm.push(...number(0), [ Opcodes.return ]);
2311
+
2312
+ if (func.returnType === null) func.returnType = TYPES.undefined;
1774
2313
  }
1775
2314
 
1776
2315
  // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
@@ -1781,9 +2320,7 @@ const generateFunc = (scope, decl) => {
1781
2320
  if (local.type === Valtype.v128) {
1782
2321
  vecParams++;
1783
2322
 
1784
- /* func.memory = true; // mark func as using memory
1785
-
1786
- wasm.unshift( // add v128 load for param
2323
+ /* wasm.unshift( // add v128 load for param
1787
2324
  [ Opcodes.i32_const, 0 ],
1788
2325
  [ ...Opcodes.v128_load, 0, i * 16 ],
1789
2326
  [ Opcodes.local_set, local.idx ]
@@ -1894,10 +2431,10 @@ const generateFunc = (scope, decl) => {
1894
2431
  };
1895
2432
 
1896
2433
  const generateCode = (scope, decl) => {
1897
- const out = [];
2434
+ let out = [];
1898
2435
 
1899
2436
  for (const x of decl.body) {
1900
- out.push(...generate(scope, x));
2437
+ out = out.concat(generate(scope, x));
1901
2438
  }
1902
2439
 
1903
2440
  return out;
@@ -1933,6 +2470,18 @@ const internalConstrs = {
1933
2470
  ];
1934
2471
  },
1935
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
1936
2485
  }
1937
2486
  };
1938
2487
 
@@ -1946,7 +2495,9 @@ export default program => {
1946
2495
  depth = [];
1947
2496
  typeStates = {};
1948
2497
  arrays = new Map();
2498
+ varMetadata = new Map();
1949
2499
  pages = new Map();
2500
+ data = [];
1950
2501
  currentFuncIndex = importedFuncs.length;
1951
2502
 
1952
2503
  globalThis.valtype = 'f64';
@@ -2023,5 +2574,5 @@ export default program => {
2023
2574
  // if blank main func and other exports, remove it
2024
2575
  if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
2025
2576
 
2026
- return { funcs, globals, tags, exceptions, pages };
2577
+ return { funcs, globals, tags, exceptions, pages, data };
2027
2578
  };