porffor 0.1.1 → 0.2.0-08a272e

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,12 @@
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
+ import { log } from "./log.js";
7
8
  import parse from "./parse.js";
9
+ import * as Rhemyn from "../rhemyn/compile.js";
8
10
 
9
11
  let globals = {};
10
12
  let globalInd = 0;
@@ -35,7 +37,14 @@ const debug = str => {
35
37
  };
36
38
 
37
39
  const todo = msg => {
38
- throw new Error(`todo: ${msg}`);
40
+ class TodoError extends Error {
41
+ constructor(message) {
42
+ super(message);
43
+ this.name = 'TodoError';
44
+ }
45
+ }
46
+
47
+ throw new TodoError(`todo: ${msg}`);
39
48
 
40
49
  const code = [];
41
50
 
@@ -101,6 +110,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
101
110
  case 'WhileStatement':
102
111
  return generateWhile(scope, decl);
103
112
 
113
+ case 'ForOfStatement':
114
+ return generateForOf(scope, decl);
115
+
104
116
  case 'BreakStatement':
105
117
  return generateBreak(scope, decl);
106
118
 
@@ -141,47 +153,72 @@ const generate = (scope, decl, global = false, name = undefined) => {
141
153
 
142
154
  return [];
143
155
 
144
- case 'TaggedTemplateExpression':
145
- // hack for inline asm
146
- if (decl.tag.name !== 'asm') return todo('tagged template expressions not implemented');
156
+ case 'TaggedTemplateExpression': {
157
+ const funcs = {
158
+ asm: str => {
159
+ let out = [];
147
160
 
148
- const str = decl.quasi.quasis[0].value.raw;
149
- let out = [];
161
+ for (const line of str.split('\n')) {
162
+ const asm = line.trim().split(';;')[0].split(' ');
163
+ if (asm[0] === '') continue; // blank
150
164
 
151
- for (const line of str.split('\n')) {
152
- const asm = line.trim().split(';;')[0].split(' ');
153
- if (asm[0] === '') continue; // blank
165
+ if (asm[0] === 'local') {
166
+ const [ name, idx, type ] = asm.slice(1);
167
+ scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
168
+ continue;
169
+ }
154
170
 
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
- }
171
+ if (asm[0] === 'returns') {
172
+ scope.returns = asm.slice(1).map(x => Valtype[x]);
173
+ continue;
174
+ }
160
175
 
161
- if (asm[0] === 'returns') {
162
- scope.returns = asm.slice(1).map(x => Valtype[x]);
163
- continue;
164
- }
176
+ if (asm[0] === 'memory') {
177
+ allocPage('asm instrinsic');
178
+ // todo: add to store/load offset insts
179
+ continue;
180
+ }
165
181
 
166
- if (asm[0] === 'memory') {
167
- scope.memory = true;
168
- allocPage('asm instrinsic');
169
- // todo: add to store/load offset insts
170
- continue;
171
- }
182
+ let inst = Opcodes[asm[0].replace('.', '_')];
183
+ if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
184
+
185
+ if (!Array.isArray(inst)) inst = [ inst ];
186
+ const immediates = asm.slice(1).map(x => parseInt(x));
187
+
188
+ out.push([ ...inst, ...immediates ]);
189
+ }
190
+
191
+ return out;
192
+ },
172
193
 
173
- let inst = Opcodes[asm[0].replace('.', '_')];
174
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
194
+ __internal_print_type: str => {
195
+ const type = getType(scope, str) - TYPES.number;
175
196
 
176
- if (!Array.isArray(inst)) inst = [ inst ];
177
- const immediates = asm.slice(1).map(x => parseInt(x));
197
+ return [
198
+ ...number(type),
199
+ [ Opcodes.call, importedFuncs.print ],
178
200
 
179
- out.push([ ...inst, ...immediates ]);
201
+ // newline
202
+ ...number(10),
203
+ [ Opcodes.call, importedFuncs.printChar ]
204
+ ];
205
+ }
180
206
  }
181
207
 
182
- return out;
208
+ const name = decl.tag.name;
209
+ // hack for inline asm
210
+ if (!funcs[name]) return todo('tagged template expressions not implemented');
211
+
212
+ const str = decl.quasi.quasis[0].value.raw;
213
+ return funcs[name](str);
214
+ }
183
215
 
184
216
  default:
217
+ if (decl.type.startsWith('TS')) {
218
+ // ignore typescript nodes
219
+ return [];
220
+ }
221
+
185
222
  return todo(`no generation for ${decl.type}!`);
186
223
  }
187
224
  };
