porffor 0.1.1 → 0.2.0-09999e8

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,130 @@ 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
+ type = typeAnnoToPorfType(type);
1763
+
1764
+ // if (decl.name) console.log(decl.name, { type, elementType });
1765
+
1766
+ return { type, elementType };
1767
+ };
1768
+
1076
1769
  const generateVar = (scope, decl) => {
1077
- const out = [];
1770
+ let out = [];
1078
1771
 
1079
1772
  const topLevel = scope.name === 'main';
1080
1773
 
1081
1774
  // 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;
1775
+ const global = topLevel || decl._bare; // decl.kind === 'var';
1084
1776
 
1085
1777
  for (const x of decl.declarations) {
1086
1778
  const name = mapName(x.id.name);
@@ -1100,45 +1792,19 @@ const generateVar = (scope, decl) => {
1100
1792
  continue; // always ignore
1101
1793
  }
1102
1794
 
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`);
1795
+ let idx = allocVar(scope, name, global);
1796
+ if (x.init) {
1797
+ out = out.concat(generate(scope, x.init, global, name));
1108
1798
 
1109
- idx = target[name].idx;
1110
- } else {
1111
- idx = global ? globalInd++ : scope.localInd++;
1112
- target[name] = { idx, type: valtypeBinary };
1799
+ out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
1800
+ out.push(...setType(scope, name, getNodeType(scope, x.init)));
1113
1801
  }
1114
1802
 
1115
- typeStates[name] = x.init ? getNodeType(scope, x.init) : TYPES.undefined;
1116
-
1117
- // x.init ??= DEFAULT_VALUE;
1118
- 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
+ // hack: this follows spec properly but is mostly unneeded 😅
1804
+ // out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
1140
1805
 
1141
- out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
1806
+ if (typedInput && x.id.typeAnnotation) {
1807
+ addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1142
1808
  }
1143
1809
  }
1144
1810
 
@@ -1165,8 +1831,6 @@ const generateAssign = (scope, decl) => {
1165
1831
  const name = decl.left.object.name;
1166
1832
  const pointer = arrays.get(name);
1167
1833
 
1168
- scope.memory = true;
1169
-
1170
1834
  const aotPointer = pointer != null;
1171
1835
 
1172
1836
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
@@ -1177,7 +1841,7 @@ const generateAssign = (scope, decl) => {
1177
1841
  Opcodes.i32_to_u
1178
1842
  ]),
1179
1843
 
1180
- ...generate(scope, decl.right, false, name),
1844
+ ...generate(scope, decl.right),
1181
1845
  [ Opcodes.local_tee, newValueTmp ],
1182
1846
 
1183
1847
  Opcodes.i32_to_u,
@@ -1187,13 +1851,76 @@ const generateAssign = (scope, decl) => {
1187
1851
  ];
1188
1852
  }
1189
1853
 
1854
+ const op = decl.operator.slice(0, -1) || '=';
1855
+
1856
+ // arr[i]
1857
+ if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1858
+ const name = decl.left.object.name;
1859
+ const pointer = arrays.get(name);
1860
+
1861
+ const aotPointer = pointer != null;
1862
+
1863
+ const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1864
+ const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1865
+
1866
+ return [
1867
+ ...typeSwitch(scope, getNodeType(scope, decl.left.object), {
1868
+ [TYPES._array]: [
1869
+ ...(aotPointer ? [] : [
1870
+ ...generate(scope, decl.left.object),
1871
+ Opcodes.i32_to_u
1872
+ ]),
1873
+
1874
+ // get index as valtype
1875
+ ...generate(scope, decl.left.property),
1876
+ Opcodes.i32_to_u,
1877
+
1878
+ // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
1879
+ ...number(ValtypeSize[valtype], Valtype.i32),
1880
+ [ Opcodes.i32_mul ],
1881
+ ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
1882
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1883
+
1884
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
1885
+ [ Opcodes.local_get, pointerTmp ],
1886
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1887
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
1888
+ [ Opcodes.local_tee, newValueTmp ],
1889
+
1890
+ [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1891
+ ],
1892
+
1893
+ default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
1894
+
1895
+ // [TYPES.string]: [
1896
+ // // turn into byte offset by * sizeof i16
1897
+ // ...number(ValtypeSize.i16, Valtype.i32),
1898
+ // [ Opcodes.i32_mul ],
1899
+ // ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
1900
+ // ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1901
+
1902
+ // ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
1903
+ // [ Opcodes.local_get, pointerTmp ],
1904
+ // [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1905
+ // ], generate(scope, decl.right), number(TYPES.string, Valtype.i32), getNodeType(scope, decl.right))),
1906
+ // [ Opcodes.local_tee, newValueTmp ],
1907
+
1908
+ // Opcodes.i32_to_u,
1909
+ // [ StoreOps.i16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1910
+ // ]
1911
+ }, Blocktype.void),
1912
+
1913
+ [ Opcodes.local_get, newValueTmp ]
1914
+ ];
1915
+ }
1916
+
1190
1917
  const [ local, isGlobal ] = lookupName(scope, name);
1191
1918
 
1192
1919
  if (local === undefined) {
1193
- // todo: this should be a devtools/repl/??? only thing
1920
+ // todo: this should be a sloppy mode only thing
1194
1921
 
1195
1922
  // only allow = for this
1196
- if (decl.operator !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1923
+ if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1197
1924
 
1198
1925
  if (builtinVars[name]) {
1199
1926
  // just return rhs (eg `NaN = 2`)
@@ -1202,25 +1929,53 @@ const generateAssign = (scope, decl) => {
1202
1929
 
1203
1930
  // set global and return (eg a = 2)
1204
1931
  return [
1205
- ...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
1932
+ ...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
1206
1933
  [ Opcodes.global_get, globals[name].idx ]
1207
1934
  ];
1208
1935
  }
1209
1936
 
1210
- if (decl.operator === '=') {
1211
- typeStates[name] = getNodeType(scope, decl.right);
1212
-
1937
+ if (op === '=') {
1213
1938
  return [
1214
1939
  ...generate(scope, decl.right, isGlobal, name),
1215
1940
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1216
- [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1941
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1942
+
1943
+ ...setType(scope, name, getNodeType(scope, decl.right))
1944
+ ];
1945
+ }
1946
+
1947
+ if (op === '||' || op === '&&' || op === '??') {
1948
+ // todo: is this needed?
1949
+ // for logical assignment ops, it is not left @= right ~= left = left @ right
1950
+ // instead, left @ (left = right)
1951
+ // eg, x &&= y ~= x && (x = y)
1952
+
1953
+ return [
1954
+ ...performOp(scope, op, [
1955
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1956
+ ], [
1957
+ ...generate(scope, decl.right),
1958
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1959
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1960
+ ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1961
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1962
+
1963
+ getLastType(scope),
1964
+ // hack: type is idx+1
1965
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
1217
1966
  ];
1218
1967
  }
1219
1968
 
1220
1969
  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),
1970
+ ...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
1971
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1223
- [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1972
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1973
+
1974
+ // todo: string concat types
1975
+
1976
+ // hack: type is idx+1
1977
+ ...number(TYPES.number, Valtype.i32),
1978
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
1224
1979
  ];
1225
1980
  };
1226
1981
 
@@ -1245,13 +2000,14 @@ const generateUnary = (scope, decl) => {
1245
2000
 
1246
2001
  case '!':
1247
2002
  // !=
1248
- return falsy(scope, generate(scope, decl.argument));
2003
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
1249
2004
 
1250
2005
  case '~':
2006
+ // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
1251
2007
  return [
1252
2008
  ...generate(scope, decl.argument),
1253
2009
  Opcodes.i32_to,
1254
- [ Opcodes.i32_const, signedLEB128(-1) ],
2010
+ [ Opcodes.i32_const, ...signedLEB128(-1) ],
1255
2011
  [ Opcodes.i32_xor ],
1256
2012
  Opcodes.i32_from
1257
2013
  ];
@@ -1289,11 +2045,16 @@ const generateUnary = (scope, decl) => {
1289
2045
  return out;
1290
2046
 
1291
2047
  case 'typeof':
1292
- const type = getNodeType(scope, decl.argument);
2048
+ return typeSwitch(scope, getNodeType(scope, decl.argument), {
2049
+ [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2050
+ [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2051
+ [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2052
+ [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2053
+ [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
1293
2054
 
1294
- // for custom types, just return object
1295
- if (type > 0xffffffffffff7) return number(TYPES.object);
1296
- return number(type);
2055
+ // object and internal types
2056
+ default: makeString(scope, 'object', false, '#typeof_result'),
2057
+ });
1297
2058
 
1298
2059
  default:
1299
2060
  return todo(`unary operator ${decl.operator} not implemented yet`);
@@ -1332,9 +2093,9 @@ const generateUpdate = (scope, decl) => {
1332
2093
  };
1333
2094
 
1334
2095
  const generateIf = (scope, decl) => {
1335
- const out = truthy(scope, generate(scope, decl.test), decl.test);
2096
+ const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test), false, true);
1336
2097
 
1337
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2098
+ out.push([ Opcodes.if, Blocktype.void ]);
1338
2099
  depth.push('if');
1339
2100
 
1340
2101
  const consOut = generate(scope, decl.consequent);
@@ -1356,16 +2117,28 @@ const generateIf = (scope, decl) => {
1356
2117
  };
1357
2118
 
1358
2119
  const generateConditional = (scope, decl) => {
1359
- const out = [ ...generate(scope, decl.test) ];
2120
+ const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test), false, true);
1360
2121
 
1361
- out.push(Opcodes.i32_to, [ Opcodes.if, valtypeBinary ]);
2122
+ out.push([ Opcodes.if, valtypeBinary ]);
1362
2123
  depth.push('if');
1363
2124
 
1364
2125
  out.push(...generate(scope, decl.consequent));
1365
2126
 
2127
+ // note type
2128
+ out.push(
2129
+ ...getNodeType(scope, decl.consequent),
2130
+ setLastType(scope)
2131
+ );
2132
+
1366
2133
  out.push([ Opcodes.else ]);
1367
2134
  out.push(...generate(scope, decl.alternate));
1368
2135
 
2136
+ // note type
2137
+ out.push(
2138
+ ...getNodeType(scope, decl.alternate),
2139
+ setLastType(scope)
2140
+ );
2141
+
1369
2142
  out.push([ Opcodes.end ]);
1370
2143
  depth.pop();
1371
2144
 
@@ -1422,9 +2195,148 @@ const generateWhile = (scope, decl) => {
1422
2195
  return out;
1423
2196
  };
1424
2197
 
2198
+ const generateForOf = (scope, decl) => {
2199
+ const out = [];
2200
+
2201
+ // todo: for of inside for of might fuck up?
2202
+ const pointer = localTmp(scope, 'forof_base_pointer', Valtype.i32);
2203
+ const length = localTmp(scope, 'forof_length', Valtype.i32);
2204
+ const counter = localTmp(scope, 'forof_counter', Valtype.i32);
2205
+
2206
+ out.push(
2207
+ // set pointer as right
2208
+ ...generate(scope, decl.right),
2209
+ Opcodes.i32_to_u,
2210
+ [ Opcodes.local_set, pointer ],
2211
+
2212
+ // set counter as 0 (could be already used)
2213
+ ...number(0, Valtype.i32),
2214
+ [ Opcodes.local_set, counter ],
2215
+
2216
+ // get length
2217
+ [ Opcodes.local_get, pointer ],
2218
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
2219
+ [ Opcodes.local_set, length ]
2220
+ );
2221
+
2222
+ depth.push('forof');
2223
+
2224
+ // setup local for left
2225
+ generate(scope, decl.left);
2226
+
2227
+ const leftName = decl.left.declarations[0].id.name;
2228
+ const [ local, isGlobal ] = lookupName(scope, leftName);
2229
+
2230
+ depth.push('block');
2231
+ depth.push('block');
2232
+
2233
+ // // todo: we should only do this for strings but we don't know at compile-time :(
2234
+ // hack: this is naughty and will break things!
2235
+ let newOut = number(0, Valtype.f64), newPointer = -1;
2236
+ if (pages.hasString) {
2237
+ 0, [ newOut, newPointer ] = makeArray(scope, {
2238
+ rawElements: new Array(1)
2239
+ }, isGlobal, leftName, true, 'i16');
2240
+ }
2241
+
2242
+ // set type for local
2243
+ out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2244
+ [TYPES._array]: [
2245
+ ...setType(scope, leftName, TYPES.number),
2246
+
2247
+ [ Opcodes.loop, Blocktype.void ],
2248
+
2249
+ [ Opcodes.local_get, pointer ],
2250
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2251
+
2252
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2253
+
2254
+ [ Opcodes.block, Blocktype.void ],
2255
+ [ Opcodes.block, Blocktype.void ],
2256
+ ...generate(scope, decl.body),
2257
+ [ Opcodes.end ],
2258
+
2259
+ // increment iter pointer by valtype size
2260
+ [ Opcodes.local_get, pointer ],
2261
+ ...number(ValtypeSize[valtype], Valtype.i32),
2262
+ [ Opcodes.i32_add ],
2263
+ [ Opcodes.local_set, pointer ],
2264
+
2265
+ // increment counter by 1
2266
+ [ Opcodes.local_get, counter ],
2267
+ ...number(1, Valtype.i32),
2268
+ [ Opcodes.i32_add ],
2269
+ [ Opcodes.local_tee, counter ],
2270
+
2271
+ // loop if counter != length
2272
+ [ Opcodes.local_get, length ],
2273
+ [ Opcodes.i32_ne ],
2274
+ [ Opcodes.br_if, 1 ],
2275
+
2276
+ [ Opcodes.end ],
2277
+ [ Opcodes.end ]
2278
+ ],
2279
+ [TYPES.string]: [
2280
+ ...setType(scope, leftName, TYPES.string),
2281
+
2282
+ [ Opcodes.loop, Blocktype.void ],
2283
+
2284
+ // setup new/out array
2285
+ ...newOut,
2286
+ [ Opcodes.drop ],
2287
+
2288
+ ...number(0, Valtype.i32), // base 0 for store after
2289
+
2290
+ // load current string ind {arg}
2291
+ [ Opcodes.local_get, pointer ],
2292
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2293
+
2294
+ // store to new string ind 0
2295
+ [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2296
+
2297
+ // return new string (page)
2298
+ ...number(newPointer),
2299
+
2300
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2301
+
2302
+ [ Opcodes.block, Blocktype.void ],
2303
+ [ Opcodes.block, Blocktype.void ],
2304
+ ...generate(scope, decl.body),
2305
+ [ Opcodes.end ],
2306
+
2307
+ // increment iter pointer by valtype size
2308
+ [ Opcodes.local_get, pointer ],
2309
+ ...number(ValtypeSize.i16, Valtype.i32),
2310
+ [ Opcodes.i32_add ],
2311
+ [ Opcodes.local_set, pointer ],
2312
+
2313
+ // increment counter by 1
2314
+ [ Opcodes.local_get, counter ],
2315
+ ...number(1, Valtype.i32),
2316
+ [ Opcodes.i32_add ],
2317
+ [ Opcodes.local_tee, counter ],
2318
+
2319
+ // loop if counter != length
2320
+ [ Opcodes.local_get, length ],
2321
+ [ Opcodes.i32_ne ],
2322
+ [ Opcodes.br_if, 1 ],
2323
+
2324
+ [ Opcodes.end ],
2325
+ [ Opcodes.end ]
2326
+ ],
2327
+ default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2328
+ }, Blocktype.void));
2329
+
2330
+ depth.pop();
2331
+ depth.pop();
2332
+ depth.pop();
2333
+
2334
+ return out;
2335
+ };
2336
+
1425
2337
  const getNearestLoop = () => {
1426
2338
  for (let i = depth.length - 1; i >= 0; i--) {
1427
- if (depth[i] === 'while' || depth[i] === 'for') return i;
2339
+ if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
1428
2340
  }
1429
2341
 
1430
2342
  return -1;
@@ -1508,13 +2420,25 @@ const generateAssignPat = (scope, decl) => {
1508
2420
  };
1509
2421
 
1510
2422
  let pages = new Map();
1511
- const allocPage = reason => {
1512
- if (pages.has(reason)) return pages.get(reason);
2423
+ const allocPage = (reason, type) => {
2424
+ if (pages.has(reason)) return pages.get(reason).ind;
2425
+
2426
+ if (reason.startsWith('array:')) pages.hasArray = true;
2427
+ if (reason.startsWith('string:')) pages.hasString = true;
2428
+
2429
+ const ind = pages.size;
2430
+ pages.set(reason, { ind, type });
2431
+
2432
+ if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2433
+
2434
+ return ind;
2435
+ };
1513
2436
 
1514
- let ind = pages.size;
1515
- pages.set(reason, ind);
2437
+ const freePage = reason => {
2438
+ const { ind } = pages.get(reason);
2439
+ pages.delete(reason);
1516
2440
 
1517
- if (codeLog) log('codegen', `allocated new page of memory (${ind}) | ${reason}`);
2441
+ if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
1518
2442
 
1519
2443
  return ind;
1520
2444
  };
@@ -1528,7 +2452,7 @@ const itemTypeToValtype = {
1528
2452
  i16: 'i32'
1529
2453
  };
1530
2454
 
1531
- const storeOps = {
2455
+ const StoreOps = {
1532
2456
  i32: Opcodes.i32_store,
1533
2457
  i64: Opcodes.i64_store,
1534
2458
  f64: Opcodes.f64_store,
@@ -1537,13 +2461,32 @@ const storeOps = {
1537
2461
  i16: Opcodes.i32_store16
1538
2462
  };
1539
2463
 
2464
+ let data = [];
2465
+
2466
+ const compileBytes = (val, itemType, signed = true) => {
2467
+ // todo: this is a mess and needs confirming / ????
2468
+ switch (itemType) {
2469
+ case 'i8': return [ val % 256 ];
2470
+ case 'i16': return [ val % 256, Math.floor(val / 256) ];
2471
+
2472
+ case 'i32':
2473
+ case 'i64':
2474
+ return enforceFourBytes(signedLEB128(val));
2475
+
2476
+ case 'f64': return ieee754_binary64(val);
2477
+ }
2478
+ };
2479
+
1540
2480
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
1541
2481
  const out = [];
1542
2482
 
2483
+ let firstAssign = false;
1543
2484
  if (!arrays.has(name) || name === '$undeclared') {
2485
+ firstAssign = true;
2486
+
1544
2487
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1545
2488
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1546
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`) * pageSize);
2489
+ arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
1547
2490
  }
