porffor 0.0.0-8c0bdaa → 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,15 +385,16 @@ 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 pointer = arrays.get(name ?? '$undeclared');
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) {
396
+ const pointer = arrays.get(name ?? '$undeclared');
397
+
337
398
  return [
338
399
  // setup right
339
400
  ...right,
@@ -384,15 +445,12 @@ const concatStrings = (scope, left, right, global, name, assign) => {
384
445
 
385
446
  const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
386
447
 
387
- const newOut = makeArray(scope, {
448
+ // alloc/assign array
449
+ const [ , pointer ] = makeArray(scope, {
388
450
  rawElements: new Array(0)
389
451
  }, global, name, true, 'i16');
390
452
 
391
453
  return [
392
- // setup new/out array
393
- ...newOut,
394
- [ Opcodes.drop ],
395
-
396
454
  // setup left
397
455
  ...left,
398
456
  Opcodes.i32_to_u,
@@ -458,89 +516,259 @@ const concatStrings = (scope, left, right, global, name, assign) => {
458
516
  ];
459
517
  };
460
518
 
461
- 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) => {
462
623
  // arrays are always truthy
463
624
  if (type === TYPES._array) return [
464
625
  ...wasm,
465
626
  [ Opcodes.drop ],
466
- number(0)
627
+ ...number(1, int ? Valtype.i32 : valtypeBinary)
467
628
  ];
468
629
 
469
630
  if (type === TYPES.string) {
470
- // if "" (length = 0)
631
+ // if not "" (length = 0)
471
632
  return [
472
633
  // pointer
473
634
  ...wasm,
635
+ Opcodes.i32_to_u,
474
636
 
475
637
  // get length
476
638
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
477
639
 
478
- // if length == 0
479
- [ Opcodes.i32_eqz ],
480
- Opcodes.i32_from_u
481
- ]
640
+ // if length != 0
641
+ /* [ Opcodes.i32_eqz ],
642
+ [ Opcodes.i32_eqz ], */
643
+ ...(int ? [] : [ Opcodes.i32_from_u ])
644
+ ];
482
645
  }
483
646
 
484
- // if = 0
647
+ // if != 0
485
648
  return [
486
649
  ...wasm,
487
650
 
488
- ...Opcodes.eqz,
489
- Opcodes.i32_from_u
651
+ /* Opcodes.eqz,
652
+ [ Opcodes.i32_eqz ],
653
+ Opcodes.i32_from */
490
654
  ];
491
655
  };
492
656
 
493
- const truthy = (scope, wasm, type) => {
657
+ const falsy = (scope, wasm, type, int = false) => {
494
658
  // arrays are always truthy
495
659
  if (type === TYPES._array) return [
496
660
  ...wasm,
497
661
  [ Opcodes.drop ],
498
- number(1)
662
+ ...number(0, int ? Valtype.i32 : valtypeBinary)
499
663
  ];
500
664
 
501
665
  if (type === TYPES.string) {
502
- // if not "" (length = 0)
666
+ // if "" (length = 0)
503
667
  return [
504
668
  // pointer
505
669
  ...wasm,
670
+ Opcodes.i32_to_u,
506
671
 
507
672
  // get length
508
673
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
509
674
 
510
- // if length != 0
511
- /* [ Opcodes.i32_eqz ],
512
- [ Opcodes.i32_eqz ], */
513
- Opcodes.i32_from_u
675
+ // if length == 0
676
+ [ Opcodes.i32_eqz ],
677
+ ...(int ? [] : [ Opcodes.i32_from_u ])
514
678
  ]
515
679
  }
516
680
 
517
- // if != 0
681
+ // if = 0
518
682
  return [
519
683
  ...wasm,
520
684
 
521
- /* Opcodes.eqz,
522
- [ Opcodes.i32_eqz ],
523
- 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)
524
708
  ];
525
709
  };
526
710
 
527
- const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$unspecified', assign = false) => {
711
+ const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
528
712
  if (op === '||' || op === '&&' || op === '??') {
529
- return performLogicOp(scope, op, left, right);
713
+ return performLogicOp(scope, op, left, right, leftType, rightType);
530
714
  }
531
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
+ ];
744
+ }
745
+
746
+ // todo: niche null hell with 0
747
+
532
748
  if (leftType === TYPES.string || rightType === TYPES.string) {
533
749
  if (op === '+') {
534
750
  // string concat (a + b)
535
751
  return concatStrings(scope, left, right, _global, _name, assign);
536
752
  }
537
753
 
538
- // any other math op, NaN
539
- if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
754
+ // not an equality op, NaN
755
+ if (!eqOp) return number(NaN);
540
756
 
541
757
  // else leave bool ops
542
- // todo: convert string to number if string and number or le/ge op
543
- // todo: string equality
758
+ // todo: convert string to number if string and number/bool
759
+ // todo: string (>|>=|<|<=) string
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];
@@ -570,16 +798,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
570
798
  };
571
799
 
572
800
  const generateBinaryExp = (scope, decl, _global, _name) => {
573
- const out = [
574
- ...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
575
- ];
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);
576
802
 
577
803
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
578
804
 
579
805
  return out;
580
806
  };
581
807
 
582
- 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 = [] }) => {
583
809
  const existing = funcs.find(x => x.name === name);
584
810
  if (existing) return existing;
585
811
 
@@ -615,7 +841,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
615
841
  returns,
616
842
  returnType: TYPES[returnType ?? 'number'],
617
843
  wasm,
618
- memory,
619
844
  internal: true,
620
845
  index: currentFuncIndex++
621
846
  };
@@ -634,7 +859,7 @@ const includeBuiltin = (scope, builtin) => {
634
859
  };
635
860
 
636
861
  const generateLogicExp = (scope, decl) => {
637
- 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));
638
863
  };
639
864
 
640
865
  const TYPES = {
@@ -648,7 +873,8 @@ const TYPES = {
648
873
  bigint: 0xffffffffffff7,
649
874
 
650
875
  // these are not "typeof" types but tracked internally
651
- _array: 0xffffffffffff8
876
+ _array: 0xfffffffffff0f,
877
+ _regexp: 0xfffffffffff1f
652
878
  };
653
879
 
654
880
  const TYPE_NAMES = {
@@ -682,6 +908,9 @@ const getType = (scope, _name) => {
682
908
 
683
909
  const getNodeType = (scope, node) => {
684
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
+
685
914
  return TYPES[typeof node.value];
686
915
  }
687
916
 
@@ -696,7 +925,7 @@ const getNodeType = (scope, node) => {
696
925
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
697
926
  const name = node.callee.name;
698
927
  const func = funcs.find(x => x.name === name);
699
- if (func) return func.returnType ?? TYPES.number;
928
+ if (func) return func.returnType;
700
929
 
701
930
  if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
702
931
  if (internalConstrs[name]) return internalConstrs[name].type;
@@ -715,15 +944,18 @@ const getNodeType = (scope, node) => {
715
944
 
716
945
  // literal.func()
717
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
+
718
952
  const baseType = getNodeType(scope, node.callee.object);
719
953
 
720
954
  const func = node.callee.property.name;
721
955
  protoFunc = prototypeFuncs[baseType]?.[func];
722
956
  }
723
957
 
724
- if (protoFunc) return protoFunc.returnType ?? TYPES.number;
725
-
726
- return TYPES.number;
958
+ if (protoFunc) return protoFunc.returnType;
727
959
  }
728
960
 
729
961
  if (node.type === 'ExpressionStatement') {
@@ -755,14 +987,16 @@ const getNodeType = (scope, node) => {
755
987
 
756
988
  if (objectType === TYPES.string && node.computed) return TYPES.string;
757
989
  }
758
-
759
- // default to number
760
- return TYPES.number;
761
990
  };
762
991
 
763
992
  const generateLiteral = (scope, decl, global, name) => {
764
993
  if (decl.value === null) return number(NULL);
765
994
 
995
+ if (decl.regex) {
996
+ scope.regex[name] = decl.regex;
997
+ return number(1);
998
+ }
999
+
766
1000
  switch (typeof decl.value) {
767
1001
  case 'number':
768
1002
  return number(decl.value);
@@ -784,15 +1018,41 @@ const generateLiteral = (scope, decl, global, name) => {
784
1018
  case 'bigint': return number(TYPES.bigint);
785
1019
  }
786
1020
 
1021
+ const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
1022
+ let wellFormed = aotWFA ? true : undefined;
1023
+
787
1024
  const str = decl.value;
788
1025
  const rawElements = new Array(str.length);
1026
+ let j = 0;
789
1027
  for (let i = 0; i < str.length; i++) {
790
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
+ }
791
1047
  }
792
1048
 
1049
+ // console.log(wellFormed, str);
1050
+
1051
+ if (aotWFA) addVarMeta(name, { wellFormed });
1052
+
793
1053
  return makeArray(scope, {
794
1054
  rawElements
795
- }, global, name, false, 'i16');
1055
+ }, global, name, false, 'i16')[0];
796
1056
 
797
1057
  default:
798
1058
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -802,7 +1062,8 @@ const generateLiteral = (scope, decl, global, name) => {
802
1062
  const countLeftover = wasm => {
803
1063
  let count = 0, depth = 0;
804
1064
 
805
- for (const inst of wasm) {
1065
+ for (let i = 0; i < wasm.length; i++) {
1066
+ const inst = wasm[i];
806
1067
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
807
1068
  if (inst[0] === Opcodes.if) count--;
808
1069
  if (inst[1] !== Blocktype.void) count++;
@@ -823,6 +1084,8 @@ const countLeftover = wasm => {
823
1084
  } else count--;
824
1085
  if (func) count += func.returns.length;
825
1086
  } else count--;
1087
+
1088
+ // console.log(count, decompile([ inst ]).slice(0, -1));
826
1089
  }
827
1090
 
828
1091
  return count;
@@ -906,7 +1169,7 @@ const generateCall = (scope, decl, _global, _name) => {
906
1169
  }
907
1170
 
908
1171
  let out = [];
909
- let protoFunc, protoName, baseType, baseName = '$undeclared';
1172
+ let protoFunc, protoName, baseType, baseName;
910
1173
  // ident.func()
911
1174
  if (name && name.startsWith('__')) {
912
1175
  const spl = name.slice(2).split('_');
@@ -921,6 +1184,25 @@ const generateCall = (scope, decl, _global, _name) => {
921
1184
 
922
1185
  // literal.func()
923
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
+
924
1206
  baseType = getNodeType(scope, decl.callee.object);
925
1207
 
926
1208
  const func = decl.callee.property.name;
@@ -929,11 +1211,36 @@ const generateCall = (scope, decl, _global, _name) => {
929
1211
 
930
1212
  out = generate(scope, decl.callee.object);
931
1213
  out.push([ Opcodes.drop ]);
1214
+
1215
+ baseName = [...arrays.keys()].pop();
932
1216
  }
933
1217
 
934
- if (protoFunc) {
935
- 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,
936
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) {
937
1244
  let pointer = arrays.get(baseName);
938
1245
 
939
1246
  if (pointer == null) {
@@ -941,10 +1248,9 @@ const generateCall = (scope, decl, _global, _name) => {
941
1248
  if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
942
1249
 
943
1250
  // register array
944
- makeArray(scope, {
1251
+ 0, [ , pointer ] = makeArray(scope, {
945
1252
  rawElements: new Array(0)
946
1253
  }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
947
- pointer = arrays.get(baseName);
948
1254
 
949
1255
  const [ local, isGlobal ] = lookupName(scope, baseName);
950
1256
 
@@ -962,29 +1268,39 @@ const generateCall = (scope, decl, _global, _name) => {
962
1268
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) return arrayUtil.getLength(pointer)
963
1269
 
964
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;
965
1272
 
966
1273
  // use local for cached i32 length as commonly used
967
1274
  let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
968
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
+
969
1294
  return [
970
1295
  ...out,
971
1296
 
972
- ...arrayUtil.getLengthI32(pointer),
973
- [ Opcodes.local_set, lengthLocal ],
1297
+ ...(!lengthI32CacheUsed ? [] : [
1298
+ ...arrayUtil.getLengthI32(pointer),
1299
+ [ Opcodes.local_set, lengthLocal ],
1300
+ ]),
974
1301
 
975
1302
  [ Opcodes.block, valtypeBinary ],
976
- ...protoFunc(pointer, {
977
- cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
978
- get: arrayUtil.getLength(pointer),
979
- getI32: arrayUtil.getLengthI32(pointer),
980
- set: value => arrayUtil.setLength(pointer, value),
981
- setI32: value => arrayUtil.setLengthI32(pointer, value)
982
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
983
- const out = makeArray(scope, {
984
- rawElements: new Array(length)
985
- }, _global, _name, true, itemType);
986
- return [ out, arrays.get(_name ?? '$undeclared') ];
987
- }),
1303
+ ...protoOut,
988
1304
  [ Opcodes.end ]
989
1305
  ];
990
1306
  }
@@ -1041,11 +1357,10 @@ const generateCall = (scope, decl, _global, _name) => {
1041
1357
  args = args.slice(0, func.params.length);
1042
1358
  }
1043
1359
 
1044
- if (func && func.memory) scope.memory = true;
1045
1360
  if (func && func.throws) scope.throws = true;
1046
1361
 
1047
1362
  for (const arg of args) {
1048
- out.push(...generate(scope, arg));
1363
+ out = out.concat(generate(scope, arg));
1049
1364
  }
1050
1365
 
1051
1366
  out.push([ Opcodes.call, idx ]);
@@ -1056,8 +1371,8 @@ const generateCall = (scope, decl, _global, _name) => {
1056
1371
  const generateNew = (scope, decl, _global, _name) => {
1057
1372
  // hack: basically treat this as a normal call for builtins for now
1058
1373
  const name = mapName(decl.callee.name);
1059
- if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1060
- 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)})`);
1061
1376
 
1062
1377
  return generateCall(scope, decl, _global, _name);
1063
1378
  };
@@ -1074,12 +1389,12 @@ const unhackName = name => {
1074
1389
  };
1075
1390
 
1076
1391
  const generateVar = (scope, decl) => {
1077
- const out = [];
1392
+ let out = [];
1078
1393
 
1079
1394
  const topLevel = scope.name === 'main';
1080
1395
 
1081
1396
  // global variable if in top scope (main) and var ..., or if wanted
1082
- const global = decl.kind === 'var';
1397
+ const global = topLevel || decl._bare; // decl.kind === 'var';
1083
1398
  const target = global ? globals : scope.locals;
1084
1399
 
1085
1400
  for (const x of decl.declarations) {
@@ -1116,7 +1431,7 @@ const generateVar = (scope, decl) => {
1116
1431
 
1117
1432
  // x.init ??= DEFAULT_VALUE;
1118
1433
  if (x.init) {
1119
- out.push(...generate(scope, x.init, global, name));
1434
+ out = out.concat(generate(scope, x.init, global, name));
1120
1435
 
1121
1436
  // if our value is the result of a function, infer the type from that func's return value
1122
1437
  if (out[out.length - 1][0] === Opcodes.call) {
@@ -1165,8 +1480,6 @@ const generateAssign = (scope, decl) => {
1165
1480
  const name = decl.left.object.name;
1166
1481
  const pointer = arrays.get(name);
1167
1482
 
1168
- scope.memory = true;
1169
-
1170
1483
  const aotPointer = pointer != null;
1171
1484
 
1172
1485
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
@@ -1187,13 +1500,60 @@ const generateAssign = (scope, decl) => {
1187
1500
  ];
1188
1501
  }
1189
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
+
1190
1550
  const [ local, isGlobal ] = lookupName(scope, name);
1191
1551
 
1192
1552
  if (local === undefined) {
1193
- // todo: this should be a devtools/repl/??? only thing
1553
+ // todo: this should be a sloppy mode only thing
1194
1554
 
1195
1555
  // only allow = for this
1196
- if (decl.operator !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1556
+ if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1197
1557
 
1198
1558
  if (builtinVars[name]) {
1199
1559
  // just return rhs (eg `NaN = 2`)
@@ -1202,13 +1562,15 @@ const generateAssign = (scope, decl) => {
1202
1562
 
1203
1563
  // set global and return (eg a = 2)
1204
1564
  return [
1205
- ...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
1565
+ ...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
1206
1566
  [ Opcodes.global_get, globals[name].idx ]
1207
1567
  ];
1208
1568
  }
1209
1569
 
1210
- if (decl.operator === '=') {
1211
- typeStates[name] = getNodeType(scope, decl.right);
1570
+ typeStates[name] = getNodeType(scope, decl.right);
1571
+
1572
+ if (op === '=') {
1573
+ // typeStates[name] = getNodeType(scope, decl.right);
1212
1574
 
1213
1575
  return [
1214
1576
  ...generate(scope, decl.right, isGlobal, name),
@@ -1217,8 +1579,26 @@ const generateAssign = (scope, decl) => {
1217
1579
  ];
1218
1580
  }
1219
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
+
1220
1600
  return [
1221
- ...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),
1222
1602
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1223
1603
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1224
1604
  ];
@@ -1245,13 +1625,14 @@ const generateUnary = (scope, decl) => {
1245
1625
 
1246
1626
  case '!':
1247
1627
  // !=
1248
- return falsy(scope, generate(scope, decl.argument));
1628
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
1249
1629
 
1250
1630
  case '~':
1631
+ // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
1251
1632
  return [
1252
1633
  ...generate(scope, decl.argument),
1253
1634
  Opcodes.i32_to,
1254
- [ Opcodes.i32_const, signedLEB128(-1) ],
1635
+ [ Opcodes.i32_const, ...signedLEB128(-1) ],
1255
1636
  [ Opcodes.i32_xor ],
1256
1637
  Opcodes.i32_from
1257
1638
  ];
@@ -1289,7 +1670,7 @@ const generateUnary = (scope, decl) => {
1289
1670
  return out;
1290
1671
 
1291
1672
  case 'typeof':
1292
- const type = getNodeType(scope, decl.argument);
1673
+ const type = getNodeType(scope, decl.argument) ?? TYPES.number;
1293
1674
 
1294
1675
  // for custom types, just return object
1295
1676
  if (type > 0xffffffffffff7) return number(TYPES.object);
@@ -1332,7 +1713,7 @@ const generateUpdate = (scope, decl) => {
1332
1713
  };
1333
1714
 
1334
1715
  const generateIf = (scope, decl) => {
1335
- const out = truthy(scope, generate(scope, decl.test), decl.test);
1716
+ const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test));
1336
1717
 
1337
1718
  out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1338
1719
  depth.push('if');
@@ -1422,9 +1803,116 @@ const generateWhile = (scope, decl) => {
1422
1803
  return out;
1423
1804
  };
1424
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
+
1425
1913
  const getNearestLoop = () => {
1426
1914
  for (let i = depth.length - 1; i >= 0; i--) {
1427
- if (depth[i] === 'while' || depth[i] === 'for') return i;
1915
+ if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
1428
1916
  }
1429
1917
 
1430
1918
  return -1;
@@ -1508,13 +1996,22 @@ const generateAssignPat = (scope, decl) => {
1508
1996
  };
1509
1997
 
1510
1998
  let pages = new Map();
1511
- const allocPage = reason => {
1512
- 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
+ };
1513
2009
 
1514
- let ind = pages.size;
1515
- pages.set(reason, ind);
2010
+ const freePage = reason => {
2011
+ const { ind } = pages.get(reason);
2012
+ pages.delete(reason);
1516
2013
 
1517
- if (codeLog) log('codegen', `allocated new page of memory (${ind}) | ${reason}`);
2014
+ if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
1518
2015
 
1519
2016
  return ind;
1520
2017
  };
@@ -1528,7 +2025,7 @@ const itemTypeToValtype = {
1528
2025
  i16: 'i32'
1529
2026
  };
1530
2027
 
1531
- const storeOps = {
2028
+ const StoreOps = {
1532
2029
  i32: Opcodes.i32_store,
1533
2030
  i64: Opcodes.i64_store,
1534
2031
  f64: Opcodes.f64_store,
@@ -1537,13 +2034,31 @@ const storeOps = {
1537
2034
  i16: Opcodes.i32_store16
1538
2035
  };
1539
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
+
1540
2052
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
1541
2053
  const out = [];
1542
2054
 
2055
+ let firstAssign = false;
1543
2056
  if (!arrays.has(name) || name === '$undeclared') {
2057
+ firstAssign = true;
2058
+
1544
2059
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1545
2060
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1546
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`) * pageSize);
2061
+ arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
1547
2062
  }
1548
2063
 
1549
2064
  const pointer = arrays.get(name);
@@ -1551,8 +2066,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1551
2066
  const useRawElements = !!decl.rawElements;
1552
2067
  const elements = useRawElements ? decl.rawElements : decl.elements;
1553
2068
 
2069
+ const valtype = itemTypeToValtype[itemType];
1554
2070
  const length = elements.length;
1555
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
+
1556
2092
  // store length as 0th array
1557
2093
  out.push(
1558
2094
  ...number(0, Valtype.i32),
@@ -1560,8 +2096,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1560
2096
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
1561
2097
  );
1562
2098
 
1563
- const storeOp = storeOps[itemType];
1564
- const valtype = itemTypeToValtype[itemType];
2099
+ const storeOp = StoreOps[itemType];
1565
2100
 
1566
2101
  if (!initEmpty) for (let i = 0; i < length; i++) {
1567
2102
  if (elements[i] == null) continue;
@@ -1576,14 +2111,23 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1576
2111
  // local value as pointer
1577
2112
  out.push(...number(pointer));
1578
2113
 
1579
- scope.memory = true;
1580
-
1581
- return out;
2114
+ return [ out, pointer ];
1582
2115
  };
1583
2116
 
1584
2117
  let arrays = new Map();
1585
2118
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
1586
- return makeArray(scope, decl, global, name, initEmpty, valtype);
2119
+ return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2120
+ };
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
+ }
1587
2131
  };
1588
2132
 
1589
2133
  export const generateMember = (scope, decl, _global, _name) => {
@@ -1596,8 +2140,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1596
2140
  const name = decl.object.name;
1597
2141
  const pointer = arrays.get(name);
1598
2142
 
1599
- scope.memory = true;
1600
-
1601
2143
  const aotPointer = pointer != null;
1602
2144
 
1603
2145
  return [
@@ -1617,8 +2159,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1617
2159
  const name = decl.object.name;
1618
2160
  const pointer = arrays.get(name);
1619
2161
 
1620
- scope.memory = true;
1621
-
1622
2162
  const aotPointer = pointer != null;
1623
2163
 
1624
2164
  if (type === TYPES._array) {
@@ -1644,10 +2184,9 @@ export const generateMember = (scope, decl, _global, _name) => {
1644
2184
 
1645
2185
  // string
1646
2186
 
1647
- const newOut = makeArray(scope, {
2187
+ const [ newOut, newPointer ] = makeArray(scope, {
1648
2188
  rawElements: new Array(1)
1649
2189
  }, _global, _name, true, 'i16');
1650
- const newPointer = arrays.get(_name ?? '$undeclared');
1651
2190
 
1652
2191
  return [
1653
2192
  // setup new/out array
@@ -1729,7 +2268,7 @@ const generateFunc = (scope, decl) => {
1729
2268
  locals: {},
1730
2269
  localInd: 0,
1731
2270
  returns: [ valtypeBinary ],
1732
- memory: false,
2271
+ returnType: null,
1733
2272
  throws: false,
1734
2273
  name
1735
2274
  };
@@ -1753,9 +2292,8 @@ const generateFunc = (scope, decl) => {
1753
2292
  name,
1754
2293
  params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
1755
2294
  returns: innerScope.returns,
1756
- returnType: innerScope.returnType ?? TYPES.number,
2295
+ returnType: innerScope.returnType,
1757
2296
  locals: innerScope.locals,
1758
- memory: innerScope.memory,
1759
2297
  throws: innerScope.throws,
1760
2298
  index: currentFuncIndex++
1761
2299
  };
@@ -1770,6 +2308,8 @@ const generateFunc = (scope, decl) => {
1770
2308
 
1771
2309
  if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
1772
2310
  wasm.push(...number(0), [ Opcodes.return ]);
2311
+
2312
+ if (func.returnType === null) func.returnType = TYPES.undefined;
1773
2313
  }
1774
2314
 
1775
2315
  // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
@@ -1780,9 +2320,7 @@ const generateFunc = (scope, decl) => {
1780
2320
  if (local.type === Valtype.v128) {
1781
2321
  vecParams++;
1782
2322
 
1783
- /* func.memory = true; // mark func as using memory
1784
-
1785
- wasm.unshift( // add v128 load for param
2323
+ /* wasm.unshift( // add v128 load for param
1786
2324
  [ Opcodes.i32_const, 0 ],
1787
2325
  [ ...Opcodes.v128_load, 0, i * 16 ],
1788
2326
  [ Opcodes.local_set, local.idx ]
@@ -1893,10 +2431,10 @@ const generateFunc = (scope, decl) => {
1893
2431
  };
1894
2432
 
1895
2433
  const generateCode = (scope, decl) => {
1896
- const out = [];
2434
+ let out = [];
1897
2435
 
1898
2436
  for (const x of decl.body) {
1899
- out.push(...generate(scope, x));
2437
+ out = out.concat(generate(scope, x));
1900
2438
  }
1901
2439
 
1902
2440
  return out;
@@ -1912,10 +2450,9 @@ const internalConstrs = {
1912
2450
 
1913
2451
  // new Array(n)
1914
2452
 
1915
- makeArray(scope, {
2453
+ const [ , pointer ] = makeArray(scope, {
1916
2454
  rawElements: new Array(0)
1917
2455
  }, global, name, true);
1918
- const pointer = arrays.get(name ?? '$undeclared');
1919
2456
 
1920
2457
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
1921
2458
 
@@ -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
  };