@@ -269,19 +306,17 @@ const generateIdent = (scope, decl) => {
269
306
 
270
307
  const generateReturn = (scope, decl) => {
271
308
  if (decl.argument === null) {
272
- if (!scope.returnType) scope.returnType = TYPES.undefined;
273
-
274
309
  // just bare "return"
275
310
  return [
276
311
  ...number(UNDEFINED), // "undefined" if func returns
312
+ ...number(TYPES.undefined, Valtype.i32), // type undefined
277
313
  [ Opcodes.return ]
278
314
  ];
279
315
  }
280
316
 
281
- if (!scope.returnType) scope.returnType = getNodeType(scope, decl.argument);
282
-
283
317
  return [
284
318
  ...generate(scope, decl.argument),
319
+ ...getNodeType(scope, decl.argument),
285
320
  [ Opcodes.return ]
286
321
  ];
287
322
  };
@@ -295,11 +330,13 @@ const localTmp = (scope, name, type = valtypeBinary) => {
295
330
  return idx;
296
331
  };
297
332
 
298
- const performLogicOp = (scope, op, left, right) => {
333
+ const isIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
334
+
335
+ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
299
336
  const checks = {
300
- '||': Opcodes.eqz,
301
- '&&': [ Opcodes.i32_to ]
302
- // todo: ??
337
+ '||': falsy,
338
+ '&&': truthy,
339
+ '??': nullish
303
340
  };
304
341
 
305
342
  if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
@@ -307,14 +344,52 @@ const performLogicOp = (scope, op, left, right) => {
307
344
  // generic structure for {a} OP {b}
308
345
  // -->
309
346
  // _ = {a}; if (OP_CHECK) {b} else _
347
+
348
+ // if we can, use int tmp and convert at the end to help prevent unneeded conversions
349
+ // (like if we are in an if condition - very common)
350
+ const leftIsInt = isIntOp(left[left.length - 1]);
351
+ const rightIsInt = isIntOp(right[right.length - 1]);
352
+
353
+ const canInt = leftIsInt && rightIsInt;
354
+
355
+ if (canInt) {
356
+ // remove int -> float conversions from left and right
357
+ left.pop();
358
+ right.pop();
359
+
360
+ return [
361
+ ...left,
362
+ [ Opcodes.local_tee, localTmp(scope, 'logictmpi', Valtype.i32) ],
363
+ ...checks[op](scope, [], leftType, true, true),
364
+ [ Opcodes.if, Valtype.i32 ],
365
+ ...right,
366
+ // note type
367
+ ...rightType,
368
+ setLastType(scope),
369
+ [ Opcodes.else ],
370
+ [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
371
+ // note type
372
+ ...leftType,
373
+ setLastType(scope),
374
+ [ Opcodes.end ],
375
+ Opcodes.i32_from
376
+ ];
377
+ }
378
+
310
379
  return [
311
380
  ...left,
312
381
  [ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
313
- ...checks[op],
382
+ ...checks[op](scope, [], leftType, false, true),
314
383
  [ Opcodes.if, valtypeBinary ],
315
384
  ...right,
385
+ // note type
386
+ ...rightType,
387
+ setLastType(scope),
316
388
  [ Opcodes.else ],
317
389
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
390
+ // note type
391
+ ...leftType,
392
+ setLastType(scope),
318
393
  [ Opcodes.end ]
319
394
  ];
320
395
  };
@@ -325,15 +400,16 @@ const concatStrings = (scope, left, right, global, name, assign) => {
325
400
  // todo: optimize by looking up names in arrays and using that if exists?
326
401
  // todo: optimize this if using literals/known lengths?
327
402
 
328
- scope.memory = true;
329
-
330
- const pointer = arrays.get(name ?? '$undeclared');
331
-
332
403
  const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
333
404
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
334
405
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
335
406
 
407
+ const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
408
+ if (aotWFA) addVarMeta(name, { wellFormed: undefined });
409
+
336
410
  if (assign) {
411
+ const pointer = arrays.get(name ?? '$undeclared');
412
+
337
413
  return [
338
414
  // setup right
339
415
  ...right,
@@ -384,15 +460,12 @@ const concatStrings = (scope, left, right, global, name, assign) => {
384
460
 
385
461
  const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
386
462
 
387
- const newOut = makeArray(scope, {
463
+ // alloc/assign array
464
+ const [ , pointer ] = makeArray(scope, {
388
465
  rawElements: new Array(0)
389
466
  }, global, name, true, 'i16');
390
467
 
391
468
  return [
392
- // setup new/out array
393
- ...newOut,
394
- [ Opcodes.drop ],
395
-
396
469
  // setup left
397
470
  ...left,
398
471
  Opcodes.i32_to_u,
@@ -458,90 +531,309 @@ const concatStrings = (scope, left, right, global, name, assign) => {
458
531
  ];
459
532
  };
460
533
 
461
- const falsy = (scope, wasm, type) => {
462
- // arrays are always truthy
463
- if (type === TYPES._array) return [
464
- ...wasm,
465
- [ Opcodes.drop ],
466
- number(0)
467
- ];
468
-
469
- if (type === TYPES.string) {
470
- // if "" (length = 0)
471
- return [
472
- // pointer
473
- ...wasm,
534
+ const compareStrings = (scope, left, right) => {
535
+ // todo: this should be rewritten into a func
536
+ // todo: convert left and right to strings if not
537
+ // todo: optimize by looking up names in arrays and using that if exists?
538
+ // todo: optimize this if using literals/known lengths?
474
539
 
475
- // get length
476
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
540
+ const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
541
+ const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
542
+ const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
543
+ const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
477
544
 
478
- // if length == 0
479
- [ Opcodes.i32_eqz ],
480
- Opcodes.i32_from_u
481
- ]
482
- }
545
+ const index = localTmp(scope, 'compare_index', Valtype.i32);
546
+ const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
483
547
 
484
- // if = 0
485
548
  return [
486
- ...wasm,
549
+ // setup left
550
+ ...left,
551
+ Opcodes.i32_to_u,
552
+ [ Opcodes.local_tee, leftPointer ],
487
553
 
488
- ...Opcodes.eqz,
489
- Opcodes.i32_from_u
554
+ // setup right
555
+ ...right,
556
+ Opcodes.i32_to_u,
557
+ [ Opcodes.local_tee, rightPointer ],
558
+
559
+ // fast path: check leftPointer == rightPointer
560
+ // use if (block) for everything after to "return" a value early
561
+ [ Opcodes.i32_ne ],
562
+ [ Opcodes.if, Valtype.i32 ],
563
+
564
+ // get lengths
565
+ [ Opcodes.local_get, leftPointer ],
566
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
567
+ [ Opcodes.local_tee, leftLength ],
568
+
569
+ [ Opcodes.local_get, rightPointer ],
570
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
571
+ [ Opcodes.local_tee, rightLength ],
572
+
573
+ // fast path: check leftLength != rightLength
574
+ [ Opcodes.i32_ne ],
575
+ [ Opcodes.if, Blocktype.void ],
576
+ ...number(0, Valtype.i32),
577
+ [ Opcodes.br, 1 ],
578
+ [ Opcodes.end ],
579
+
580
+ // no fast path for length = 0 as it would probably be slower for most of the time?
581
+
582
+ // tmp could have already been used
583
+ ...number(0, Valtype.i32),
584
+ [ Opcodes.local_set, index ],
585
+
586
+ // setup index end as length * sizeof i16 (2)
587
+ // we do this instead of having to do mul/div each iter for perf™
588
+ [ Opcodes.local_get, leftLength ],
589
+ ...number(ValtypeSize.i16, Valtype.i32),
590
+ [ Opcodes.i32_mul ],
591
+ [ Opcodes.local_set, indexEnd ],
592
+
593
+ // iterate over each char and check if eq
594
+ [ Opcodes.loop, Blocktype.void ],
595
+
596
+ // fetch left
597
+ [ Opcodes.local_get, index ],
598
+ [ Opcodes.local_get, leftPointer ],
599
+ [ Opcodes.i32_add ],
600
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
601
+
602
+ // fetch right
603
+ [ Opcodes.local_get, index ],
604
+ [ Opcodes.local_get, rightPointer ],
605
+ [ Opcodes.i32_add ],
606
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
607
+
608
+ // not equal, "return" false
609
+ [ Opcodes.i32_ne ],
610
+ [ Opcodes.if, Blocktype.void ],
611
+ ...number(0, Valtype.i32),
612
+ [ Opcodes.br, 2 ],
613
+ [ Opcodes.end ],
614
+
615
+ // index += sizeof i16 (2)
616
+ [ Opcodes.local_get, index ],
617
+ ...number(ValtypeSize.i16, Valtype.i32),
618
+ [ Opcodes.i32_add ],
619
+ [ Opcodes.local_tee, index ],
620
+
621
+ // if index != index end (length * sizeof 16), loop
622
+ [ Opcodes.local_get, indexEnd ],
623
+ [ Opcodes.i32_ne ],
624
+ [ Opcodes.br_if, 0 ],
625
+ [ Opcodes.end ],
626
+
627
+ // no failed checks, so true!
628
+ ...number(1, Valtype.i32),
629
+
630
+ // pointers match, so true
631
+ [ Opcodes.else ],
632
+ ...number(1, Valtype.i32),
633
+ [ Opcodes.end ],
634
+
635
+ // convert i32 result to valtype
636
+ // do not do as automatically added by binary exp gen for equality ops
637
+ // Opcodes.i32_from_u
490
638
  ];
491
639
  };
492
640
 
493
- const truthy = (scope, wasm, type) => {
494
- // arrays are always truthy
495
- if (type === TYPES._array) return [
641
+ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
642
+ if (isIntOp(wasm[wasm.length - 1])) return [
496
643
  ...wasm,
497
- [ Opcodes.drop ],
498
- number(1)
644
+ ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
499
645
  ];
500
646
 
501
- if (type === TYPES.string) {
502
- // if not "" (length = 0)
503
- return [
504
- // pointer
505
- ...wasm,
506
-
507
- // get length
508
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
647
+ const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
509
648
 
510
- // if length != 0
511
- /* [ Opcodes.i32_eqz ],
512
- [ Opcodes.i32_eqz ], */
513
- Opcodes.i32_from_u
514
- ]
515
- }
649
+ const def = [
650
+ // if value != 0
651
+ [ Opcodes.local_get, tmp ],
516
652
 
517
- // if != 0
518
- return [
519
- ...wasm,
653
+ // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
654
+ ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
520
655
 
521
656
  /* Opcodes.eqz,
522
657
  [ Opcodes.i32_eqz ],
523
658
  Opcodes.i32_from */
524
659
  ];
660
+
661
+ return [
662
+ ...wasm,
663
+ [ Opcodes.local_set, tmp ],
664
+
665
+ ...typeSwitch(scope, type, {
666
+ // [TYPES.number]: def,
667
+ [TYPES._array]: [
668
+ // arrays are always truthy
669
+ ...number(1, intOut ? Valtype.i32 : valtypeBinary)
670
+ ],
671
+ [TYPES.string]: [
672
+ [ Opcodes.local_get, tmp ],
673
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
674
+
675
+ // get length
676
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
677
+
678
+ // if length != 0
679
+ /* [ Opcodes.i32_eqz ],
680
+ [ Opcodes.i32_eqz ], */
681
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
682
+ ],
683
+ default: def
684
+ }, intOut ? Valtype.i32 : valtypeBinary)
685
+ ];
686
+ };
687
+
688
+ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
689
+ const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
690
+ return [
691
+ ...wasm,
692
+ [ Opcodes.local_set, tmp ],
693
+
694
+ ...typeSwitch(scope, type, {
695
+ [TYPES._array]: [
696
+ // arrays are always truthy
697
+ ...number(0, intOut ? Valtype.i32 : valtypeBinary)
698
+ ],
699
+ [TYPES.string]: [
700
+ [ Opcodes.local_get, tmp ],
701
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
702
+
703
+ // get length
704
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
705
+
706
+ // if length == 0
707
+ [ Opcodes.i32_eqz ],
708
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
709
+ ],
710
+ default: [
711
+ // if value == 0
712
+ [ Opcodes.local_get, tmp ],
713
+
714
+ ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
715
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
716
+ ]
717
+ }, intOut ? Valtype.i32 : valtypeBinary)
718
+ ];
719
+ };
720
+
721
+ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
722
+ const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
723
+ return [
724
+ ...wasm,
725
+ [ Opcodes.local_set, tmp ],
726
+
727
+ ...typeSwitch(scope, type, {
728
+ [TYPES.undefined]: [
729
+ // undefined
730
+ ...number(1, intOut ? Valtype.i32 : valtypeBinary)
731
+ ],
732
+ [TYPES.object]: [
733
+ // object, null if == 0
734
+ [ Opcodes.local_get, tmp ],
735
+
736
+ ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
737
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
738
+ ],
739
+ default: [
740
+ // not
741
+ ...number(0, intOut ? Valtype.i32 : valtypeBinary)
742
+ ]
743
+ }, intOut ? Valtype.i32 : valtypeBinary)
744
+ ];
525
745
  };
526
746
 
527
- const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$unspecified', assign = false) => {
747
+ const stringOnly = wasm => {
748
+ if (!Array.isArray(wasm[0])) return [ ...wasm, 'string_only' ];
749
+ if (wasm.length === 1) return [ [ ...wasm[0], 'string_only' ] ];
750
+
751
+ return [
752
+ [ ...wasm[0], 'string_only|start' ],
753
+ ...wasm.slice(1, -1),
754
+ [ ...wasm[wasm.length - 1], 'string_only|end' ]
755
+ ];
756
+ }
757
+
758
+ const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
528
759
  if (op === '||' || op === '&&' || op === '??') {
529
- return performLogicOp(scope, op, left, right);
760
+ return performLogicOp(scope, op, left, right, leftType, rightType);
761
+ }
762
+
763
+ const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
764
+ const strictOp = op === '===' || op === '!==';
765
+
766
+ const startOut = [], endOut = [];
767
+ const finalise = out => startOut.concat(out, endOut);
768
+
769
+ // if strict (in)equal check types match
770
+ if (strictOp) {
771
+ // startOut.push(
772
+ // ...leftType,
773
+ // ...rightType,
774
+ // [ Opcodes.i32_eq ]
775
+ // );
776
+
777
+ // endOut.push(
778
+ // [ Opcodes.i32_and ]
779
+ // );
780
+
781
+ // startOut.push(
782
+ // [ Opcodes.block, Valtype.i32 ],
783
+ // ...leftType,
784
+ // ...rightType,
785
+ // [ Opcodes.i32_ne ],
786
+ // [ Opcodes.if, Blocktype.void ],
787
+ // ...number(op === '===' ? 0 : 1, Valtype.i32),
788
+ // [ Opcodes.br, 1 ],
789
+ // [ Opcodes.end ]
790
+ // );
791
+
792
+ // endOut.push(
793
+ // [ Opcodes.end ]
794
+ // );
795
+
796
+ endOut.push(
797
+ ...leftType,
798
+ ...rightType,
799
+ ...(op === '===' ? [
800
+ [ Opcodes.i32_eq ],
801
+ [ Opcodes.i32_and ]
802
+ ] : [
803
+ [ Opcodes.i32_ne ],
804
+ [ Opcodes.i32_or ]
805
+ ])
806
+ );
530
807
  }
531
808
 
532
- if (leftType === TYPES.string || rightType === TYPES.string) {
533
- if (op === '+') {
534
- // string concat (a + b)
535
- return concatStrings(scope, left, right, _global, _name, assign);
536
- }
809
+ // todo: if equality op and an operand is undefined, return false
810
+ // todo: niche null hell with 0
537
811
 
538
- // any other math op, NaN
539
- if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
812
+ // if (leftType === TYPES.string || rightType === TYPES.string) {
813
+ // if (op === '+') {
814
+ // // string concat (a + b)
815
+ // return finalise(concatStrings(scope, left, right, _global, _name, assign));
816
+ // }
540
817
 
541
- // else leave bool ops
542
- // todo: convert string to number if string and number or le/ge op
543
- // todo: string equality
544
- }
818
+ // // not an equality op, NaN
819
+ // if (!eqOp) return finalise(number(NaN));
820
+
821
+ // // else leave bool ops
822
+ // // todo: convert string to number if string and number/bool
823
+ // // todo: string (>|>=|<|<=) string
824
+
825
+ // // string comparison
826
+ // if (op === '===' || op === '==') {
827
+ // return finalise(compareStrings(scope, left, right));
828
+ // }
829
+
830
+ // if (op === '!==' || op === '!=') {
831
+ // return finalise([
832
+ // ...compareStrings(scope, left, right),
833
+ // [ Opcodes.i32_eqz ]
834
+ // ]);
835
+ // }
836
+ // }
545
837
 
546
838
  let ops = operatorOpcode[valtype][op];
547
839
 
@@ -551,35 +843,99 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
551
843
  includeBuiltin(scope, builtinName);
552
844
  const idx = funcIndex[builtinName];
553
845
 
554
- return [
846
+ return finalise([
555
847
  ...left,
556
848
  ...right,
557
849
  [ Opcodes.call, idx ]
558
- ];
850
+ ]);
559
851
  }
560
852
 
561
853
  if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
562
854
 
563
855
  if (!Array.isArray(ops)) ops = [ ops ];
856
+ ops = [ ops ];
857
+
858
+ let tmpLeft, tmpRight;
859
+ // if equal op, check if strings for compareStrings
860
+ if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
861
+ const knownLeft = knownType(scope, leftType);
862
+ const knownRight = knownType(scope, rightType);
863
+
864
+ // todo: intelligent partial skip later
865
+ // if neither known are string, stop this madness
866
+ if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
867
+ return;
868
+ }
564
869
 
565
- return [
870
+ tmpLeft = localTmp(scope, '__tmpop_left');
871
+ tmpRight = localTmp(scope, '__tmpop_right');
872
+
873
+ ops.unshift(...stringOnly([
874
+ // if left is string
875
+ ...leftType,
876
+ ...number(TYPES.string, Valtype.i32),
877
+ [ Opcodes.i32_eq ],
878
+
879
+ // if right is string
880
+ ...rightType,
881
+ ...number(TYPES.string, Valtype.i32),
882
+ [ Opcodes.i32_eq ],
883
+
884
+ // if either are true
885
+ [ Opcodes.i32_or ],
886
+ [ Opcodes.if, Blocktype.void ],
887
+
888
+ // todo: convert non-strings to strings, for now fail immediately if one is not
889
+ // if left is not string
890
+ ...leftType,
891
+ ...number(TYPES.string, Valtype.i32),
892
+ [ Opcodes.i32_ne ],
893
+
894
+ // if right is not string
895
+ ...rightType,
896
+ ...number(TYPES.string, Valtype.i32),
897
+ [ Opcodes.i32_ne ],
898
+
899
+ // if either are true
900
+ [ Opcodes.i32_or ],
901
+ [ Opcodes.if, Blocktype.void ],
902
+ ...number(0, Valtype.i32),
903
+ [ Opcodes.br, 1 ],
904
+ [ Opcodes.end ],
905
+
906
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
907
+ // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
908
+ ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
909
+ [ Opcodes.br, 1 ],
910
+ [ Opcodes.end ],
911
+ ]));
912
+
913
+ // if not already in block, add a block
914
+ // if (endOut.length === 0) {
915
+ startOut.push(stringOnly([ Opcodes.block, Valtype.i32 ]));
916
+ // endOut.push(stringOnly([ Opcodes.end ]));
917
+ endOut.unshift(stringOnly([ Opcodes.end ]));
918
+ // }
919
+ })();
920
+
921
+ return finalise([
566
922
  ...left,
923
+ ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
567
924
  ...right,
568
- ops
569
- ];
925
+ ...(tmpRight != null ? stringOnly([ [ Opcodes.local_tee, tmpRight ] ]) : []),
926
+ ...ops
927
+ ]);
570
928
  };
571
929
 
572
930
  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
- ];
931
+ 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
932
 
577
933
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
578
934
 
579
935
  return out;
580
936
  };
581
937
 
582
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, memory, localNames = [], globalNames = [] }) => {
938
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
583
939
  const existing = funcs.find(x => x.name === name);
584
940
  if (existing) return existing;
585
941
 
@@ -615,7 +971,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
615
971
  returns,
616
972
  returnType: TYPES[returnType ?? 'number'],
617
973
  wasm,
618
- memory,
619
974
  internal: true,
620
975
  index: currentFuncIndex++
621
976
  };
@@ -634,21 +989,45 @@ const includeBuiltin = (scope, builtin) => {
634
989
  };
635
990
 
636
991
  const generateLogicExp = (scope, decl) => {
637
- return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
992
+ return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
638
993
  };
639
994
 
995
+ // T = JS type, V = value/pointer
996
+ // 0bTTT
997
+ // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
998
+ // 50 bits usable: 0 11111111111 11??????????????????????????????????????????????????
999
+ // js type: 4 bits
1000
+ // internal type: ? bits
1001
+ // pointer: 32 bits
1002
+ // https://piotrduperas.com/posts/nan-boxing
1003
+ // 0x7ffc000000000000
1004
+ // budget: 50 bits
1005
+ // js type: 4 bits
1006
+ // internal type: ? bits
1007
+ // pointer: 32 bits
1008
+
1009
+ // generic
1010
+ // 1 23 4 5
1011
+ // 0 11111111111 11TTTTIIII??????????PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
1012
+ // 1: regular iEEE 754 double NaN
1013
+ // 2: extra 1 bit to identify NaN box
1014
+ // 3: js type
1015
+ // 4: internal type
1016
+ // 5: pointer
1017
+
640
1018
  const TYPES = {
641
- number: 0xffffffffffff0,
642
- boolean: 0xffffffffffff1,
643
- string: 0xffffffffffff2,
644
- undefined: 0xffffffffffff3,
645
- object: 0xffffffffffff4,
646
- function: 0xffffffffffff5,
647
- symbol: 0xffffffffffff6,
648
- bigint: 0xffffffffffff7,
1019
+ number: 0x00,
1020
+ boolean: 0x01,
1021
+ string: 0x02,
1022
+ undefined: 0x03,
1023
+ object: 0x04,
1024
+ function: 0x05,
1025
+ symbol: 0x06,
1026
+ bigint: 0x07,
649
1027
 
650
1028
  // these are not "typeof" types but tracked internally
651
- _array: 0xffffffffffff8
1029
+ _array: 0x10,
1030
+ _regexp: 0x11
652
1031
  };
653
1032
 
654
1033
  const TYPE_NAMES = {
@@ -661,108 +1040,201 @@ const TYPE_NAMES = {
661
1040
  [TYPES.symbol]: 'Symbol',
662
1041
  [TYPES.bigint]: 'BigInt',
663
1042
 
664
- [TYPES._array]: 'Array'
1043
+ [TYPES._array]: 'Array',
1044
+ [TYPES._regexp]: 'RegExp'
665
1045
  };
666
1046
 
667
- let typeStates = {};
668
-
669
1047
  const getType = (scope, _name) => {
670
1048
  const name = mapName(_name);
671
- if (scope.locals[name]) return typeStates[name];
672
1049
 
673
- if (builtinVars[name]) return TYPES[builtinVars[name].type ?? 'number'];
674
- if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) return TYPES.function;
675
- if (globals[name]) return typeStates[name];
1050
+ if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1051
+ if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
676
1052
 
677
- if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)]) return TYPES.function;
678
- if (name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) return TYPES.function;
1053
+ let type = TYPES.undefined;
1054
+ if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1055
+ if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
679
1056
 