1548
2491
 
1549
2492
  const pointer = arrays.get(name);
@@ -1551,8 +2494,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1551
2494
  const useRawElements = !!decl.rawElements;
1552
2495
  const elements = useRawElements ? decl.rawElements : decl.elements;
1553
2496
 
2497
+ const valtype = itemTypeToValtype[itemType];
1554
2498
  const length = elements.length;
1555
2499
 
2500
+ if (firstAssign && useRawElements) {
2501
+ let bytes = compileBytes(length, 'i32');
2502
+
2503
+ if (!initEmpty) for (let i = 0; i < length; i++) {
2504
+ if (elements[i] == null) continue;
2505
+
2506
+ bytes.push(...compileBytes(elements[i], itemType));
2507
+ }
2508
+
2509
+ data.push({
2510
+ offset: pointer,
2511
+ bytes
2512
+ });
2513
+
2514
+ // local value as pointer
2515
+ out.push(...number(pointer));
2516
+
2517
+ return [ out, pointer ];
2518
+ }
2519
+
1556
2520
  // store length as 0th array
1557
2521
  out.push(
1558
2522
  ...number(0, Valtype.i32),
@@ -1560,8 +2524,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1560
2524
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
1561
2525
  );
1562
2526
 
1563
- const storeOp = storeOps[itemType];
1564
- const valtype = itemTypeToValtype[itemType];
2527
+ const storeOp = StoreOps[itemType];
1565
2528
 
1566
2529
  if (!initEmpty) for (let i = 0; i < length; i++) {
1567
2530
  if (elements[i] == null) continue;
@@ -1576,30 +2539,34 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1576
2539
  // local value as pointer
1577
2540
  out.push(...number(pointer));
1578
2541
 
1579
- scope.memory = true;
2542
+ return [ out, pointer ];
2543
+ };
2544
+
2545
+ const makeString = (scope, str, global = false, name = '$undeclared') => {
2546
+ const rawElements = new Array(str.length);
2547
+ for (let i = 0; i < str.length; i++) {
2548
+ rawElements[i] = str.charCodeAt(i);
2549
+ }
1580
2550
 
1581
- return out;
2551
+ return makeArray(scope, {
2552
+ rawElements
2553
+ }, global, name, false, 'i16')[0];
1582
2554
  };
1583
2555
 
1584
2556
  let arrays = new Map();
1585
2557
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
1586
- return makeArray(scope, decl, global, name, initEmpty, valtype);
2558
+ return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
1587
2559
  };
1588
2560
 
1589
2561
  export const generateMember = (scope, decl, _global, _name) => {
1590
- const type = getNodeType(scope, decl.object);
2562
+ const name = decl.object.name;
2563
+ const pointer = arrays.get(name);
2564
+
2565
+ const aotPointer = pointer != null;
1591
2566
 
1592
2567
  // hack: .length
1593
2568
  if (decl.property.name === 'length') {
1594
2569
  // 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
2570
  return [
1604
2571
  ...(aotPointer ? number(0, Valtype.i32) : [
1605
2572
  ...generate(scope, decl.object),
@@ -1611,18 +2578,17 @@ export const generateMember = (scope, decl, _global, _name) => {
1611
2578
  ];
1612
2579
  }
1613
2580
 
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;
2581
+ // // todo: we should only do this for strings but we don't know at compile-time :(
2582
+ // hack: this is naughty and will break things!
2583
+ let newOut = number(0, Valtype.f64), newPointer = -1;
2584
+ if (pages.hasString) {
2585
+ 0, [ newOut, newPointer ] = makeArray(scope, {
2586
+ rawElements: new Array(1)
2587
+ }, _global, _name, true, 'i16');
2588
+ }
1623
2589
 
1624
- if (type === TYPES._array) {
1625
- return [
2590
+ return typeSwitch(scope, getNodeType(scope, decl.object), {
2591
+ [TYPES._array]: [
1626
2592
  // get index as valtype
1627
2593
  ...generate(scope, decl.property),
1628
2594
 
@@ -1638,45 +2604,46 @@ export const generateMember = (scope, decl, _global, _name) => {
1638
2604
  ]),
1639
2605
 
1640
2606
  // read from memory
1641
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1642
- ];
1643
- }
2607
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
1644
2608
 
1645
- // string
2609
+ ...number(TYPES.number, Valtype.i32),
2610
+ setLastType(scope)
2611
+ ],
1646
2612
 
1647
- const newOut = makeArray(scope, {
1648
- rawElements: new Array(1)
1649
- }, _global, _name, true, 'i16');
1650
- const newPointer = arrays.get(_name ?? '$undeclared');
2613
+ [TYPES.string]: [
2614
+ // setup new/out array
2615
+ ...newOut,
2616
+ [ Opcodes.drop ],
1651
2617
 
1652
- return [
1653
- // setup new/out array
1654
- ...newOut,
1655
- [ Opcodes.drop ],
2618
+ ...number(0, Valtype.i32), // base 0 for store later
1656
2619
 
1657
- ...number(0, Valtype.i32), // base 0 for store later
2620
+ ...generate(scope, decl.property),
1658
2621
 
1659
- ...generate(scope, decl.property),
2622
+ Opcodes.i32_to_u,
2623
+ ...number(ValtypeSize.i16, Valtype.i32),
2624
+ [ Opcodes.i32_mul ],
1660
2625
 
1661
- Opcodes.i32_to_u,
1662
- ...number(ValtypeSize.i16, Valtype.i32),
1663
- [ Opcodes.i32_mul ],
2626
+ ...(aotPointer ? [] : [
2627
+ ...generate(scope, decl.object),
2628
+ Opcodes.i32_to_u,
2629
+ [ Opcodes.i32_add ]
2630
+ ]),
1664
2631
 
1665
- ...(aotPointer ? [] : [
1666
- ...generate(scope, decl.object),
1667
- Opcodes.i32_to_u,
1668
- [ Opcodes.i32_add ]
1669
- ]),
2632
+ // load current string ind {arg}
2633
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
1670
2634
 
1671
- // load current string ind {arg}
1672
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2635
+ // store to new string ind 0
2636
+ [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
1673
2637
 
1674
- // store to new string ind 0
1675
- [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2638
+ // return new string (page)
2639
+ ...number(newPointer),
1676
2640
 
1677
- // return new string (page)
1678
- ...number(newPointer)
1679
- ];
2641
+ ...number(TYPES.string, Valtype.i32),
2642
+ setLastType(scope)
2643
+ ],
2644
+
2645
+ default: [ [ Opcodes.unreachable ] ]
2646
+ });
1680
2647
  };
1681
2648
 
1682
2649
  const randId = () => Math.random().toString(16).slice(0, -4);
@@ -1721,22 +2688,25 @@ const generateFunc = (scope, decl) => {
1721
2688
  if (decl.generator) return todo('generator functions are not supported');
1722
2689
 
1723
2690
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
1724
- const params = decl.params?.map(x => x.name) ?? [];
2691
+ const params = decl.params ?? [];
1725
2692
 
1726
2693
  // const innerScope = { ...scope };
1727
2694
  // TODO: share scope/locals between !!!
1728
2695
  const innerScope = {
1729
2696
  locals: {},
1730
2697
  localInd: 0,
1731
- returns: [ valtypeBinary ],
1732
- memory: false,
2698
+ // value, type
2699
+ returns: [ valtypeBinary, Valtype.i32 ],
1733
2700
  throws: false,
1734
2701
  name
1735
2702
  };
1736
2703
 
1737
2704
  for (let i = 0; i < params.length; i++) {
1738
- const param = params[i];
1739
- innerScope.locals[param] = { idx: innerScope.localInd++, type: valtypeBinary };
2705
+ allocVar(innerScope, params[i].name, false);
2706
+
2707
+ if (typedInput && params[i].typeAnnotation) {
2708
+ addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
2709
+ }
1740
2710
  }
1741
2711
 
1742
2712
  let body = objectHack(decl.body);
@@ -1751,11 +2721,9 @@ const generateFunc = (scope, decl) => {
1751
2721
  const wasm = generate(innerScope, body);
1752
2722
  const func = {
1753
2723
  name,
1754
- params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
2724
+ params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
1755
2725
  returns: innerScope.returns,
1756
- returnType: innerScope.returnType ?? TYPES.number,
1757
2726
  locals: innerScope.locals,
1758
- memory: innerScope.memory,
1759
2727
  throws: innerScope.throws,
1760
2728
  index: currentFuncIndex++
1761
2729
  };
@@ -1768,121 +2736,13 @@ const generateFunc = (scope, decl) => {
1768
2736
  }
1769
2737
  }
1770
2738
 
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]])
2739
+ // add end return if not found
2740
+ if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
2741
+ wasm.push(
2742
+ ...number(0),
2743
+ ...number(TYPES.undefined, Valtype.i32),
2744
+ [ Opcodes.return ]
1883
2745
  );
1884
-
1885
- func.returns = new Array(lanes).fill(type);
1886
2746
  }
1887
2747
 
1888
2748
  func.wasm = wasm;
@@ -1893,10 +2753,10 @@ const generateFunc = (scope, decl) => {
1893
2753
  };
1894
2754
 
1895
2755
  const generateCode = (scope, decl) => {
1896
- const out = [];
2756
+ let out = [];
1897
2757
 
1898
2758
  for (const x of decl.body) {
1899
- out.push(...generate(scope, x));
2759
+ out = out.concat(generate(scope, x));
1900
2760
  }
1901
2761
 
1902
2762
  return out;
@@ -1912,10 +2772,9 @@ const internalConstrs = {
1912
2772
 
1913
2773
  // new Array(n)
1914
2774
 
1915
- makeArray(scope, {
2775
+ const [ , pointer ] = makeArray(scope, {
1916
2776
  rawElements: new Array(0)
1917
2777
  }, global, name, true);
1918
- const pointer = arrays.get(name ?? '$undeclared');
1919
2778
 
1920
2779
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
1921
2780
 
@@ -1933,9 +2792,38 @@ const internalConstrs = {
1933
2792
  ];
1934
2793
  },
1935
2794
  type: TYPES._array
2795
+ },
2796
+
2797
+ __Array_of: {
2798
+ // this is not a constructor but best fits internal structure here
2799
+ generate: (scope, decl, global, name) => {
2800
+ // Array.of(i0, i1, ...)
2801
+ return generateArray(scope, {
2802
+ elements: decl.arguments
2803
+ }, global, name);
2804
+ },
2805
+ type: TYPES._array,
2806
+ notConstr: true
1936
2807
  }
1937
2808
  };
1938
2809
 
2810
+ // const _ = Array.prototype.push;
2811
+ // Array.prototype.push = function (a) {
2812
+ // const check = arr => {
2813
+ // for (const x of arr) {
2814
+ // if (x === undefined) {
2815
+ // console.trace(arr);
2816
+ // process.exit();
2817
+ // }
2818
+ // if (Array.isArray(x)) check(x);
2819
+ // }
2820
+ // };
2821
+ // if (Array.isArray(a) && !new Error().stack.includes('node:')) check(a);
2822
+ // // if (Array.isArray(a)) check(a);
2823
+
2824
+ // return _.apply(this, arguments);
2825
+ // };
2826
+
1939
2827
  export default program => {
1940
2828
  globals = {};
1941
2829
  globalInd = 0;
@@ -1944,9 +2832,9 @@ export default program => {
1944
2832
  funcs = [];
1945
2833
  funcIndex = {};
1946
2834
  depth = [];
1947
- typeStates = {};
1948
2835
  arrays = new Map();
1949
2836
  pages = new Map();
2837
+ data = [];
1950
2838
  currentFuncIndex = importedFuncs.length;
1951
2839
 
1952
2840
  globalThis.valtype = 'f64';
@@ -1996,18 +2884,20 @@ export default program => {
1996
2884
  body: program.body
1997
2885
  };
1998
2886
 
2887
+ if (process.argv.includes('-ast-log')) console.log(program.body.body);
2888
+
1999
2889
  generateFunc(scope, program);
2000
2890
 
2001
2891
  const main = funcs[funcs.length - 1];
2002
2892
  main.export = true;
2003
- main.returns = [ valtypeBinary ];
2893
+ main.returns = [ valtypeBinary, Valtype.i32 ];
2004
2894
 
2005
2895
  const lastInst = main.wasm[main.wasm.length - 1] ?? [ Opcodes.end ];
2006
2896
  if (lastInst[0] === Opcodes.drop) {
2007
2897
  main.wasm.splice(main.wasm.length - 1, 1);
2008
2898
 
2009
2899
  const finalStatement = program.body.body[program.body.body.length - 1];
2010
- main.returnType = getNodeType(main, finalStatement);
2900
+ main.wasm.push(...getNodeType(main, finalStatement));
2011
2901
  }
2012
2902
 
2013
2903
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
@@ -2023,5 +2913,5 @@ export default program => {
2023
2913
  // if blank main func and other exports, remove it
2024
2914
  if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
2025
2915
 
2026
- return { funcs, globals, tags, exceptions, pages };
2916
+ return { funcs, globals, tags, exceptions, pages, data };
2027
2917
  };