680
- return TYPES.undefined;
1057
+ if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
1058
+ name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
1059
+
1060
+ return number(type, Valtype.i32);
681
1061
  };
682
1062
 
683
- const getNodeType = (scope, node) => {
684
- if (node.type === 'Literal') {
685
- return TYPES[typeof node.value];
686
- }
1063
+ const setType = (scope, _name, type) => {
1064
+ const name = mapName(_name);
687
1065
 
688
- if (isFuncType(node.type)) {
689
- return TYPES.function;
690
- }
1066
+ const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
691
1067
 
692
- if (node.type === 'Identifier') {
693
- return getType(scope, node.name);
694
- }
1068
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return [];
1069
+ if (scope.locals[name]) return [
1070
+ ...out,
1071
+ [ Opcodes.local_set, scope.locals[name + '#type'].idx ]
1072
+ ];
695
1073
 
696
- if (node.type === 'CallExpression' || node.type === 'NewExpression') {
697
- const name = node.callee.name;
698
- const func = funcs.find(x => x.name === name);
699
- if (func) return func.returnType ?? TYPES.number;
1074
+ if (typedInput && globals[name]?.metadata?.type != null) return [];
1075
+ if (globals[name]) return [
1076
+ ...out,
1077
+ [ Opcodes.global_set, globals[name + '#type'].idx ]
1078
+ ];
1079
+
1080
+ // throw new Error('could not find var');
1081
+ };
700
1082
 
701
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
702
- if (internalConstrs[name]) return internalConstrs[name].type;
1083
+ const getLastType = scope => {
1084
+ scope.gotLastType = true;
1085
+ return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1086
+ };
703
1087
 
704
- let protoFunc;
705
- // ident.func()
706
- if (name && name.startsWith('__')) {
707
- const spl = name.slice(2).split('_');
1088
+ const setLastType = scope => {
1089
+ return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1090
+ };
708
1091
 
709
- const baseName = spl.slice(0, -1).join('_');
710
- const baseType = getType(scope, baseName);
1092
+ const getNodeType = (scope, node) => {
1093
+ const inner = () => {
1094
+ if (node.type === 'Literal') {
1095
+ if (node.regex) return TYPES._regexp;
711
1096
 
712
- const func = spl[spl.length - 1];
713
- protoFunc = prototypeFuncs[baseType]?.[func];
1097
+ return TYPES[typeof node.value];
714
1098
  }
715
1099
 
716
- // literal.func()
717
- if (!name && node.callee.type === 'MemberExpression') {
718
- const baseType = getNodeType(scope, node.callee.object);
1100
+ if (isFuncType(node.type)) {
1101
+ return TYPES.function;
1102
+ }
719
1103
 
720
- const func = node.callee.property.name;
721
- protoFunc = prototypeFuncs[baseType]?.[func];
1104
+ if (node.type === 'Identifier') {
1105
+ return getType(scope, node.name);
722
1106
  }
723
1107
 
724
- if (protoFunc) return protoFunc.returnType ?? TYPES.number;
1108
+ if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1109
+ const name = node.callee.name;
1110
+ const func = funcs.find(x => x.name === name);
725
1111
 
726
- return TYPES.number;
727
- }
1112
+ if (func) {
1113
+ // console.log(scope, func, func.returnType);
1114
+ if (func.returnType) return func.returnType;
1115
+ }
728
1116
 
729
- if (node.type === 'ExpressionStatement') {
730
- return getNodeType(scope, node.expression);
731
- }
1117
+ if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1118
+ if (internalConstrs[name]) return internalConstrs[name].type;
732
1119
 
733
- if (node.type === 'AssignmentExpression') {
734
- return getNodeType(scope, node.right);
735
- }
1120
+ // check if this is a prototype function
1121
+ // if so and there is only one impl (eg charCodeAt)
1122
+ // use that return type as that is the only possibility
1123
+ // (if non-matching type it would error out)
1124
+ if (name.startsWith('__')) {
1125
+ const spl = name.slice(2).split('_');
736
1126
 
737
- if (node.type === 'ArrayExpression') {
738
- return TYPES._array;
739
- }
1127
+ const func = spl[spl.length - 1];
1128
+ const protoFuncs = Object.values(prototypeFuncs).filter(x => x[func] != null);
1129
+ if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1130
+ }
740
1131
 
741
- if (node.type === 'BinaryExpression') {
742
- if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1132
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
743
1133
 
744
- if (node.operator === '+' && (getNodeType(scope, node.left) === TYPES.string || getNodeType(scope, node.right) === TYPES.string)) return TYPES.string;
745
- }
1134
+ // presume
1135
+ // todo: warn here?
1136
+ return TYPES.number;
746
1137
 
747
- if (node.type === 'UnaryExpression') {
748
- if (node.operator === '!') return TYPES.boolean;
749
- if (node.operator === 'void') return TYPES.undefined;
750
- if (node.operator === 'delete') return TYPES.boolean;
751
- }
1138
+ // let protoFunc;
1139
+ // // ident.func()
1140
+ // if (name && name.startsWith('__')) {
1141
+ // const spl = name.slice(2).split('_');
752
1142
 
753
- if (node.type === 'MemberExpression') {
754
- const objectType = getNodeType(scope, node.object);
1143
+ // const baseName = spl.slice(0, -1).join('_');
1144
+ // const baseType = getType(scope, baseName);
755
1145
 
756
- if (objectType === TYPES.string && node.computed) return TYPES.string;
757
- }
1146
+ // const func = spl[spl.length - 1];
1147
+ // protoFunc = prototypeFuncs[baseType]?.[func];
1148
+ // }
1149
+
1150
+ // // literal.func()
1151
+ // if (!name && node.callee.type === 'MemberExpression') {
1152
+ // if (node.callee.object.regex) {
1153
+ // const funcName = node.callee.property.name;
1154
+ // return Rhemyn[funcName] ? TYPES.boolean : TYPES.undefined;
1155
+ // }
1156
+
1157
+ // const baseType = getNodeType(scope, node.callee.object);
1158
+
1159
+ // const func = node.callee.property.name;
1160
+ // protoFunc = prototypeFuncs[baseType]?.[func];
1161
+ // }
1162
+
1163
+ // if (protoFunc) return protoFunc.returnType;
1164
+ }
1165
+
1166
+ if (node.type === 'ExpressionStatement') {
1167
+ return getNodeType(scope, node.expression);
1168
+ }
1169
+
1170
+ if (node.type === 'AssignmentExpression') {
1171
+ return getNodeType(scope, node.right);
1172
+ }
1173
+
1174
+ if (node.type === 'ArrayExpression') {
1175
+ return TYPES._array;
1176
+ }
1177
+
1178
+ if (node.type === 'BinaryExpression') {
1179
+ if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1180
+ return TYPES.number;
1181
+
1182
+ // todo: string concat types
1183
+ // if (node.operator !== '+') return TYPES.number;
1184
+ // else return [
1185
+ // // if left is string
1186
+ // ...getNodeType(scope, node.left),
1187
+ // ...number(TYPES.string, Valtype.i32),
1188
+ // [ Opcodes.i32_eq ],
1189
+
1190
+ // // if right is string
1191
+ // ...getNodeType(scope, node.right),
1192
+ // ...number(TYPES.string, Valtype.i32),
1193
+ // [ Opcodes.i32_eq ],
1194
+
1195
+ // // if either are true
1196
+ // [ Opcodes.i32_or ],
1197
+ // ];
1198
+ }
1199
+
1200
+ if (node.type === 'UnaryExpression') {
1201
+ if (node.operator === '!') return TYPES.boolean;
1202
+ if (node.operator === 'void') return TYPES.undefined;
1203
+ if (node.operator === 'delete') return TYPES.boolean;
1204
+ if (node.operator === 'typeof') return TYPES.string;
758
1205
 
759
- // default to number
760
- return TYPES.number;
1206
+ return TYPES.number;
1207
+ }
1208
+
1209
+ if (node.type === 'MemberExpression') {
1210
+ // hack: if something.length, number type
1211
+ if (node.property.name === 'length') return TYPES.number;
1212
+
1213
+ // we cannot guess
1214
+ return TYPES.number;
1215
+ }
1216
+
1217
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1218
+
1219
+ // presume
1220
+ // todo: warn here?
1221
+ return TYPES.number;
1222
+ };
1223
+
1224
+ const ret = inner();
1225
+ // console.trace(node, ret);
1226
+ if (typeof ret === 'number') return number(ret, Valtype.i32);
1227
+ return ret;
761
1228
  };
762
1229
 
763
1230
  const generateLiteral = (scope, decl, global, name) => {
764
1231
  if (decl.value === null) return number(NULL);
765
1232
 
1233
+ if (decl.regex) {
1234
+ scope.regex[name] = decl.regex;
1235
+ return number(1);
1236
+ }
1237
+
766
1238
  switch (typeof decl.value) {
767
1239
  case 'number':
768
1240
  return number(decl.value);
@@ -772,27 +1244,16 @@ const generateLiteral = (scope, decl, global, name) => {
772
1244
  return number(decl.value ? 1 : 0);
773
1245
 
774
1246
  case 'string':
775
- // this is a terrible hack which changes type strings ("number" etc) to known const number values
776
- switch (decl.value) {
777
- case 'number': return number(TYPES.number);
778
- case 'boolean': return number(TYPES.boolean);
779
- case 'string': return number(TYPES.string);
780
- case 'undefined': return number(TYPES.undefined);
781
- case 'object': return number(TYPES.object);
782
- case 'function': return number(TYPES.function);
783
- case 'symbol': return number(TYPES.symbol);
784
- case 'bigint': return number(TYPES.bigint);
785
- }
786
-
787
1247
  const str = decl.value;
788
1248
  const rawElements = new Array(str.length);
1249
+ let j = 0;
789
1250
  for (let i = 0; i < str.length; i++) {
790
1251
  rawElements[i] = str.charCodeAt(i);
791
1252
  }
792
1253
 
793
1254
  return makeArray(scope, {
794
1255
  rawElements
795
- }, global, name, false, 'i16');
1256
+ }, global, name, false, 'i16')[0];
796
1257
 
797
1258
  default:
798
1259
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -802,7 +1263,8 @@ const generateLiteral = (scope, decl, global, name) => {
802
1263
  const countLeftover = wasm => {
803
1264
  let count = 0, depth = 0;
804
1265
 
805
- for (const inst of wasm) {
1266
+ for (let i = 0; i < wasm.length; i++) {
1267
+ const inst = wasm[i];
806
1268
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
807
1269
  if (inst[0] === Opcodes.if) count--;
808
1270
  if (inst[1] !== Blocktype.void) count++;
@@ -811,11 +1273,12 @@ const countLeftover = wasm => {
811
1273
  if (inst[0] === Opcodes.end) depth--;
812
1274
 
813
1275
  if (depth === 0)
814
- if ([Opcodes.throw, Opcodes.return, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1276
+ if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
815
1277
  else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
816
1278
  else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
817
1279
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
818
1280
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1281
+ else if (inst[0] === Opcodes.return) count = 0;
819
1282
  else if (inst[0] === Opcodes.call) {
820
1283
  let func = funcs.find(x => x.index === inst[1]);
821
1284
  if (func) {
@@ -823,6 +1286,8 @@ const countLeftover = wasm => {
823
1286
  } else count--;
824
1287
  if (func) count += func.returns.length;
825
1288
  } else count--;
1289
+
1290
+ // console.log(count, decompile([ inst ]).slice(0, -1));
826
1291
  }
827
1292
 
828
1293
  return count;
@@ -843,7 +1308,7 @@ const generateExp = (scope, decl) => {
843
1308
  return out;
844
1309
  };
845
1310
 
846
- const arrayUtil = {
1311
+ const CTArrayUtil = {
847
1312
  getLengthI32: pointer => [
848
1313
  ...number(0, Valtype.i32),
849
1314
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
@@ -869,6 +1334,32 @@ const arrayUtil = {
869
1334
  ]
870
1335
  };
871
1336
 
1337
+ const RTArrayUtil = {
1338
+ getLengthI32: pointer => [
1339
+ ...pointer,
1340
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ]
1341
+ ],
1342
+
1343
+ getLength: pointer => [
1344
+ ...pointer,
1345
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
1346
+ Opcodes.i32_from_u
1347
+ ],
1348
+
1349
+ setLengthI32: (pointer, value) => [
1350
+ ...pointer,
1351
+ ...value,
1352
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
1353
+ ],
1354
+
1355
+ setLength: (pointer, value) => [
1356
+ ...pointer,
1357
+ ...value,
1358
+ Opcodes.i32_to_u,
1359
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
1360
+ ]
1361
+ };
1362
+
872
1363
  const generateCall = (scope, decl, _global, _name) => {
873
1364
  /* const callee = decl.callee;
874
1365
  const args = decl.arguments;
@@ -898,95 +1389,164 @@ const generateCall = (scope, decl, _global, _name) => {
898
1389
  const lastInst = out[out.length - 1];
899
1390
  if (lastInst && lastInst[0] === Opcodes.drop) {
900
1391
  out.splice(out.length - 1, 1);
1392
+
1393
+ const finalStatement = parsed.body[parsed.body.length - 1];
1394
+ out.push(
1395
+ ...getNodeType(scope, finalStatement),
1396
+ setLastType(scope)
1397
+ );
901
1398
  } else if (countLeftover(out) === 0) {
902
1399
  out.push(...number(UNDEFINED));
1400
+ out.push(
1401
+ ...number(TYPES.undefined, Valtype.i32),
1402
+ setLastType(scope)
1403
+ );
903
1404
  }
904
1405
 
1406
+ // if (lastInst && lastInst[0] === Opcodes.drop) {
1407
+ // out.splice(out.length - 1, 1);
1408
+ // } else if (countLeftover(out) === 0) {
1409
+ // out.push(...number(UNDEFINED));
1410
+ // }
1411
+
905
1412
  return out;
906
1413
  }
907
1414
 
908
- let out = [];
909
- let protoFunc, protoName, baseType, baseName = '$undeclared';
1415
+ let protoName, target;
910
1416
  // ident.func()
911
1417
  if (name && name.startsWith('__')) {
912
1418
  const spl = name.slice(2).split('_');
913
1419
 
914
- baseName = spl.slice(0, -1).join('_');
915
- baseType = getType(scope, baseName);
1420
+ protoName = spl[spl.length - 1];
916
1421
 
917
- const func = spl[spl.length - 1];
918
- protoFunc = prototypeFuncs[baseType]?.[func] ?? Object.values(prototypeFuncs).map(x => x[func]).find(x => x);
919
- protoName = func;
1422
+ target = { ...decl.callee };
1423
+ target.name = spl.slice(0, -1).join('_');
920
1424
  }
921
1425
 
922
1426
  // literal.func()
923
1427
  if (!name && decl.callee.type === 'MemberExpression') {
924
- baseType = getNodeType(scope, decl.callee.object);
1428
+ // megahack for /regex/.func()
1429
+ if (decl.callee.object.regex) {
1430
+ const funcName = decl.callee.property.name;
1431
+ const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1432
+
1433
+ funcIndex[func.name] = func.index;
1434
+ funcs.push(func);
1435
+
1436
+ return [
1437
+ // make string arg
1438
+ ...generate(scope, decl.arguments[0]),
925
1439
 
926
- const func = decl.callee.property.name;
927
- protoFunc = prototypeFuncs[baseType]?.[func] ?? Object.values(prototypeFuncs).map(x => x[func]).find(x => x);
928
- protoName = func;
1440
+ // call regex func
1441
+ Opcodes.i32_to_u,
1442
+ [ Opcodes.call, func.index ],
1443
+ Opcodes.i32_from_u,
1444
+
1445
+ ...number(TYPES.boolean, Valtype.i32),
1446
+ setLastType(scope)
1447
+ ];
1448
+ }
929
1449
 
930
- out = generate(scope, decl.callee.object);
931
- out.push([ Opcodes.drop ]);
1450
+ protoName = decl.callee.property.name;
1451
+
1452
+ target = decl.callee.object;
932
1453
  }
933
1454
 
934
- if (protoFunc) {
935
- scope.memory = true;
1455
+ // if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
1456
+ // const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
936
1457
 
937
- let pointer = arrays.get(baseName);
1458
+ // funcIndex[func.name] = func.index;
1459
+ // funcs.push(func);
938
1460
 
939
- if (pointer == null) {
940
- // unknown dynamic pointer, so clone to new pointer which we know aot. now that's what I call inefficient™
941
- if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
1461
+ // return [
1462
+ // generate(scope, decl.callee.object)
942
1463
 
943
- // register array
944
- makeArray(scope, {
945
- rawElements: new Array(0)
946
- }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
947
- pointer = arrays.get(baseName);
1464
+ // // call regex func
1465
+ // [ Opcodes.call, func.index ],
1466
+ // Opcodes.i32_from_u
1467
+ // ];
1468
+ // }
948
1469
 
949
- const [ local, isGlobal ] = lookupName(scope, baseName);
1470
+ if (protoName) {
1471
+ const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1472
+ const f = prototypeFuncs[x][protoName];
1473
+ if (f) acc[x] = f;
1474
+ return acc;
1475
+ }, {});
950
1476
 
951
- out = [
952
- // clone to new pointer
953
- ...number(pointer, Valtype.i32), // dst = new pointer
954
- [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ], // src = unknown pointer
955
- Opcodes.i32_to_u,
956
- ...number(pageSize, Valtype.i32), // size = pagesize
1477
+ // no prototype function candidates, ignore
1478
+ if (Object.keys(protoCands).length > 0) {
1479
+ // use local for cached i32 length as commonly used
1480
+ const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1481
+ const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1482
+ const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
957
1483
 
958
- [ ...Opcodes.memory_copy, 0x00, 0x00 ],
959
- ];
960
- }
1484
+ // TODO: long-term, prototypes should be their individual separate funcs
961
1485
 
962
- if (protoFunc.noArgRetLength && decl.arguments.length === 0) return arrayUtil.getLength(pointer)
1486
+ let lengthI32CacheUsed = false;
1487
+ const protoBC = {};
1488
+ for (const x in protoCands) {
1489
+ const protoFunc = protoCands[x];
1490
+ if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
1491
+ protoBC[x] = [
1492
+ ...RTArrayUtil.getLength(getPointer),
963
1493
 
964
- let protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp`, protoFunc.local) : -1;
1494
+ ...number(TYPES.number, Valtype.i32),
1495
+ setLastType(scope)
1496
+ ];
1497
+ continue;
1498
+ }
965
1499
 
966
- // use local for cached i32 length as commonly used
967
- let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1500
+ // const protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp`, protoFunc.local) : -1;
1501
+ // const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp2`, protoFunc.local2) : -1;
1502
+ const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1503
+ const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1504
+
1505
+ const protoOut = protoFunc(getPointer, {
1506
+ getCachedI32: () => {
1507
+ lengthI32CacheUsed = true;
1508
+ return [ [ Opcodes.local_get, lengthLocal ] ];
1509
+ },
1510
+ setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
1511
+ get: () => RTArrayUtil.getLength(getPointer),
1512
+ getI32: () => RTArrayUtil.getLengthI32(getPointer),
1513
+ set: value => RTArrayUtil.setLength(getPointer, value),
1514
+ setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
1515
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1516
+ return makeArray(scope, {
1517
+ rawElements: new Array(length)
1518
+ }, _global, _name, true, itemType);
1519
+ });
1520
+
1521
+ protoBC[x] = [
1522
+ [ Opcodes.block, valtypeBinary ],
1523
+ ...protoOut,
1524
+
1525
+ ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1526
+ setLastType(scope),
1527
+ [ Opcodes.end ]
1528
+ ];
1529
+ }
968
1530
 
969
- return [
970
- ...out,
971
-
972
- ...arrayUtil.getLengthI32(pointer),
973
- [ Opcodes.local_set, lengthLocal ],
974
-
975
- [ 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
- }),
988
- [ Opcodes.end ]
989
- ];
1531
+ return [
1532
+ ...generate(scope, target),
1533
+
1534
+ Opcodes.i32_to_u,
1535
+ [ Opcodes.local_set, pointerLocal ],
1536
+
1537
+ ...(!lengthI32CacheUsed ? [] : [
1538
+ ...RTArrayUtil.getLengthI32(getPointer),
1539
+ [ Opcodes.local_set, lengthLocal ],
1540
+ ]),
1541
+
1542
+ ...typeSwitch(scope, getNodeType(scope, target), {
1543
+ ...protoBC,
1544
+
1545
+ // TODO: error better
1546
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1547
+ }, valtypeBinary),
1548
+ ];
1549
+ }
990
1550
  }
991
1551
 
992
1552
  // TODO: only allows callee as literal
@@ -1030,34 +1590,50 @@ const generateCall = (scope, decl, _global, _name) => {
1030
1590
 
1031
1591
  const func = funcs.find(x => x.index === idx);
1032
1592
 
1593
+ const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1594
+ const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
1595
+
1033
1596
  let args = decl.arguments;
1034
- if (func && args.length < func.params.length) {
1597
+ if (func && args.length < paramCount) {
1035
1598
  // too little args, push undefineds
1036
- args = args.concat(new Array(func.params.length - args.length).fill(DEFAULT_VALUE));
1599
+ args = args.concat(new Array(paramCount - args.length).fill(DEFAULT_VALUE));
1037
1600
  }
1038
1601
 
1039
- if (func && args.length > func.params.length) {
1602
+ if (func && args.length > paramCount) {
1040
1603
  // too many args, slice extras off
1041
- args = args.slice(0, func.params.length);
1604
+ args = args.slice(0, paramCount);
1042
1605
  }
1043
1606
 
1044
- if (func && func.memory) scope.memory = true;
1045
1607
  if (func && func.throws) scope.throws = true;
1046
1608
 
1609
+ let out = [];
1047
1610
  for (const arg of args) {
1048
- out.push(...generate(scope, arg));
1611
+ out = out.concat(generate(scope, arg));
1612
+ if (userFunc) out = out.concat(getNodeType(scope, arg));
1049
1613
  }
1050
1614
 
1051
1615
  out.push([ Opcodes.call, idx ]);
1052
1616
 
1617
+ if (!userFunc) {
1618
+ // let type;
1619
+ // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1620
+ // if (internalConstrs[name]) type = internalConstrs[name].type;
1621
+ // if (importedFuncs[name] && importedFuncs[]) type =
1622
+
1623
+ // if (type) out.push(
1624
+ // ...number(type, Valtype.i32),
1625
+ // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1626
+ // );
1627
+ } else out.push(setLastType(scope));
1628
+
1053
1629
  return out;
1054
1630
  };
1055
1631
 
1056
1632
  const generateNew = (scope, decl, _global, _name) => {
1057
1633
  // hack: basically treat this as a normal call for builtins for now
1058
1634
  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)})`);
1635
+ if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1636
+ if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1061
1637
 
1062
1638
  return generateCall(scope, decl, _global, _name);
1063
1639
  };
@@ -1073,14 +1649,131 @@ const unhackName = name => {
1073
1649
  return name;
1074
1650
  };
1075
1651
 
1652
+ const knownType = (scope, type) => {
1653
+ if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
1654
+ return type[0][1];
1655
+ }
1656
+
1657
+ if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
1658
+ const idx = type[0][1];
1659
+
1660
+ // type idx = var idx + 1
1661
+ const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
1662
+ if (v.metadata?.type != null) return v.metadata.type;
1663
+ }
1664
+
1665
+ return null;
1666
+ };
1667
+
1668
+ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1669
+ const known = knownType(scope, type);
1670
+ if (known != null) {
1671
+ return bc[known] ?? bc.default;
1672
+ }
1673
+
1674
+ const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1675
+
1676
+ const out = [
1677
+ ...type,
1678
+ [ Opcodes.local_set, tmp ],
1679
+ [ Opcodes.block, returns ]
1680
+ ];
1681
+
1682
+ // todo: use br_table?
1683
+
1684
+ for (const x in bc) {
1685
+ if (x === 'default') continue;
1686
+
1687
+ // if type == x
1688
+ out.push([ Opcodes.local_get, tmp ]);
1689
+ out.push(...number(x, Valtype.i32));
1690
+ out.push([ Opcodes.i32_eq ]);
1691
+
1692
+ out.push([ Opcodes.if, Blocktype.void, `TYPESWITCH|${TYPE_NAMES[x]}` ]);
1693
+ out.push(...bc[x]);
1694
+ out.push([ Opcodes.br, 1 ]);
1695
+ out.push([ Opcodes.end ]);
1696
+ }
1697
+
1698
+ // default
1699
+ if (bc.default) out.push(...bc.default);
1700
+ else if (returns !== Blocktype.void) out.push(...number(0, returns));
1701
+
1702
+ out.push([ Opcodes.end, 'TYPESWITCH_end' ]);
1703
+
1704
+ return out;
1705
+ };
1706
+
1707
+ const allocVar = (scope, name, global = false) => {
1708
+ const target = global ? globals : scope.locals;
1709
+
1710
+ // already declared
1711
+ if (target[name]) {
1712
+ // parser should catch this but sanity check anyway
1713
+ // if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
1714
+
1715
+ return target[name].idx;
1716
+ }
1717
+
1718
+ let idx = global ? globalInd++ : scope.localInd++;
1719
+ target[name] = { idx, type: valtypeBinary };
1720
+
1721
+ let typeIdx = global ? globalInd++ : scope.localInd++;
1722
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
1723
+
1724
+ return idx;
1725
+ };
1726
+
1727
+ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1728
+ const target = global ? globals : scope.locals;
1729
+
1730
+ target[name].metadata ??= {};
1731
+ for (const x in metadata) {
1732
+ if (metadata[x] != null) target[name].metadata[x] = metadata[x];
1733
+ }
1734
+ };
1735
+
1736
+ const typeAnnoToPorfType = x => {
1737
+ if (TYPES[x]) return TYPES[x];
1738
+ if (TYPES['_' + x]) return TYPES['_' + x];
1739
+
1740
+ switch (x) {
1741
+ case 'i32':
1742
+ return TYPES.number;
1743
+ }
1744
+
1745
+ return null;
1746
+ };
1747
+
1748
+ const extractTypeAnnotation = decl => {
1749
+ let a = decl;
1750
+ while (a.typeAnnotation) a = a.typeAnnotation;
1751
+
1752
+ let type, elementType;
1753
+ if (a.typeName) {
1754
+ type = a.typeName.name;
1755
+ } else if (a.type.endsWith('Keyword')) {
1756
+ type = a.type.slice(2, -7).toLowerCase();
1757
+ } else if (a.type === 'TSArrayType') {
1758
+ type = 'array';
1759
+ elementType = extractTypeAnnotation(a.elementType).type;
1760
+ }
1761
+
1762
+ const typeName = type;
1763
+ type = typeAnnoToPorfType(type);
1764
+
1765
+ // if (decl.name) console.log(decl.name, { type, elementType });
1766
+
1767
+ return { type, typeName, elementType };
1768
+ };
1769
+
1076
1770
  const generateVar = (scope, decl) => {
1077
- const out = [];
1771
+ let out = [];
1078
1772
 
1079
1773
  const topLevel = scope.name === 'main';
1080
1774
 
1081
1775
  // global variable if in top scope (main) and var ..., or if wanted
1082
- const global = decl.kind === 'var';
1083
- const target = global ? globals : scope.locals;
1776
+ const global = topLevel || decl._bare; // decl.kind === 'var';
1084
1777
 
1085
1778
  for (const x of decl.declarations) {
1086
1779
  const name = mapName(x.id.name);
@@ -1100,46 +1793,21 @@ const generateVar = (scope, decl) => {
1100
1793
  continue; // always ignore
1101
1794
  }
1102
1795
 
1103
- let idx;
1104
- // already declared
1105
- if (target[name]) {
1106
- // parser should catch this but sanity check anyway
1107
- if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
1796
+ let idx = allocVar(scope, name, global);
1108
1797
 
1109
- idx = target[name].idx;
1110
- } else {
1111
- idx = global ? globalInd++ : scope.localInd++;
1112
- target[name] = { idx, type: valtypeBinary };
1798
+ if (typedInput && x.id.typeAnnotation) {
1799
+ addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1113
1800
  }
1114
1801
 
1115
- typeStates[name] = x.init ? getNodeType(scope, x.init) : TYPES.undefined;
1116
-
1117
- // x.init ??= DEFAULT_VALUE;
1118
1802
  if (x.init) {
1119
- out.push(...generate(scope, x.init, global, name));
1120
-
1121
- // if our value is the result of a function, infer the type from that func's return value
1122
- if (out[out.length - 1][0] === Opcodes.call) {
1123
- const ind = out[out.length - 1][1];
1124
- if (ind >= importedFuncs.length) { // not an imported func
1125
- const func = funcs.find(x => x.index === ind);
1126
- if (!func) throw new Error('could not find func being called as var value to infer type'); // sanity check
1127
-
1128
- const returns = func.returns;
1129
- if (returns.length > 1) throw new Error('func returning >1 value being set as 1 local'); // sanity check
1130
-
1131
- target[name].type = func.returns[0];
1132
- if (target[name].type === Valtype.v128) {
1133
- // specify vec subtype inferred from first vec type in function name
1134
- target[name].vecType = func.name.split('_').find(x => x.includes('x'));
1135
- }
1136
- } else {
1137
- // we do not have imports that return yet, ignore for now
1138
- }
1139
- }
1803
+ out = out.concat(generate(scope, x.init, global, name));
1140
1804
 
1141
1805
  out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
1806
+ out.push(...setType(scope, name, getNodeType(scope, x.init)));
1142
1807
  }
1808
+
1809
+ // hack: this follows spec properly but is mostly unneeded 😅
1810
+ // out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
1143
1811
  }
1144
1812
 
1145
1813
  return out;
@@ -1165,8 +1833,6 @@ const generateAssign = (scope, decl) => {
1165
1833
  const name = decl.left.object.name;
1166
1834
  const pointer = arrays.get(name);
1167
1835
 
1168
- scope.memory = true;
1169
-
1170
1836
  const aotPointer = pointer != null;
1171
1837
 
1172
1838
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
@@ -1177,7 +1843,7 @@ const generateAssign = (scope, decl) => {
1177
1843
  Opcodes.i32_to_u
1178
1844
  ]),
1179
1845
 
1180
- ...generate(scope, decl.right, false, name),
1846
+ ...generate(scope, decl.right),
1181
1847
  [ Opcodes.local_tee, newValueTmp ],
1182
1848
 
1183
1849
  Opcodes.i32_to_u,
@@ -1187,13 +1853,76 @@ const generateAssign = (scope, decl) => {
1187
1853
  ];
1188
1854
  }
1189
1855
 
1856
+ const op = decl.operator.slice(0, -1) || '=';
1857
+
1858
+ // arr[i]
1859
+ if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1860
+ const name = decl.left.object.name;
1861
+ const pointer = arrays.get(name);
1862
+
1863
+ const aotPointer = pointer != null;
1864
+
1865
+ const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1866
+ const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1867
+
1868
+ return [
1869
+ ...typeSwitch(scope, getNodeType(scope, decl.left.object), {
1870
+ [TYPES._array]: [
1871
+ ...(aotPointer ? [] : [
1872
+ ...generate(scope, decl.left.object),
1873
+ Opcodes.i32_to_u
1874
+ ]),
1875
+
1876
+ // get index as valtype
1877
+ ...generate(scope, decl.left.property),
1878
+ Opcodes.i32_to_u,
1879
+
1880
+ // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
1881
+ ...number(ValtypeSize[valtype], Valtype.i32),
1882
+ [ Opcodes.i32_mul ],
1883
+ ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
1884
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1885
+
1886
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
1887
+ [ Opcodes.local_get, pointerTmp ],
1888
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1889
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
1890
+ [ Opcodes.local_tee, newValueTmp ],
1891
+
1892
+ [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1893
+ ],
1894
+
1895
+ default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
1896
+
1897
+ // [TYPES.string]: [
1898
+ // // turn into byte offset by * sizeof i16
1899
+ // ...number(ValtypeSize.i16, Valtype.i32),
1900
+ // [ Opcodes.i32_mul ],
1901
+ // ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
1902
+ // ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1903
+
1904
+ // ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
1905
+ // [ Opcodes.local_get, pointerTmp ],
1906
+ // [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1907
+ // ], generate(scope, decl.right), number(TYPES.string, Valtype.i32), getNodeType(scope, decl.right))),
1908
+ // [ Opcodes.local_tee, newValueTmp ],
1909
+
1910
+ // Opcodes.i32_to_u,
1911
+ // [ StoreOps.i16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1912
+ // ]
1913
+ }, Blocktype.void),
1914
+
1915
+ [ Opcodes.local_get, newValueTmp ]
1916
+ ];
1917
+ }
1918
+
1190
1919
  const [ local, isGlobal ] = lookupName(scope, name);
1191
1920
 
1192
1921
  if (local === undefined) {
1193
- // todo: this should be a devtools/repl/??? only thing
1922
+ // todo: this should be a sloppy mode only thing
1194
1923
 
1195
1924
  // only allow = for this
1196
- if (decl.operator !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1925
+ if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1197
1926
 
1198
1927
  if (builtinVars[name]) {
1199
1928
  // just return rhs (eg `NaN = 2`)
@@ -1202,25 +1931,53 @@ const generateAssign = (scope, decl) => {
1202
1931
 
1203
1932
  // set global and return (eg a = 2)
1204
1933
  return [
1205
- ...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
1934
+ ...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
1206
1935
  [ Opcodes.global_get, globals[name].idx ]
1207
1936
  ];
1208
1937
  }
1209
1938
 
1210
- if (decl.operator === '=') {
1211
- typeStates[name] = getNodeType(scope, decl.right);
1212
-
1939
+ if (op === '=') {
1213
1940
  return [
1214
1941
  ...generate(scope, decl.right, isGlobal, name),
1215
1942
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1216
- [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1943
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1944
+
1945
+ ...setType(scope, name, getNodeType(scope, decl.right))
1946
+ ];
1947
+ }
1948
+
1949
+ if (op === '||' || op === '&&' || op === '??') {
1950
+ // todo: is this needed?
1951
+ // for logical assignment ops, it is not left @= right ~= left = left @ right
1952
+ // instead, left @ (left = right)
1953
+ // eg, x &&= y ~= x && (x = y)
1954
+
1955
+ return [
1956
+ ...performOp(scope, op, [
1957
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1958
+ ], [
1959
+ ...generate(scope, decl.right),
1960
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1961
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1962
+ ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1963
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1964
+
1965
+ getLastType(scope),
1966
+ // hack: type is idx+1
1967
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
1217
1968
  ];
1218
1969
  }
1219
1970
 
1220
1971
  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),
1972
+ ...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
1973
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1223
- [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1974
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1975
+
1976
+ // todo: string concat types
1977
+
1978
+ // hack: type is idx+1
1979
+ ...number(TYPES.number, Valtype.i32),
1980
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
1224
1981
  ];
1225
1982
  };
1226
1983
 
@@ -1245,13 +2002,14 @@ const generateUnary = (scope, decl) => {
1245
2002
 
1246
2003
  case '!':
1247
2004
  // !=
1248
- return falsy(scope, generate(scope, decl.argument));
2005
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
1249
2006
 
1250
2007
  case '~':
2008
+ // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
1251
2009
  return [
1252
2010
  ...generate(scope, decl.argument),
1253
2011
  Opcodes.i32_to,
1254
- [ Opcodes.i32_const, signedLEB128(-1) ],
2012
+ [ Opcodes.i32_const, ...signedLEB128(-1) ],
1255
2013
  [ Opcodes.i32_xor ],
1256
2014
  Opcodes.i32_from
1257
2015
  ];
@@ -1289,11 +2047,16 @@ const generateUnary = (scope, decl) => {
1289
2047
  return out;
1290
2048
 
1291
2049
  case 'typeof':
1292
- const type = getNodeType(scope, decl.argument);
2050
+ return typeSwitch(scope, getNodeType(scope, decl.argument), {
2051
+ [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2052
+ [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2053
+ [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2054
+ [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2055
+ [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
1293
2056
 
1294
- // for custom types, just return object
1295
- if (type > 0xffffffffffff7) return number(TYPES.object);
1296
- return number(type);
2057
+ // object and internal types
2058
+ default: makeString(scope, 'object', false, '#typeof_result'),
2059
+ });
1297
2060
 
1298
2061
  default:
1299
2062
  return todo(`unary operator ${decl.operator} not implemented yet`);
@@ -1332,9 +2095,9 @@ const generateUpdate = (scope, decl) => {
1332
2095
  };
1333
2096
 
1334
2097
  const generateIf = (scope, decl) => {
1335
- const out = truthy(scope, generate(scope, decl.test), decl.test);
2098
+ const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test), false, true);
1336
2099
 
1337
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2100
+ out.push([ Opcodes.if, Blocktype.void ]);
1338
2101
  depth.push('if');
1339
2102
 
1340
2103
  const consOut = generate(scope, decl.consequent);
@@ -1356,16 +2119,28 @@ const generateIf = (scope, decl) => {
1356
2119
  };
1357
2120
 
1358
2121
  const generateConditional = (scope, decl) => {
1359
- const out = [ ...generate(scope, decl.test) ];
2122
+ const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test), false, true);
1360
2123
 
1361
- out.push(Opcodes.i32_to, [ Opcodes.if, valtypeBinary ]);
2124
+ out.push([ Opcodes.if, valtypeBinary ]);
1362
2125
  depth.push('if');
1363
2126
 
1364
2127
  out.push(...generate(scope, decl.consequent));
1365
2128
 
2129
+ // note type
2130
+ out.push(
2131
+ ...getNodeType(scope, decl.consequent),
2132
+ setLastType(scope)
2133
+ );
2134
+
1366
2135
  out.push([ Opcodes.else ]);
1367
2136
  out.push(...generate(scope, decl.alternate));
1368
2137
 
2138
+ // note type
2139
+ out.push(
2140
+ ...getNodeType(scope, decl.alternate),
2141
+ setLastType(scope)
2142
+ );
2143
+
1369
2144
  out.push([ Opcodes.end ]);
1370
2145
  depth.pop();
1371
2146
 
@@ -1422,9 +2197,148 @@ const generateWhile = (scope, decl) => {
1422
2197
  return out;
1423
2198
  };
1424
2199
 
2200
+ const generateForOf = (scope, decl) => {
2201
+ const out = [];
2202
+
2203
+ // todo: for of inside for of might fuck up?
2204
+ const pointer = localTmp(scope, 'forof_base_pointer', Valtype.i32);
2205
+ const length = localTmp(scope, 'forof_length', Valtype.i32);
2206
+ const counter = localTmp(scope, 'forof_counter', Valtype.i32);
2207
+
2208
+ out.push(
2209
+ // set pointer as right
2210
+ ...generate(scope, decl.right),
2211
+ Opcodes.i32_to_u,
2212
+ [ Opcodes.local_set, pointer ],
2213
+
2214
+ // set counter as 0 (could be already used)
2215
+ ...number(0, Valtype.i32),
2216
+ [ Opcodes.local_set, counter ],
2217
+
2218
+ // get length
2219
+ [ Opcodes.local_get, pointer ],
2220
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
2221
+ [ Opcodes.local_set, length ]
2222
+ );
2223
+
2224
+ depth.push('forof');
2225
+
2226
+ // setup local for left
2227
+ generate(scope, decl.left);
2228
+
2229
+ const leftName = decl.left.declarations[0].id.name;
2230
+ const [ local, isGlobal ] = lookupName(scope, leftName);
2231
+
2232
+ depth.push('block');
2233
+ depth.push('block');
2234
+
2235
+ // // todo: we should only do this for strings but we don't know at compile-time :(
2236
+ // hack: this is naughty and will break things!
2237
+ let newOut = number(0, Valtype.f64), newPointer = -1;
2238
+ if (pages.hasString) {
2239
+ 0, [ newOut, newPointer ] = makeArray(scope, {
2240
+ rawElements: new Array(1)
2241
+ }, isGlobal, leftName, true, 'i16');
2242
+ }
2243
+
2244
+ // set type for local
2245
+ out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2246
+ [TYPES._array]: [
2247
+ ...setType(scope, leftName, TYPES.number),
2248
+
2249
+ [ Opcodes.loop, Blocktype.void ],
2250
+
2251
+ [ Opcodes.local_get, pointer ],
2252
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2253
+
2254
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2255
+
2256
+ [ Opcodes.block, Blocktype.void ],
2257
+ [ Opcodes.block, Blocktype.void ],
2258
+ ...generate(scope, decl.body),
2259
+ [ Opcodes.end ],
2260
+
2261
+ // increment iter pointer by valtype size
2262
+ [ Opcodes.local_get, pointer ],
2263
+ ...number(ValtypeSize[valtype], Valtype.i32),
2264
+ [ Opcodes.i32_add ],
2265
+ [ Opcodes.local_set, pointer ],
2266
+
2267
+ // increment counter by 1
2268
+ [ Opcodes.local_get, counter ],
2269
+ ...number(1, Valtype.i32),
2270
+ [ Opcodes.i32_add ],
2271
+ [ Opcodes.local_tee, counter ],
2272
+
2273
+ // loop if counter != length
2274
+ [ Opcodes.local_get, length ],
2275
+ [ Opcodes.i32_ne ],
2276
+ [ Opcodes.br_if, 1 ],
2277
+
2278
+ [ Opcodes.end ],
2279
+ [ Opcodes.end ]
2280
+ ],
2281
+ [TYPES.string]: [
2282
+ ...setType(scope, leftName, TYPES.string),
2283
+
2284
+ [ Opcodes.loop, Blocktype.void ],
2285
+
2286
+ // setup new/out array
2287
+ ...newOut,
2288
+ [ Opcodes.drop ],
2289
+
2290
+ ...number(0, Valtype.i32), // base 0 for store after
2291
+
2292
+ // load current string ind {arg}
2293
+ [ Opcodes.local_get, pointer ],
2294
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2295
+
2296
+ // store to new string ind 0
2297
+ [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2298
+
2299
+ // return new string (page)
2300
+ ...number(newPointer),
2301
+
2302
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2303
+
2304
+ [ Opcodes.block, Blocktype.void ],
2305
+ [ Opcodes.block, Blocktype.void ],
2306
+ ...generate(scope, decl.body),
2307
+ [ Opcodes.end ],
2308
+
2309
+ // increment iter pointer by valtype size
2310
+ [ Opcodes.local_get, pointer ],
2311
+ ...number(ValtypeSize.i16, Valtype.i32),
2312
+ [ Opcodes.i32_add ],
2313
+ [ Opcodes.local_set, pointer ],
2314
+
2315
+ // increment counter by 1
2316
+ [ Opcodes.local_get, counter ],
2317
+ ...number(1, Valtype.i32),
2318
+ [ Opcodes.i32_add ],
2319
+ [ Opcodes.local_tee, counter ],
2320
+
2321
+ // loop if counter != length
2322
+ [ Opcodes.local_get, length ],
2323
+ [ Opcodes.i32_ne ],
2324
+ [ Opcodes.br_if, 1 ],
2325
+
2326
+ [ Opcodes.end ],
2327
+ [ Opcodes.end ]
2328
+ ],
2329
+ default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2330
+ }, Blocktype.void));
2331
+
2332
+ depth.pop();
2333
+ depth.pop();
2334
+ depth.pop();
2335
+
2336
+ return out;
2337
+ };
2338
+
1425
2339
  const getNearestLoop = () => {
1426
2340
  for (let i = depth.length - 1; i >= 0; i--) {
1427
- if (depth[i] === 'while' || depth[i] === 'for') return i;
2341
+ if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
1428
2342
  }
1429
2343
 
1430
2344
  return -1;
@@ -1508,13 +2422,25 @@ const generateAssignPat = (scope, decl) => {
1508
2422
  };
1509
2423
 
1510
2424
  let pages = new Map();
1511
- const allocPage = reason => {
1512
- if (pages.has(reason)) return pages.get(reason);
2425
+ const allocPage = (reason, type) => {
2426
+ if (pages.has(reason)) return pages.get(reason).ind;
2427
+
2428
+ if (reason.startsWith('array:')) pages.hasArray = true;
2429
+ if (reason.startsWith('string:')) pages.hasString = true;
2430
+
2431
+ const ind = pages.size;
2432
+ pages.set(reason, { ind, type });
2433
+
2434
+ if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2435
+
2436
+ return ind;
2437
+ };
1513
2438
 
1514
- let ind = pages.size;
1515
- pages.set(reason, ind);
2439
+ const freePage = reason => {
2440
+ const { ind } = pages.get(reason);
2441
+ pages.delete(reason);
1516
2442
 
1517
- if (codeLog) log('codegen', `allocated new page of memory (${ind}) | ${reason}`);
2443
+ if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
1518
2444
 
1519
2445
  return ind;
1520
2446
  };
@@ -1528,7 +2454,7 @@ const itemTypeToValtype = {
1528
2454
  i16: 'i32'
1529
2455
  };
1530
2456
 
1531
- const storeOps = {
2457
+ const StoreOps = {
1532
2458
  i32: Opcodes.i32_store,
1533
2459
  i64: Opcodes.i64_store,
1534
2460
  f64: Opcodes.f64_store,
@@ -1537,13 +2463,32 @@ const storeOps = {
1537
2463
  i16: Opcodes.i32_store16
1538
2464
  };
1539
2465
 
2466
+ let data = [];
2467
+
2468
+ const compileBytes = (val, itemType, signed = true) => {
2469
+ // todo: this is a mess and needs confirming / ????
2470
+ switch (itemType) {
2471
+ case 'i8': return [ val % 256 ];
2472
+ case 'i16': return [ val % 256, Math.floor(val / 256) ];
2473
+
2474
+ case 'i32':
2475
+ case 'i64':
2476
+ return enforceFourBytes(signedLEB128(val));
2477
+
2478
+ case 'f64': return ieee754_binary64(val);
2479
+ }
2480
+ };
2481
+
1540
2482
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
1541
2483
  const out = [];
1542
2484
 
2485
+ let firstAssign = false;
1543
2486
  if (!arrays.has(name) || name === '$undeclared') {
2487
+ firstAssign = true;
2488
+
1544
2489
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1545
2490
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1546
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`) * pageSize);
2491
+ arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
1547
2492
  }
1548
2493
 
1549
2494
  const pointer = arrays.get(name);
@@ -1551,8 +2496,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1551
2496
  const useRawElements = !!decl.rawElements;
1552
2497
  const elements = useRawElements ? decl.rawElements : decl.elements;
1553
2498
 
2499
+ const valtype = itemTypeToValtype[itemType];
1554
2500
  const length = elements.length;
1555
2501
 
2502
+ if (firstAssign && useRawElements) {
2503
+ let bytes = compileBytes(length, 'i32');
2504
+
2505
+ if (!initEmpty) for (let i = 0; i < length; i++) {
2506
+ if (elements[i] == null) continue;
2507
+
2508
+ bytes.push(...compileBytes(elements[i], itemType));
2509
+ }
2510
+
2511
+ data.push({
2512
+ offset: pointer,
2513
+ bytes
2514
+ });
2515
+
2516
+ // local value as pointer
2517
+ out.push(...number(pointer));
2518
+
2519
+ return [ out, pointer ];
2520
+ }
2521
+
1556
2522
  // store length as 0th array
1557
2523
  out.push(
1558
2524
  ...number(0, Valtype.i32),
@@ -1560,8 +2526,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1560
2526
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
1561
2527
  );
1562
2528
 
1563
- const storeOp = storeOps[itemType];
1564
- const valtype = itemTypeToValtype[itemType];
2529
+ const storeOp = StoreOps[itemType];
1565
2530
 
1566
2531
  if (!initEmpty) for (let i = 0; i < length; i++) {
1567
2532
  if (elements[i] == null) continue;
@@ -1576,30 +2541,34 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1576
2541
  // local value as pointer
1577
2542
  out.push(...number(pointer));
1578
2543
 
1579
- scope.memory = true;
2544
+ return [ out, pointer ];
2545
+ };
1580
2546
 
1581
- return out;
2547
+ const makeString = (scope, str, global = false, name = '$undeclared') => {
2548
+ const rawElements = new Array(str.length);
2549
+ for (let i = 0; i < str.length; i++) {
2550
+ rawElements[i] = str.charCodeAt(i);
2551
+ }
2552
+
2553
+ return makeArray(scope, {
2554
+ rawElements
2555
+ }, global, name, false, 'i16')[0];
1582
2556
  };
1583
2557
 
1584
2558
  let arrays = new Map();
1585
2559
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
1586
- return makeArray(scope, decl, global, name, initEmpty, valtype);
2560
+ return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
1587
2561
  };
1588
2562
 
1589
2563
  export const generateMember = (scope, decl, _global, _name) => {
1590
- const type = getNodeType(scope, decl.object);
2564
+ const name = decl.object.name;
2565
+ const pointer = arrays.get(name);
2566
+
2567
+ const aotPointer = pointer != null;
1591
2568
 
1592
2569
  // hack: .length
1593
2570
  if (decl.property.name === 'length') {
1594
2571
  // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
1595
-
1596
- const name = decl.object.name;
1597
- const pointer = arrays.get(name);
1598
-
1599
- scope.memory = true;
1600
-
1601
- const aotPointer = pointer != null;
1602
-
1603
2572
  return [
1604
2573
  ...(aotPointer ? number(0, Valtype.i32) : [
1605
2574
  ...generate(scope, decl.object),
@@ -1611,18 +2580,17 @@ export const generateMember = (scope, decl, _global, _name) => {
1611
2580
  ];
1612
2581
  }
1613
2582
 
1614
- // this is just for arr[ind] for now. objects are partially supported via object hack (a.b -> __a_b)
1615
- if (![TYPES._array, TYPES.string].includes(type)) return todo(`computed member expression for objects are not supported yet`);
1616
-
1617
- const name = decl.object.name;
1618
- const pointer = arrays.get(name);
1619
-
1620
- scope.memory = true;
1621
-
1622
- const aotPointer = pointer != null;
2583
+ // // todo: we should only do this for strings but we don't know at compile-time :(
2584
+ // hack: this is naughty and will break things!
2585
+ let newOut = number(0, valtypeBinary), newPointer = -1;
2586
+ if (pages.hasString) {
2587
+ 0, [ newOut, newPointer ] = makeArray(scope, {
2588
+ rawElements: new Array(1)
2589
+ }, _global, _name, true, 'i16');
2590
+ }
1623
2591
 
1624
- if (type === TYPES._array) {
1625
- return [
2592
+ return typeSwitch(scope, getNodeType(scope, decl.object), {
2593
+ [TYPES._array]: [
1626
2594
  // get index as valtype
1627
2595
  ...generate(scope, decl.property),
1628
2596
 
@@ -1638,45 +2606,46 @@ export const generateMember = (scope, decl, _global, _name) => {
1638
2606
  ]),
1639
2607
 
1640
2608
  // read from memory
1641
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1642
- ];
1643
- }
2609
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
1644
2610
 
1645
- // string
2611
+ ...number(TYPES.number, Valtype.i32),
2612
+ setLastType(scope)
2613
+ ],
1646
2614
 
1647
- const newOut = makeArray(scope, {
1648
- rawElements: new Array(1)
1649
- }, _global, _name, true, 'i16');
1650
- const newPointer = arrays.get(_name ?? '$undeclared');
2615
+ [TYPES.string]: [
2616
+ // setup new/out array
2617
+ ...newOut,
2618
+ [ Opcodes.drop ],
1651
2619
 
1652
- return [
1653
- // setup new/out array
1654
- ...newOut,
1655
- [ Opcodes.drop ],
2620
+ ...number(0, Valtype.i32), // base 0 for store later
1656
2621
 
1657
- ...number(0, Valtype.i32), // base 0 for store later
2622
+ ...generate(scope, decl.property),
1658
2623
 
1659
- ...generate(scope, decl.property),
2624
+ Opcodes.i32_to_u,
2625
+ ...number(ValtypeSize.i16, Valtype.i32),
2626
+ [ Opcodes.i32_mul ],
1660
2627
 
1661
- Opcodes.i32_to_u,
1662
- ...number(ValtypeSize.i16, Valtype.i32),
1663
- [ Opcodes.i32_mul ],
2628
+ ...(aotPointer ? [] : [
2629
+ ...generate(scope, decl.object),
2630
+ Opcodes.i32_to_u,
2631
+ [ Opcodes.i32_add ]
2632
+ ]),
1664
2633
 
1665
- ...(aotPointer ? [] : [
1666
- ...generate(scope, decl.object),
1667
- Opcodes.i32_to_u,
1668
- [ Opcodes.i32_add ]
1669
- ]),
2634
+ // load current string ind {arg}
2635
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2636
+
2637
+ // store to new string ind 0
2638
+ [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
1670
2639
 
1671
- // load current string ind {arg}
1672
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2640
+ // return new string (page)
2641
+ ...number(newPointer),
1673
2642
 
1674
- // store to new string ind 0
1675
- [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2643
+ ...number(TYPES.string, Valtype.i32),
2644
+ setLastType(scope)
2645
+ ],
1676
2646
 
1677
- // return new string (page)
1678
- ...number(newPointer)
1679
- ];
2647
+ default: [ [ Opcodes.unreachable ] ]
2648
+ });
1680
2649
  };
1681
2650
 
1682
2651
  const randId = () => Math.random().toString(16).slice(0, -4);
@@ -1721,22 +2690,25 @@ const generateFunc = (scope, decl) => {
1721
2690
  if (decl.generator) return todo('generator functions are not supported');
1722
2691
 
1723
2692
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
1724
- const params = decl.params?.map(x => x.name) ?? [];
2693
+ const params = decl.params ?? [];
1725
2694
 
1726
2695
  // const innerScope = { ...scope };
1727
2696
  // TODO: share scope/locals between !!!
1728
2697
  const innerScope = {
1729
2698
  locals: {},
1730
2699
  localInd: 0,
1731
- returns: [ valtypeBinary ],
1732
- memory: false,
2700
+ // value, type
2701
+ returns: [ valtypeBinary, Valtype.i32 ],
1733
2702
  throws: false,
1734
2703
  name
1735
2704
  };
1736
2705
 
1737
2706
  for (let i = 0; i < params.length; i++) {
1738
- const param = params[i];
1739
- innerScope.locals[param] = { idx: innerScope.localInd++, type: valtypeBinary };
2707
+ allocVar(innerScope, params[i].name, false);
2708
+
2709
+ if (typedInput && params[i].typeAnnotation) {
2710
+ addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
2711
+ }
1740
2712
  }
1741
2713
 
1742
2714
  let body = objectHack(decl.body);
@@ -1751,11 +2723,9 @@ const generateFunc = (scope, decl) => {
1751
2723
  const wasm = generate(innerScope, body);
1752
2724
  const func = {
1753
2725
  name,
1754
- params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
2726
+ params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
1755
2727
  returns: innerScope.returns,
1756
- returnType: innerScope.returnType ?? TYPES.number,
1757
2728
  locals: innerScope.locals,
1758
- memory: innerScope.memory,
1759
2729
  throws: innerScope.throws,
1760
2730
  index: currentFuncIndex++
1761
2731
  };
@@ -1768,121 +2738,13 @@ const generateFunc = (scope, decl) => {
1768
2738
  }
1769
2739
  }
1770
2740
 
1771
- if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
1772
- wasm.push(...number(0), [ Opcodes.return ]);
1773
- }
1774
-
1775
- // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
1776
- let offset = 0, vecParams = 0;
1777
- for (let i = 0; i < params.length; i++) {
1778
- const name = params[i];
1779
- const local = func.locals[name];
1780
- if (local.type === Valtype.v128) {
1781
- vecParams++;
1782
-
1783
- /* func.memory = true; // mark func as using memory
1784
-
1785
- wasm.unshift( // add v128 load for param
1786
- [ Opcodes.i32_const, 0 ],
1787
- [ ...Opcodes.v128_load, 0, i * 16 ],
1788
- [ Opcodes.local_set, local.idx ]
1789
- ); */
1790
-
1791
- // using params and replace_lane is noticably faster than just loading from memory (above) somehow
1792
-
1793
- // extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
1794
- const { vecType } = local;
1795
- let [ type, lanes ] = vecType.split('x');
1796
- if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
1797
-
1798
- lanes = parseInt(lanes);
1799
- type = Valtype[type];
1800
-
1801
- const name = params[i]; // get original param name
1802
-
1803
- func.params.splice(offset, 1, ...new Array(lanes).fill(type)); // add new params of {type}, {lanes} times
1804
-
1805
- // update index of original local
1806
- // delete func.locals[name];
1807
-
1808
- // add new locals for params
1809
- for (let j = 0; j < lanes; j++) {
1810
- func.locals[name + j] = { idx: offset + j, type, vecParamAutogen: true };
1811
- }
1812
-
1813
- // prepend wasm to generate expected v128 locals
1814
- wasm.splice(i * 2 + offset * 2, 0,
1815
- ...i32x4(0, 0, 0, 0),
1816
- ...new Array(lanes).fill(0).flatMap((_, j) => [
1817
- [ Opcodes.local_get, offset + j ],
1818
- [ ...Opcodes[vecType + '_replace_lane'], j ]
1819
- ]),
1820
- [ Opcodes.local_set, i ]
1821
- );
1822
-
1823
- offset += lanes;
1824
-
1825
- // note: wrapping is disabled for now due to perf/dx concerns (so this will never run)
1826
- /* if (!func.name.startsWith('#')) func.name = '##' + func.name;
1827
-
1828
- // add vec type index to hash name prefix for wrapper to know how to wrap
1829
- const vecTypeIdx = [ 'i8x16', 'i16x8', 'i32x4', 'i64x2', 'f32x4', 'f64x2' ].indexOf(local.vecType);
1830
- const secondHash = func.name.slice(1).indexOf('#');
1831
- func.name = '#' + func.name.slice(1, secondHash) + vecTypeIdx + func.name.slice(secondHash); */
1832
- }
1833
- }
1834
-
1835
- if (offset !== 0) {
1836
- // bump local indexes for all other locals after
1837
- for (const x in func.locals) {
1838
- const local = func.locals[x];
1839
- if (!local.vecParamAutogen) local.idx += offset;
1840
- }
1841
-
1842
- // bump local indexes in wasm local.get/set
1843
- for (let j = 0; j < wasm.length; j++) {
1844
- const inst = wasm[j];
1845
- if (j < offset * 2 + vecParams * 2) {
1846
- if (inst[0] === Opcodes.local_set) inst[1] += offset;
1847
- continue;
1848
- }
1849
-
1850
- if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set) inst[1] += offset;
1851
- }
1852
- }
1853
-
1854
- // change v128 return into many <type> instead as unsupported return valtype
1855
- const lastReturnLocal = wasm.length > 2 && wasm[wasm.length - 1][0] === Opcodes.return && Object.values(func.locals).find(x => x.idx === wasm[wasm.length - 2][1]);
1856
- if (lastReturnLocal && lastReturnLocal.type === Valtype.v128) {
1857
- const name = Object.keys(func.locals)[Object.values(func.locals).indexOf(lastReturnLocal)];
1858
- // extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
1859
- const { vecType } = lastReturnLocal;
1860
- let [ type, lanes ] = vecType.split('x');
1861
- if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
1862
-
1863
- lanes = parseInt(lanes);
1864
- type = Valtype[type];
1865
-
1866
- const vecIdx = lastReturnLocal.idx;
1867
-
1868
- const lastIdx = Math.max(0, ...Object.values(func.locals).map(x => x.idx));
1869
- const tmpIdx = [];
1870
- for (let i = 0; i < lanes; i++) {
1871
- const idx = lastIdx + i + 1;
1872
- tmpIdx.push(idx);
1873
- func.locals[name + i] = { idx, type, vecReturnAutogen: true };
1874
- }
1875
-
1876
- wasm.splice(wasm.length - 1, 1,
1877
- ...new Array(lanes).fill(0).flatMap((_, i) => [
1878
- i === 0 ? null : [ Opcodes.local_get, vecIdx ],
1879
- [ ...Opcodes[vecType + '_extract_lane'], i ],
1880
- [ Opcodes.local_set, tmpIdx[i] ],
1881
- ].filter(x => x !== null)),
1882
- ...new Array(lanes).fill(0).map((_, i) => [ Opcodes.local_get, tmpIdx[i]])
2741
+ // add end return if not found
2742
+ if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
2743
+ wasm.push(
2744
+ ...number(0),
2745
+ ...number(TYPES.undefined, Valtype.i32),
2746
+ [ Opcodes.return ]
1883
2747
  );
1884
-
1885
- func.returns = new Array(lanes).fill(type);
1886
2748
  }
1887
2749
 
1888
2750
  func.wasm = wasm;
@@ -1893,10 +2755,10 @@ const generateFunc = (scope, decl) => {
1893
2755
  };
1894
2756
 
1895
2757
  const generateCode = (scope, decl) => {
1896
- const out = [];
2758
+ let out = [];
1897
2759
 
1898
2760
  for (const x of decl.body) {
1899
- out.push(...generate(scope, x));
2761
+ out = out.concat(generate(scope, x));
1900
2762
  }
1901
2763
 
1902
2764
  return out;
@@ -1912,10 +2774,9 @@ const internalConstrs = {
1912
2774
 
1913
2775
  // new Array(n)
1914
2776
 
1915
- makeArray(scope, {
2777
+ const [ , pointer ] = makeArray(scope, {
1916
2778
  rawElements: new Array(0)
1917
2779
  }, global, name, true);
1918
- const pointer = arrays.get(name ?? '$undeclared');
1919
2780
 
1920
2781
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
1921
2782
 
@@ -1933,9 +2794,38 @@ const internalConstrs = {
1933
2794
  ];
1934
2795
  },
1935
2796
  type: TYPES._array
2797
+ },
2798
+
2799
+ __Array_of: {
2800
+ // this is not a constructor but best fits internal structure here
2801
+ generate: (scope, decl, global, name) => {
2802
+ // Array.of(i0, i1, ...)
2803
+ return generateArray(scope, {
2804
+ elements: decl.arguments
2805
+ }, global, name);
2806
+ },
2807
+ type: TYPES._array,
2808
+ notConstr: true
1936
2809
  }
1937
2810
  };
1938
2811
 
2812
+ // const _ = Array.prototype.push;
2813
+ // Array.prototype.push = function (a) {
2814
+ // const check = arr => {
2815
+ // for (const x of arr) {
2816
+ // if (x === undefined) {
2817
+ // console.trace(arr);
2818
+ // process.exit();
2819
+ // }
2820
+ // if (Array.isArray(x)) check(x);
2821
+ // }
2822
+ // };
2823
+ // if (Array.isArray(a) && !new Error().stack.includes('node:')) check(a);
2824
+ // // if (Array.isArray(a)) check(a);
2825
+
2826
+ // return _.apply(this, arguments);
2827
+ // };
2828
+
1939
2829
  export default program => {
1940
2830
  globals = {};
1941
2831
  globalInd = 0;
@@ -1944,9 +2834,9 @@ export default program => {
1944
2834
  funcs = [];
1945
2835
  funcIndex = {};
1946
2836
  depth = [];
1947
- typeStates = {};
1948
2837
  arrays = new Map();
1949
2838
  pages = new Map();
2839
+ data = [];
1950
2840
  currentFuncIndex = importedFuncs.length;
1951
2841
 
1952
2842
  globalThis.valtype = 'f64';
@@ -1996,18 +2886,20 @@ export default program => {
1996
2886
  body: program.body
1997
2887
  };
1998
2888
 
2889
+ if (process.argv.includes('-ast-log')) console.log(program.body.body);
2890
+
1999
2891
  generateFunc(scope, program);
2000
2892
 
2001
2893
  const main = funcs[funcs.length - 1];
2002
2894
  main.export = true;
2003
- main.returns = [ valtypeBinary ];
2895
+ main.returns = [ valtypeBinary, Valtype.i32 ];
2004
2896
 
2005
2897
  const lastInst = main.wasm[main.wasm.length - 1] ?? [ Opcodes.end ];
2006
2898
  if (lastInst[0] === Opcodes.drop) {
2007
2899
  main.wasm.splice(main.wasm.length - 1, 1);
2008
2900
 
2009
2901
  const finalStatement = program.body.body[program.body.body.length - 1];
2010
- main.returnType = getNodeType(main, finalStatement);
2902
+ main.wasm.push(...getNodeType(main, finalStatement));
2011
2903
  }
2012
2904
 
2013
2905
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
@@ -2023,5 +2915,5 @@ export default program => {
2023
2915
  // if blank main func and other exports, remove it
2024
2916
  if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
2025
2917
 
2026
- return { funcs, globals, tags, exceptions, pages };
2918
+ return { funcs, globals, tags, exceptions, pages, data };
2027
2919
  };