porffor 0.1.1 → 0.2.0-c6c8c81

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,45 +153,65 @@ 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:
185
217
  return todo(`no generation for ${decl.type}!`);
@@ -269,19 +301,17 @@ const generateIdent = (scope, decl) => {
269
301
 
270
302
  const generateReturn = (scope, decl) => {
271
303
  if (decl.argument === null) {
272
- if (!scope.returnType) scope.returnType = TYPES.undefined;
273
-
274
304
  // just bare "return"
275
305
  return [
276
306
  ...number(UNDEFINED), // "undefined" if func returns
307
+ ...number(TYPES.undefined, Valtype.i32), // type undefined
277
308
  [ Opcodes.return ]
278
309
  ];
279
310
  }
280
311
 
281
- if (!scope.returnType) scope.returnType = getNodeType(scope, decl.argument);
282
-
283
312
  return [
284
313
  ...generate(scope, decl.argument),
314
+ ...getNodeType(scope, decl.argument),
285
315
  [ Opcodes.return ]
286
316
  ];
287
317
  };
@@ -295,11 +325,13 @@ const localTmp = (scope, name, type = valtypeBinary) => {
295
325
  return idx;
296
326
  };
297
327
 
298
- const performLogicOp = (scope, op, left, right) => {
328
+ const isIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
329
+
330
+ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
299
331
  const checks = {
300
- '||': Opcodes.eqz,
301
- '&&': [ Opcodes.i32_to ]
302
- // todo: ??
332
+ '||': falsy,
333
+ '&&': truthy,
334
+ '??': nullish
303
335
  };
304
336
 
305
337
  if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
@@ -307,14 +339,52 @@ const performLogicOp = (scope, op, left, right) => {
307
339
  // generic structure for {a} OP {b}
308
340
  // -->
309
341
  // _ = {a}; if (OP_CHECK) {b} else _
342
+
343
+ // if we can, use int tmp and convert at the end to help prevent unneeded conversions
344
+ // (like if we are in an if condition - very common)
345
+ const leftIsInt = isIntOp(left[left.length - 1]);
346
+ const rightIsInt = isIntOp(right[right.length - 1]);
347
+
348
+ const canInt = leftIsInt && rightIsInt;
349
+
350
+ if (canInt) {
351
+ // remove int -> float conversions from left and right
352
+ left.pop();
353
+ right.pop();
354
+
355
+ return [
356
+ ...left,
357
+ [ Opcodes.local_tee, localTmp(scope, 'logictmpi', Valtype.i32) ],
358
+ ...checks[op](scope, [], leftType, true, true),
359
+ [ Opcodes.if, Valtype.i32 ],
360
+ ...right,
361
+ // note type
362
+ ...rightType,
363
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
364
+ [ Opcodes.else ],
365
+ [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
366
+ // note type
367
+ ...leftType,
368
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
369
+ [ Opcodes.end ],
370
+ Opcodes.i32_from
371
+ ];
372
+ }
373
+
310
374
  return [
311
375
  ...left,
312
376
  [ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
313
- ...checks[op],
377
+ ...checks[op](scope, [], leftType, false, true),
314
378
  [ Opcodes.if, valtypeBinary ],
315
379
  ...right,
380
+ // note type
381
+ ...rightType,
382
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
316
383
  [ Opcodes.else ],
317
384
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
385
+ // note type
386
+ ...leftType,
387
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
318
388
  [ Opcodes.end ]
319
389
  ];
320
390
  };
@@ -325,15 +395,16 @@ const concatStrings = (scope, left, right, global, name, assign) => {
325
395
  // todo: optimize by looking up names in arrays and using that if exists?
326
396
  // todo: optimize this if using literals/known lengths?
327
397
 
328
- scope.memory = true;
329
-
330
- const pointer = arrays.get(name ?? '$undeclared');
331
-
332
398
  const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
333
399
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
334
400
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
335
401
 
402
+ const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
403
+ if (aotWFA) addVarMeta(name, { wellFormed: undefined });
404
+
336
405
  if (assign) {
406
+ const pointer = arrays.get(name ?? '$undeclared');
407
+
337
408
  return [
338
409
  // setup right
339
410
  ...right,
@@ -384,15 +455,12 @@ const concatStrings = (scope, left, right, global, name, assign) => {
384
455
 
385
456
  const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
386
457
 
387
- const newOut = makeArray(scope, {
458
+ // alloc/assign array
459
+ const [ , pointer ] = makeArray(scope, {
388
460
  rawElements: new Array(0)
389
461
  }, global, name, true, 'i16');
390
462
 
391
463
  return [
392
- // setup new/out array
393
- ...newOut,
394
- [ Opcodes.drop ],
395
-
396
464
  // setup left
397
465
  ...left,
398
466
  Opcodes.i32_to_u,
@@ -458,90 +526,309 @@ const concatStrings = (scope, left, right, global, name, assign) => {
458
526
  ];
459
527
  };
460
528
 
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,
529
+ const compareStrings = (scope, left, right) => {
530
+ // todo: this should be rewritten into a func
531
+ // todo: convert left and right to strings if not
532
+ // todo: optimize by looking up names in arrays and using that if exists?
533
+ // todo: optimize this if using literals/known lengths?
474
534
 
475
- // get length
476
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
535
+ const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
536
+ const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
537
+ const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
538
+ const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
477
539
 
478
- // if length == 0
479
- [ Opcodes.i32_eqz ],
480
- Opcodes.i32_from_u
481
- ]
482
- }
540
+ const index = localTmp(scope, 'compare_index', Valtype.i32);
541
+ const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
483
542
 
484
- // if = 0
485
543
  return [
486
- ...wasm,
544
+ // setup left
545
+ ...left,
546
+ Opcodes.i32_to_u,
547
+ [ Opcodes.local_tee, leftPointer ],
487
548
 
488
- ...Opcodes.eqz,
489
- Opcodes.i32_from_u
549
+ // setup right
550
+ ...right,
551
+ Opcodes.i32_to_u,
552
+ [ Opcodes.local_tee, rightPointer ],
553
+
554
+ // fast path: check leftPointer == rightPointer
555
+ // use if (block) for everything after to "return" a value early
556
+ [ Opcodes.i32_ne ],
557
+ [ Opcodes.if, Valtype.i32 ],
558
+
559
+ // get lengths
560
+ [ Opcodes.local_get, leftPointer ],
561
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
562
+ [ Opcodes.local_tee, leftLength ],
563
+
564
+ [ Opcodes.local_get, rightPointer ],
565
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
566
+ [ Opcodes.local_tee, rightLength ],
567
+
568
+ // fast path: check leftLength != rightLength
569
+ [ Opcodes.i32_ne ],
570
+ [ Opcodes.if, Blocktype.void ],
571
+ ...number(0, Valtype.i32),
572
+ [ Opcodes.br, 1 ],
573
+ [ Opcodes.end ],
574
+
575
+ // no fast path for length = 0 as it would probably be slower for most of the time?
576
+
577
+ // tmp could have already been used
578
+ ...number(0, Valtype.i32),
579
+ [ Opcodes.local_set, index ],
580
+
581
+ // setup index end as length * sizeof i16 (2)
582
+ // we do this instead of having to do mul/div each iter for perf™
583
+ [ Opcodes.local_get, leftLength ],
584
+ ...number(ValtypeSize.i16, Valtype.i32),
585
+ [ Opcodes.i32_mul ],
586
+ [ Opcodes.local_set, indexEnd ],
587
+
588
+ // iterate over each char and check if eq
589
+ [ Opcodes.loop, Blocktype.void ],
590
+
591
+ // fetch left
592
+ [ Opcodes.local_get, index ],
593
+ [ Opcodes.local_get, leftPointer ],
594
+ [ Opcodes.i32_add ],
595
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
596
+
597
+ // fetch right
598
+ [ Opcodes.local_get, index ],
599
+ [ Opcodes.local_get, rightPointer ],
600
+ [ Opcodes.i32_add ],
601
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
602
+
603
+ // not equal, "return" false
604
+ [ Opcodes.i32_ne ],
605
+ [ Opcodes.if, Blocktype.void ],
606
+ ...number(0, Valtype.i32),
607
+ [ Opcodes.br, 2 ],
608
+ [ Opcodes.end ],
609
+
610
+ // index += sizeof i16 (2)
611
+ [ Opcodes.local_get, index ],
612
+ ...number(ValtypeSize.i16, Valtype.i32),
613
+ [ Opcodes.i32_add ],
614
+ [ Opcodes.local_tee, index ],
615
+
616
+ // if index != index end (length * sizeof 16), loop
617
+ [ Opcodes.local_get, indexEnd ],
618
+ [ Opcodes.i32_ne ],
619
+ [ Opcodes.br_if, 0 ],
620
+ [ Opcodes.end ],
621
+
622
+ // no failed checks, so true!
623
+ ...number(1, Valtype.i32),
624
+
625
+ // pointers match, so true
626
+ [ Opcodes.else ],
627
+ ...number(1, Valtype.i32),
628
+ [ Opcodes.end ],
629
+
630
+ // convert i32 result to valtype
631
+ // do not do as automatically added by binary exp gen for equality ops
632
+ // Opcodes.i32_from_u
490
633
  ];
491
634
  };
492
635
 
493
- const truthy = (scope, wasm, type) => {
494
- // arrays are always truthy
495
- if (type === TYPES._array) return [
636
+ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
637
+ if (isIntOp(wasm[wasm.length - 1])) return [
496
638
  ...wasm,
497
- [ Opcodes.drop ],
498
- number(1)
639
+ ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
499
640
  ];
500
641
 
501
- if (type === TYPES.string) {
502
- // if not "" (length = 0)
503
- return [
504
- // pointer
505
- ...wasm,
642
+ const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
506
643
 
507
- // get length
508
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
644
+ const def = [
645
+ // if value != 0
646
+ [ Opcodes.local_get, tmp ],
509
647
 
510
- // if length != 0
511
- /* [ Opcodes.i32_eqz ],
512
- [ Opcodes.i32_eqz ], */
513
- Opcodes.i32_from_u
514
- ]
515
- }
516
-
517
- // if != 0
518
- return [
519
- ...wasm,
648
+ // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
649
+ ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
520
650
 
521
651
  /* Opcodes.eqz,
522
652
  [ Opcodes.i32_eqz ],
523
653
  Opcodes.i32_from */
524
654
  ];
655
+
656
+ return [
657
+ ...wasm,
658
+ [ Opcodes.local_set, tmp ],
659
+
660
+ ...typeSwitch(scope, type, {
661
+ // [TYPES.number]: def,
662
+ [TYPES._array]: [
663
+ // arrays are always truthy
664
+ ...number(1, intOut ? Valtype.i32 : valtypeBinary)
665
+ ],
666
+ [TYPES.string]: [
667
+ [ Opcodes.local_get, tmp ],
668
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
669
+
670
+ // get length
671
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
672
+
673
+ // if length != 0
674
+ /* [ Opcodes.i32_eqz ],
675
+ [ Opcodes.i32_eqz ], */
676
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
677
+ ],
678
+ default: def
679
+ }, intOut ? Valtype.i32 : valtypeBinary)
680
+ ];
681
+ };
682
+
683
+ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
684
+ const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
685
+ return [
686
+ ...wasm,
687
+ [ Opcodes.local_set, tmp ],
688
+
689
+ ...typeSwitch(scope, type, {
690
+ [TYPES._array]: [
691
+ // arrays are always truthy
692
+ ...number(0, intOut ? Valtype.i32 : valtypeBinary)
693
+ ],
694
+ [TYPES.string]: [
695
+ [ Opcodes.local_get, tmp ],
696
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
697
+
698
+ // get length
699
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
700
+
701
+ // if length == 0
702
+ [ Opcodes.i32_eqz ],
703
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
704
+ ],
705
+ default: [
706
+ // if value == 0
707
+ [ Opcodes.local_get, tmp ],
708
+
709
+ ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
710
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
711
+ ]
712
+ }, intOut ? Valtype.i32 : valtypeBinary)
713
+ ];
525
714
  };
526
715
 
527
- const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$unspecified', assign = false) => {
716
+ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
717
+ const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
718
+ return [
719
+ ...wasm,
720
+ [ Opcodes.local_set, tmp ],
721
+
722
+ ...typeSwitch(scope, type, {
723
+ [TYPES.undefined]: [
724
+ // undefined
725
+ ...number(1, intOut ? Valtype.i32 : valtypeBinary)
726
+ ],
727
+ [TYPES.object]: [
728
+ // object, null if == 0
729
+ [ Opcodes.local_get, tmp ],
730
+
731
+ ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
732
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
733
+ ],
734
+ default: [
735
+ // not
736
+ ...number(0, intOut ? Valtype.i32 : valtypeBinary)
737
+ ]
738
+ }, intOut ? Valtype.i32 : valtypeBinary)
739
+ ];
740
+ };
741
+
742
+ const stringOnly = wasm => {
743
+ if (!Array.isArray(wasm[0])) return [ ...wasm, 'string_only' ];
744
+ if (wasm.length === 1) return [ [ ...wasm[0], 'string_only' ] ];
745
+
746
+ return [
747
+ [ ...wasm[0], 'string_only|start' ],
748
+ ...wasm.slice(1, -1),
749
+ [ ...wasm[wasm.length - 1], 'string_only|end' ]
750
+ ];
751
+ }
752
+
753
+ const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
528
754
  if (op === '||' || op === '&&' || op === '??') {
529
- return performLogicOp(scope, op, left, right);
755
+ return performLogicOp(scope, op, left, right, leftType, rightType);
756
+ }
757
+
758
+ const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
759
+ const strictOp = op === '===' || op === '!==';
760
+
761
+ const startOut = [], endOut = [];
762
+ const finalise = out => startOut.concat(out, endOut);
763
+
764
+ // if strict (in)equal check types match
765
+ if (strictOp) {
766
+ // startOut.push(
767
+ // ...leftType,
768
+ // ...rightType,
769
+ // [ Opcodes.i32_eq ]
770
+ // );
771
+
772
+ // endOut.push(
773
+ // [ Opcodes.i32_and ]
774
+ // );
775
+
776
+ // startOut.push(
777
+ // [ Opcodes.block, Valtype.i32 ],
778
+ // ...leftType,
779
+ // ...rightType,
780
+ // [ Opcodes.i32_ne ],
781
+ // [ Opcodes.if, Blocktype.void ],
782
+ // ...number(op === '===' ? 0 : 1, Valtype.i32),
783
+ // [ Opcodes.br, 1 ],
784
+ // [ Opcodes.end ]
785
+ // );
786
+
787
+ // endOut.push(
788
+ // [ Opcodes.end ]
789
+ // );
790
+
791
+ endOut.push(
792
+ ...leftType,
793
+ ...rightType,
794
+ ...(op === '===' ? [
795
+ [ Opcodes.i32_eq ],
796
+ [ Opcodes.i32_and ]
797
+ ] : [
798
+ [ Opcodes.i32_ne ],
799
+ [ Opcodes.i32_or ]
800
+ ])
801
+ );
530
802
  }
531
803
 
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
- }
804
+ // todo: if equality op and an operand is undefined, return false
805
+ // todo: niche null hell with 0
537
806
 
538
- // any other math op, NaN
539
- if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
807
+ // if (leftType === TYPES.string || rightType === TYPES.string) {
808
+ // if (op === '+') {
809
+ // // string concat (a + b)
810
+ // return finalise(concatStrings(scope, left, right, _global, _name, assign));
811
+ // }
540
812
 
541
- // else leave bool ops
542
- // todo: convert string to number if string and number or le/ge op
543
- // todo: string equality
544
- }
813
+ // // not an equality op, NaN
814
+ // if (!eqOp) return finalise(number(NaN));
815
+
816
+ // // else leave bool ops
817
+ // // todo: convert string to number if string and number/bool
818
+ // // todo: string (>|>=|<|<=) string
819
+
820
+ // // string comparison
821
+ // if (op === '===' || op === '==') {
822
+ // return finalise(compareStrings(scope, left, right));
823
+ // }
824
+
825
+ // if (op === '!==' || op === '!=') {
826
+ // return finalise([
827
+ // ...compareStrings(scope, left, right),
828
+ // [ Opcodes.i32_eqz ]
829
+ // ]);
830
+ // }
831
+ // }
545
832
 
546
833
  let ops = operatorOpcode[valtype][op];
547
834
 
@@ -551,35 +838,90 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
551
838
  includeBuiltin(scope, builtinName);
552
839
  const idx = funcIndex[builtinName];
553
840
 
554
- return [
841
+ return finalise([
555
842
  ...left,
556
843
  ...right,
557
844
  [ Opcodes.call, idx ]
558
- ];
845
+ ]);
559
846
  }
560
847
 
561
848
  if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
562
849
 
563
850
  if (!Array.isArray(ops)) ops = [ ops ];
851
+ ops = [ ops ];
852
+
853
+ let tmpLeft, tmpRight;
854
+ // if equal op, check if strings for compareStrings
855
+ if (op === '===' || op === '==' || op === '!==' || op === '!=') {
856
+ tmpLeft = localTmp(scope, '__tmpop_left');
857
+ tmpRight = localTmp(scope, '__tmpop_right');
858
+
859
+ ops.unshift(...stringOnly([
860
+ // if left is string
861
+ ...leftType,
862
+ ...number(TYPES.string, Valtype.i32),
863
+ [ Opcodes.i32_eq ],
864
+
865
+ // if right is string
866
+ ...rightType,
867
+ ...number(TYPES.string, Valtype.i32),
868
+ [ Opcodes.i32_eq ],
869
+
870
+ // if either are true
871
+ [ Opcodes.i32_or ],
872
+ [ Opcodes.if, Blocktype.void ],
873
+
874
+ // todo: convert non-strings to strings, for now fail immediately if one is not
875
+ // if left is not string
876
+ ...leftType,
877
+ ...number(TYPES.string, Valtype.i32),
878
+ [ Opcodes.i32_ne ],
879
+
880
+ // if right is not string
881
+ ...rightType,
882
+ ...number(TYPES.string, Valtype.i32),
883
+ [ Opcodes.i32_ne ],
884
+
885
+ // if either are true
886
+ [ Opcodes.i32_or ],
887
+ [ Opcodes.if, Blocktype.void ],
888
+ ...number(0, Valtype.i32),
889
+ [ Opcodes.br, 1 ],
890
+ [ Opcodes.end ],
564
891
 
565
- return [
892
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
893
+ // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
894
+ ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
895
+ [ Opcodes.br, 1 ],
896
+ [ Opcodes.end ],
897
+ ]));
898
+
899
+ // if not already in block, add a block
900
+ // if (endOut.length === 0) {
901
+ startOut.push(stringOnly([ Opcodes.block, Valtype.i32 ]));
902
+ // endOut.push(stringOnly([ Opcodes.end ]));
903
+ endOut.unshift(stringOnly([ Opcodes.end ]));
904
+ // }
905
+ }
906
+
907
+ return finalise([
566
908
  ...left,
909
+ ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
567
910
  ...right,
568
- ops
569
- ];
911
+ ...(tmpRight != null ? stringOnly([ [ Opcodes.local_tee, tmpRight ] ]) : []),
912
+ ...ops
913
+ ]);
570
914
  };
571
915
 
572
916
  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
- ];
917
+ 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
918
 
577
919
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
578
920
 
579
921
  return out;
580
922
  };
581
923
 
582
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, memory, localNames = [], globalNames = [] }) => {
924
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
583
925
  const existing = funcs.find(x => x.name === name);
584
926
  if (existing) return existing;
585
927
 
@@ -615,7 +957,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
615
957
  returns,
616
958
  returnType: TYPES[returnType ?? 'number'],
617
959
  wasm,
618
- memory,
619
960
  internal: true,
620
961
  index: currentFuncIndex++
621
962
  };
@@ -634,21 +975,45 @@ const includeBuiltin = (scope, builtin) => {
634
975
  };
635
976
 
636
977
  const generateLogicExp = (scope, decl) => {
637
- return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
978
+ return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
638
979
  };
639
980
 
981
+ // T = JS type, V = value/pointer
982
+ // 0bTTT
983
+ // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
984
+ // 50 bits usable: 0 11111111111 11??????????????????????????????????????????????????
985
+ // js type: 4 bits
986
+ // internal type: ? bits
987
+ // pointer: 32 bits
988
+ // https://piotrduperas.com/posts/nan-boxing
989
+ // 0x7ffc000000000000
990
+ // budget: 50 bits
991
+ // js type: 4 bits
992
+ // internal type: ? bits
993
+ // pointer: 32 bits
994
+
995
+ // generic
996
+ // 1 23 4 5
997
+ // 0 11111111111 11TTTTIIII??????????PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
998
+ // 1: regular iEEE 754 double NaN
999
+ // 2: extra 1 bit to identify NaN box
1000
+ // 3: js type
1001
+ // 4: internal type
1002
+ // 5: pointer
1003
+
640
1004
  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,
1005
+ number: 0x00,
1006
+ boolean: 0x01,
1007
+ string: 0x02,
1008
+ undefined: 0x03,
1009
+ object: 0x04,
1010
+ function: 0x05,
1011
+ symbol: 0x06,
1012
+ bigint: 0x07,
649
1013
 
650
1014
  // these are not "typeof" types but tracked internally
651
- _array: 0xffffffffffff8
1015
+ _array: 0x10,
1016
+ _regexp: 0x11
652
1017
  };
653
1018
 
654
1019
  const TYPE_NAMES = {
@@ -661,108 +1026,178 @@ const TYPE_NAMES = {
661
1026
  [TYPES.symbol]: 'Symbol',
662
1027
  [TYPES.bigint]: 'BigInt',
663
1028
 
664
- [TYPES._array]: 'Array'
1029
+ [TYPES._array]: 'Array',
1030
+ [TYPES._regexp]: 'RegExp'
665
1031
  };
666
1032
 
667
- let typeStates = {};
668
-
669
1033
  const getType = (scope, _name) => {
670
1034
  const name = mapName(_name);
671
- if (scope.locals[name]) return typeStates[name];
672
1035
 
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];
1036
+ if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1037
+ if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1038
+
1039
+ let type = TYPES.undefined;
1040
+ if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1041
+ if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
676
1042
 
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;
1043
+ if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
1044
+ name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
679
1045
 
680
- return TYPES.undefined;
1046
+ return number(type, Valtype.i32);
681
1047
  };
682
1048
 
683
- const getNodeType = (scope, node) => {
684
- if (node.type === 'Literal') {
685
- return TYPES[typeof node.value];
686
- }
1049
+ const setType = (scope, _name, type) => {
1050
+ const name = mapName(_name);
687
1051
 
688
- if (isFuncType(node.type)) {
689
- return TYPES.function;
690
- }
1052
+ const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
691
1053
 
692
- if (node.type === 'Identifier') {
693
- return getType(scope, node.name);
694
- }
1054
+ if (scope.locals[name]) return [
1055
+ ...out,
1056
+ [ Opcodes.local_set, scope.locals[name + '#type'].idx ]
1057
+ ];
695
1058
 
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;
1059
+ if (globals[name]) return [
1060
+ ...out,
1061
+ [ Opcodes.global_set, globals[name + '#type'].idx ]
1062
+ ];
1063
+
1064
+ // throw new Error('could not find var');
1065
+ };
700
1066
 
701
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
702
- if (internalConstrs[name]) return internalConstrs[name].type;
1067
+ const getNodeType = (scope, node) => {
1068
+ const inner = () => {
1069
+ if (node.type === 'Literal') {
1070
+ if (node.regex) return TYPES._regexp;
703
1071
 
704
- let protoFunc;
705
- // ident.func()
706
- if (name && name.startsWith('__')) {
707
- const spl = name.slice(2).split('_');
1072
+ return TYPES[typeof node.value];
1073
+ }
708
1074
 
709
- const baseName = spl.slice(0, -1).join('_');
710
- const baseType = getType(scope, baseName);
1075
+ if (isFuncType(node.type)) {
1076
+ return TYPES.function;
1077
+ }
711
1078
 
712
- const func = spl[spl.length - 1];
713
- protoFunc = prototypeFuncs[baseType]?.[func];
1079
+ if (node.type === 'Identifier') {
1080
+ return getType(scope, node.name);
714
1081
  }
715
1082
 
716
- // literal.func()
717
- if (!name && node.callee.type === 'MemberExpression') {
718
- const baseType = getNodeType(scope, node.callee.object);
1083
+ if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1084
+ const name = node.callee.name;
1085
+ const func = funcs.find(x => x.name === name);
1086
+
1087
+ if (func) {
1088
+ // console.log(scope, func, func.returnType);
1089
+ if (func.returnType) return func.returnType;
1090
+ }
1091
+
1092
+ if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1093
+ if (internalConstrs[name]) return internalConstrs[name].type;
1094
+
1095
+ if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
719
1096
 
720
- const func = node.callee.property.name;
721
- protoFunc = prototypeFuncs[baseType]?.[func];
1097
+ // presume
1098
+ // todo: warn here?
1099
+ return TYPES.number;
1100
+
1101
+ // let protoFunc;
1102
+ // // ident.func()
1103
+ // if (name && name.startsWith('__')) {
1104
+ // const spl = name.slice(2).split('_');
1105
+
1106
+ // const baseName = spl.slice(0, -1).join('_');
1107
+ // const baseType = getType(scope, baseName);
1108
+
1109
+ // const func = spl[spl.length - 1];
1110
+ // protoFunc = prototypeFuncs[baseType]?.[func];
1111
+ // }
1112
+
1113
+ // // literal.func()
1114
+ // if (!name && node.callee.type === 'MemberExpression') {
1115
+ // if (node.callee.object.regex) {
1116
+ // const funcName = node.callee.property.name;
1117
+ // return Rhemyn[funcName] ? TYPES.boolean : TYPES.undefined;
1118
+ // }
1119
+
1120
+ // const baseType = getNodeType(scope, node.callee.object);
1121
+
1122
+ // const func = node.callee.property.name;
1123
+ // protoFunc = prototypeFuncs[baseType]?.[func];
1124
+ // }
1125
+
1126
+ // if (protoFunc) return protoFunc.returnType;
722
1127
  }
723
1128
 
724
- if (protoFunc) return protoFunc.returnType ?? TYPES.number;
1129
+ if (node.type === 'ExpressionStatement') {
1130
+ return getNodeType(scope, node.expression);
1131
+ }
725
1132
 
726
- return TYPES.number;
727
- }
1133
+ if (node.type === 'AssignmentExpression') {
1134
+ return getNodeType(scope, node.right);
1135
+ }
728
1136
 
729
- if (node.type === 'ExpressionStatement') {
730
- return getNodeType(scope, node.expression);
731
- }
1137
+ if (node.type === 'ArrayExpression') {
1138
+ return TYPES._array;
1139
+ }
732
1140
 
733
- if (node.type === 'AssignmentExpression') {
734
- return getNodeType(scope, node.right);
735
- }
1141
+ if (node.type === 'BinaryExpression') {
1142
+ if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1143
+ return TYPES.number;
1144
+
1145
+ // todo: string concat types
1146
+ // if (node.operator !== '+') return TYPES.number;
1147
+ // else return [
1148
+ // // if left is string
1149
+ // ...getNodeType(scope, node.left),
1150
+ // ...number(TYPES.string, Valtype.i32),
1151
+ // [ Opcodes.i32_eq ],
1152
+
1153
+ // // if right is string
1154
+ // ...getNodeType(scope, node.right),
1155
+ // ...number(TYPES.string, Valtype.i32),
1156
+ // [ Opcodes.i32_eq ],
1157
+
1158
+ // // if either are true
1159
+ // [ Opcodes.i32_or ],
1160
+ // ];
1161
+ }
736
1162
 
737
- if (node.type === 'ArrayExpression') {
738
- return TYPES._array;
739
- }
1163
+ if (node.type === 'UnaryExpression') {
1164
+ if (node.operator === '!') return TYPES.boolean;
1165
+ if (node.operator === 'void') return TYPES.undefined;
1166
+ if (node.operator === 'delete') return TYPES.boolean;
1167
+ if (node.operator === 'typeof') return TYPES.string;
740
1168
 
741
- if (node.type === 'BinaryExpression') {
742
- if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1169
+ return TYPES.number;
1170
+ }
743
1171
 
744
- if (node.operator === '+' && (getNodeType(scope, node.left) === TYPES.string || getNodeType(scope, node.right) === TYPES.string)) return TYPES.string;
745
- }
1172
+ if (node.type === 'MemberExpression') {
1173
+ // hack: if something.length, number type
1174
+ if (node.property.name === 'length') return TYPES.number;
746
1175
 
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
- }
1176
+ // we cannot guess
1177
+ return TYPES.number;
1178
+ }
752
1179
 
753
- if (node.type === 'MemberExpression') {
754
- const objectType = getNodeType(scope, node.object);
1180
+ if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
755
1181
 
756
- if (objectType === TYPES.string && node.computed) return TYPES.string;
757
- }
1182
+ // presume
1183
+ // todo: warn here?
1184
+ return TYPES.number;
1185
+ };
758
1186
 
759
- // default to number
760
- return TYPES.number;
1187
+ const ret = inner();
1188
+ // console.trace(node, ret);
1189
+ if (typeof ret === 'number') return number(ret, Valtype.i32);
1190
+ return ret;
761
1191
  };
762
1192
 
763
1193
  const generateLiteral = (scope, decl, global, name) => {
764
1194
  if (decl.value === null) return number(NULL);
765
1195
 
1196
+ if (decl.regex) {
1197
+ scope.regex[name] = decl.regex;
1198
+ return number(1);
1199
+ }
1200
+
766
1201
  switch (typeof decl.value) {
767
1202
  case 'number':
768
1203
  return number(decl.value);
@@ -772,27 +1207,16 @@ const generateLiteral = (scope, decl, global, name) => {
772
1207
  return number(decl.value ? 1 : 0);
773
1208
 
774
1209
  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
1210
  const str = decl.value;
788
1211
  const rawElements = new Array(str.length);
1212
+ let j = 0;
789
1213
  for (let i = 0; i < str.length; i++) {
790
1214
  rawElements[i] = str.charCodeAt(i);
791
1215
  }
792
1216
 
793
1217
  return makeArray(scope, {
794
1218
  rawElements
795
- }, global, name, false, 'i16');
1219
+ }, global, name, false, 'i16')[0];
796
1220
 
797
1221
  default:
798
1222
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -802,7 +1226,8 @@ const generateLiteral = (scope, decl, global, name) => {
802
1226
  const countLeftover = wasm => {
803
1227
  let count = 0, depth = 0;
804
1228
 
805
- for (const inst of wasm) {
1229
+ for (let i = 0; i < wasm.length; i++) {
1230
+ const inst = wasm[i];
806
1231
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
807
1232
  if (inst[0] === Opcodes.if) count--;
808
1233
  if (inst[1] !== Blocktype.void) count++;
@@ -811,11 +1236,12 @@ const countLeftover = wasm => {
811
1236
  if (inst[0] === Opcodes.end) depth--;
812
1237
 
813
1238
  if (depth === 0)
814
- if ([Opcodes.throw, Opcodes.return, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1239
+ if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
815
1240
  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
1241
  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
1242
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
818
1243
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1244
+ else if (inst[0] === Opcodes.return) count = 0;
819
1245
  else if (inst[0] === Opcodes.call) {
820
1246
  let func = funcs.find(x => x.index === inst[1]);
821
1247
  if (func) {
@@ -823,6 +1249,8 @@ const countLeftover = wasm => {
823
1249
  } else count--;
824
1250
  if (func) count += func.returns.length;
825
1251
  } else count--;
1252
+
1253
+ // console.log(count, decompile([ inst ]).slice(0, -1));
826
1254
  }
827
1255
 
828
1256
  return count;
@@ -843,7 +1271,7 @@ const generateExp = (scope, decl) => {
843
1271
  return out;
844
1272
  };
845
1273
 
846
- const arrayUtil = {
1274
+ const CTArrayUtil = {
847
1275
  getLengthI32: pointer => [
848
1276
  ...number(0, Valtype.i32),
849
1277
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
@@ -869,6 +1297,32 @@ const arrayUtil = {
869
1297
  ]
870
1298
  };
871
1299
 
1300
+ const RTArrayUtil = {
1301
+ getLengthI32: pointer => [
1302
+ ...pointer,
1303
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ]
1304
+ ],
1305
+
1306
+ getLength: pointer => [
1307
+ ...pointer,
1308
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
1309
+ Opcodes.i32_from_u
1310
+ ],
1311
+
1312
+ setLengthI32: (pointer, value) => [
1313
+ ...pointer,
1314
+ ...value,
1315
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
1316
+ ],
1317
+
1318
+ setLength: (pointer, value) => [
1319
+ ...pointer,
1320
+ ...value,
1321
+ Opcodes.i32_to_u,
1322
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
1323
+ ]
1324
+ };
1325
+
872
1326
  const generateCall = (scope, decl, _global, _name) => {
873
1327
  /* const callee = decl.callee;
874
1328
  const args = decl.arguments;
@@ -898,95 +1352,166 @@ const generateCall = (scope, decl, _global, _name) => {
898
1352
  const lastInst = out[out.length - 1];
899
1353
  if (lastInst && lastInst[0] === Opcodes.drop) {
900
1354
  out.splice(out.length - 1, 1);
1355
+
1356
+ const finalStatement = parsed.body[parsed.body.length - 1];
1357
+ out.push(
1358
+ ...getNodeType(scope, finalStatement),
1359
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1360
+ );
901
1361
  } else if (countLeftover(out) === 0) {
902
1362
  out.push(...number(UNDEFINED));
1363
+ out.push(
1364
+ ...number(TYPES.undefined, Valtype.i32),
1365
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1366
+ );
903
1367
  }
904
1368
 
1369
+ // if (lastInst && lastInst[0] === Opcodes.drop) {
1370
+ // out.splice(out.length - 1, 1);
1371
+ // } else if (countLeftover(out) === 0) {
1372
+ // out.push(...number(UNDEFINED));
1373
+ // }
1374
+
905
1375
  return out;
906
1376
  }
907
1377
 
908
- let out = [];
909
- let protoFunc, protoName, baseType, baseName = '$undeclared';
1378
+ let protoName, target;
910
1379
  // ident.func()
911
1380
  if (name && name.startsWith('__')) {
912
1381
  const spl = name.slice(2).split('_');
913
1382
 
914
- baseName = spl.slice(0, -1).join('_');
915
- baseType = getType(scope, baseName);
916
-
917
1383
  const func = spl[spl.length - 1];
918
- protoFunc = prototypeFuncs[baseType]?.[func] ?? Object.values(prototypeFuncs).map(x => x[func]).find(x => x);
919
1384
  protoName = func;
1385
+
1386
+ target = { ...decl.callee };
1387
+ target.name = spl.slice(0, -1).join('_');
920
1388
  }
921
1389
 
922
1390
  // literal.func()
923
1391
  if (!name && decl.callee.type === 'MemberExpression') {
924
- baseType = getNodeType(scope, decl.callee.object);
1392
+ // megahack for /regex/.func()
1393
+ if (decl.callee.object.regex) {
1394
+ const funcName = decl.callee.property.name;
1395
+ const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1396
+
1397
+ funcIndex[func.name] = func.index;
1398
+ funcs.push(func);
1399
+
1400
+ return [
1401
+ // make string arg
1402
+ ...generate(scope, decl.arguments[0]),
1403
+
1404
+ // call regex func
1405
+ Opcodes.i32_to_u,
1406
+ [ Opcodes.call, func.index ],
1407
+ Opcodes.i32_from_u,
1408
+
1409
+ ...number(TYPES.boolean, Valtype.i32),
1410
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1411
+ ];
1412
+ }
925
1413
 
926
1414
  const func = decl.callee.property.name;
927
- protoFunc = prototypeFuncs[baseType]?.[func] ?? Object.values(prototypeFuncs).map(x => x[func]).find(x => x);
928
1415
  protoName = func;
929
1416
 
930
- out = generate(scope, decl.callee.object);
931
- out.push([ Opcodes.drop ]);
1417
+ target = decl.callee.object;
932
1418
  }
933
1419
 
934
- if (protoFunc) {
935
- scope.memory = true;
1420
+ // if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
1421
+ // const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
936
1422
 
937
- let pointer = arrays.get(baseName);
1423
+ // funcIndex[func.name] = func.index;
1424
+ // funcs.push(func);
938
1425
 
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');
1426
+ // return [
1427
+ // generate(scope, decl.callee.object)
942
1428
 
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);
1429
+ // // call regex func
1430
+ // [ Opcodes.call, func.index ],
1431
+ // Opcodes.i32_from_u
1432
+ // ];
1433
+ // }
948
1434
 
949
- const [ local, isGlobal ] = lookupName(scope, baseName);
1435
+ if (protoName) {
1436
+ const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1437
+ const f = prototypeFuncs[x][protoName];
1438
+ if (f) acc[x] = f;
1439
+ return acc;
1440
+ }, {});
950
1441
 
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
1442
+ // no prototype function candidates, ignore
1443
+ if (Object.keys(protoCands).length > 0) {
1444
+ // use local for cached i32 length as commonly used
1445
+ const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1446
+ const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1447
+ const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
957
1448
 
958
- [ ...Opcodes.memory_copy, 0x00, 0x00 ],
959
- ];
960
- }
1449
+ // TODO: long-term, prototypes should be their individual separate funcs
1450
+
1451
+ let lengthI32CacheUsed = false;
1452
+ const protoBC = {};
1453
+ for (const x in protoCands) {
1454
+ const protoFunc = protoCands[x];
1455
+ if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
1456
+ protoBC[x] = [
1457
+ ...RTArrayUtil.getLength(getPointer),
1458
+
1459
+ ...number(TYPES.number, Valtype.i32),
1460
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
1461
+ ];
1462
+ continue;
1463
+ }
961
1464
 
962
- if (protoFunc.noArgRetLength && decl.arguments.length === 0) return arrayUtil.getLength(pointer)
1465
+ // const protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp`, protoFunc.local) : -1;
1466
+ // const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp2`, protoFunc.local2) : -1;
1467
+ const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1468
+ const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1469
+
1470
+ const protoOut = protoFunc(getPointer, {
1471
+ getCachedI32: () => {
1472
+ lengthI32CacheUsed = true;
1473
+ return [ [ Opcodes.local_get, lengthLocal ] ];
1474
+ },
1475
+ setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
1476
+ get: () => RTArrayUtil.getLength(getPointer),
1477
+ getI32: () => RTArrayUtil.getLengthI32(getPointer),
1478
+ set: value => RTArrayUtil.setLength(getPointer, value),
1479
+ setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
1480
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1481
+ return makeArray(scope, {
1482
+ rawElements: new Array(length)
1483
+ }, _global, _name, true, itemType);
1484
+ });
1485
+
1486
+ protoBC[x] = [
1487
+ [ Opcodes.block, valtypeBinary ],
1488
+ ...protoOut,
1489
+
1490
+ ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1491
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
1492
+ [ Opcodes.end ]
1493
+ ];
1494
+ }
963
1495
 
964
- let protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp`, protoFunc.local) : -1;
1496
+ return [
1497
+ ...generate(scope, target),
965
1498
 
966
- // use local for cached i32 length as commonly used
967
- let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1499
+ Opcodes.i32_to_u,
1500
+ [ Opcodes.local_set, pointerLocal ],
968
1501
 
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
- ];
1502
+ ...(!lengthI32CacheUsed ? [] : [
1503
+ ...RTArrayUtil.getLengthI32(getPointer),
1504
+ [ Opcodes.local_set, lengthLocal ],
1505
+ ]),
1506
+
1507
+ ...typeSwitch(scope, getNodeType(scope, target), {
1508
+ ...protoBC,
1509
+
1510
+ // TODO: error better
1511
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1512
+ }, valtypeBinary),
1513
+ ];
1514
+ }
990
1515
  }
991
1516
 
992
1517
  // TODO: only allows callee as literal
@@ -1030,34 +1555,50 @@ const generateCall = (scope, decl, _global, _name) => {
1030
1555
 
1031
1556
  const func = funcs.find(x => x.index === idx);
1032
1557
 
1558
+ const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1559
+ const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
1560
+
1033
1561
  let args = decl.arguments;
1034
- if (func && args.length < func.params.length) {
1562
+ if (func && args.length < paramCount) {
1035
1563
  // too little args, push undefineds
1036
- args = args.concat(new Array(func.params.length - args.length).fill(DEFAULT_VALUE));
1564
+ args = args.concat(new Array(paramCount - args.length).fill(DEFAULT_VALUE));
1037
1565
  }
1038
1566
 
1039
- if (func && args.length > func.params.length) {
1567
+ if (func && args.length > paramCount) {
1040
1568
  // too many args, slice extras off
1041
- args = args.slice(0, func.params.length);
1569
+ args = args.slice(0, paramCount);
1042
1570
  }
1043
1571
 
1044
- if (func && func.memory) scope.memory = true;
1045
1572
  if (func && func.throws) scope.throws = true;
1046
1573
 
1574
+ let out = [];
1047
1575
  for (const arg of args) {
1048
- out.push(...generate(scope, arg));
1576
+ out = out.concat(generate(scope, arg));
1577
+ if (userFunc) out = out.concat(getNodeType(scope, arg));
1049
1578
  }
1050
1579
 
1051
1580
  out.push([ Opcodes.call, idx ]);
1052
1581
 
1582
+ if (!userFunc) {
1583
+ // let type;
1584
+ // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1585
+ // if (internalConstrs[name]) type = internalConstrs[name].type;
1586
+ // if (importedFuncs[name] && importedFuncs[]) type =
1587
+
1588
+ // if (type) out.push(
1589
+ // ...number(type, Valtype.i32),
1590
+ // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1591
+ // );
1592
+ } else out.push([ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]);
1593
+
1053
1594
  return out;
1054
1595
  };
1055
1596
 
1056
1597
  const generateNew = (scope, decl, _global, _name) => {
1057
1598
  // hack: basically treat this as a normal call for builtins for now
1058
1599
  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)})`);
1600
+ if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1601
+ if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1061
1602
 
1062
1603
  return generateCall(scope, decl, _global, _name);
1063
1604
  };
@@ -1073,14 +1614,67 @@ const unhackName = name => {
1073
1614
  return name;
1074
1615
  };
1075
1616
 
1617
+ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1618
+ const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1619
+
1620
+ const out = [
1621
+ ...type,
1622
+ [ Opcodes.local_set, tmp ],
1623
+ [ Opcodes.block, returns ]
1624
+ ];
1625
+
1626
+ // todo: use br_table?
1627
+
1628
+ for (const x in bc) {
1629
+ if (x === 'default') continue;
1630
+
1631
+ // if type == x
1632
+ out.push([ Opcodes.local_get, tmp ]);
1633
+ out.push(...number(x, Valtype.i32));
1634
+ out.push([ Opcodes.i32_eq ]);
1635
+
1636
+ out.push([ Opcodes.if, Blocktype.void, `TYPESWITCH|${TYPE_NAMES[x]}` ]);
1637
+ out.push(...bc[x]);
1638
+ out.push([ Opcodes.br, 1 ]);
1639
+ out.push([ Opcodes.end ]);
1640
+ }
1641
+
1642
+ // default
1643
+ if (bc.default) out.push(...bc.default);
1644
+ else if (returns !== Blocktype.void) out.push(...number(0, returns));
1645
+
1646
+ out.push([ Opcodes.end, 'TYPESWITCH_end' ]);
1647
+
1648
+ return out;
1649
+ };
1650
+
1651
+ const allocVar = (scope, name, global = false) => {
1652
+ const target = global ? globals : scope.locals;
1653
+
1654
+ // already declared
1655
+ if (target[name]) {
1656
+ // parser should catch this but sanity check anyway
1657
+ // if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
1658
+
1659
+ return target[name].idx;
1660
+ }
1661
+
1662
+ let idx = global ? globalInd++ : scope.localInd++;
1663
+ target[name] = { idx, type: valtypeBinary };
1664
+
1665
+ let typeIdx = global ? globalInd++ : scope.localInd++;
1666
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
1667
+
1668
+ return idx;
1669
+ };
1670
+
1076
1671
  const generateVar = (scope, decl) => {
1077
- const out = [];
1672
+ let out = [];
1078
1673
 
1079
1674
  const topLevel = scope.name === 'main';
1080
1675
 
1081
1676
  // 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;
1677
+ const global = topLevel || decl._bare; // decl.kind === 'var';
1084
1678
 
1085
1679
  for (const x of decl.declarations) {
1086
1680
  const name = mapName(x.id.name);
@@ -1100,46 +1694,16 @@ const generateVar = (scope, decl) => {
1100
1694
  continue; // always ignore
1101
1695
  }
1102
1696
 
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`);
1108
-
1109
- idx = target[name].idx;
1110
- } else {
1111
- idx = global ? globalInd++ : scope.localInd++;
1112
- target[name] = { idx, type: valtypeBinary };
1113
- }
1114
-
1115
- typeStates[name] = x.init ? getNodeType(scope, x.init) : TYPES.undefined;
1116
-
1117
- // x.init ??= DEFAULT_VALUE;
1697
+ let idx = allocVar(scope, name, global);
1118
1698
  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
- }
1699
+ out = out.concat(generate(scope, x.init, global, name));
1140
1700
 
1141
1701
  out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
1702
+ out.push(...setType(scope, name, getNodeType(scope, x.init)));
1142
1703
  }
1704
+
1705
+ // hack: this follows spec properly but is mostly unneeded 😅
1706
+ // out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
1143
1707
  }
1144
1708
 
1145
1709
  return out;
@@ -1165,8 +1729,6 @@ const generateAssign = (scope, decl) => {
1165
1729
  const name = decl.left.object.name;
1166
1730
  const pointer = arrays.get(name);
1167
1731
 
1168
- scope.memory = true;
1169
-
1170
1732
  const aotPointer = pointer != null;
1171
1733
 
1172
1734
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
@@ -1177,7 +1739,7 @@ const generateAssign = (scope, decl) => {
1177
1739
  Opcodes.i32_to_u
1178
1740
  ]),
1179
1741
 
1180
- ...generate(scope, decl.right, false, name),
1742
+ ...generate(scope, decl.right),
1181
1743
  [ Opcodes.local_tee, newValueTmp ],
1182
1744
 
1183
1745
  Opcodes.i32_to_u,
@@ -1187,13 +1749,76 @@ const generateAssign = (scope, decl) => {
1187
1749
  ];
1188
1750
  }
1189
1751
 
1752
+ const op = decl.operator.slice(0, -1) || '=';
1753
+
1754
+ // arr[i]
1755
+ if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1756
+ const name = decl.left.object.name;
1757
+ const pointer = arrays.get(name);
1758
+
1759
+ const aotPointer = pointer != null;
1760
+
1761
+ const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1762
+ const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1763
+
1764
+ return [
1765
+ ...typeSwitch(scope, getNodeType(scope, decl.left.object), {
1766
+ [TYPES._array]: [
1767
+ ...(aotPointer ? [] : [
1768
+ ...generate(scope, decl.left.object),
1769
+ Opcodes.i32_to_u
1770
+ ]),
1771
+
1772
+ // get index as valtype
1773
+ ...generate(scope, decl.left.property),
1774
+ Opcodes.i32_to_u,
1775
+
1776
+ // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
1777
+ ...number(ValtypeSize[valtype], Valtype.i32),
1778
+ [ Opcodes.i32_mul ],
1779
+ ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
1780
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1781
+
1782
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
1783
+ [ Opcodes.local_get, pointerTmp ],
1784
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1785
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
1786
+ [ Opcodes.local_tee, newValueTmp ],
1787
+
1788
+ [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1789
+ ],
1790
+
1791
+ default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
1792
+
1793
+ // [TYPES.string]: [
1794
+ // // turn into byte offset by * sizeof i16
1795
+ // ...number(ValtypeSize.i16, Valtype.i32),
1796
+ // [ Opcodes.i32_mul ],
1797
+ // ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
1798
+ // ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1799
+
1800
+ // ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
1801
+ // [ Opcodes.local_get, pointerTmp ],
1802
+ // [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1803
+ // ], generate(scope, decl.right), number(TYPES.string, Valtype.i32), getNodeType(scope, decl.right))),
1804
+ // [ Opcodes.local_tee, newValueTmp ],
1805
+
1806
+ // Opcodes.i32_to_u,
1807
+ // [ StoreOps.i16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1808
+ // ]
1809
+ }, Blocktype.void),
1810
+
1811
+ [ Opcodes.local_get, newValueTmp ]
1812
+ ];
1813
+ }
1814
+
1190
1815
  const [ local, isGlobal ] = lookupName(scope, name);
1191
1816
 
1192
1817
  if (local === undefined) {
1193
- // todo: this should be a devtools/repl/??? only thing
1818
+ // todo: this should be a sloppy mode only thing
1194
1819
 
1195
1820
  // only allow = for this
1196
- if (decl.operator !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1821
+ if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1197
1822
 
1198
1823
  if (builtinVars[name]) {
1199
1824
  // just return rhs (eg `NaN = 2`)
@@ -1202,25 +1827,53 @@ const generateAssign = (scope, decl) => {
1202
1827
 
1203
1828
  // set global and return (eg a = 2)
1204
1829
  return [
1205
- ...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
1830
+ ...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
1206
1831
  [ Opcodes.global_get, globals[name].idx ]
1207
1832
  ];
1208
1833
  }
1209
1834
 
1210
- if (decl.operator === '=') {
1211
- typeStates[name] = getNodeType(scope, decl.right);
1212
-
1835
+ if (op === '=') {
1213
1836
  return [
1214
1837
  ...generate(scope, decl.right, isGlobal, name),
1215
1838
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1216
- [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1839
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1840
+
1841
+ ...setType(scope, name, getNodeType(scope, decl.right))
1842
+ ];
1843
+ }
1844
+
1845
+ if (op === '||' || op === '&&' || op === '??') {
1846
+ // todo: is this needed?
1847
+ // for logical assignment ops, it is not left @= right ~= left = left @ right
1848
+ // instead, left @ (left = right)
1849
+ // eg, x &&= y ~= x && (x = y)
1850
+
1851
+ return [
1852
+ ...performOp(scope, op, [
1853
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1854
+ ], [
1855
+ ...generate(scope, decl.right),
1856
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1857
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1858
+ ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1859
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1860
+
1861
+ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ],
1862
+ // hack: type is idx+1
1863
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
1217
1864
  ];
1218
1865
  }
1219
1866
 
1220
1867
  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),
1868
+ ...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
1869
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1223
- [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1870
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1871
+
1872
+ // todo: string concat types
1873
+
1874
+ // hack: type is idx+1
1875
+ ...number(TYPES.number, Valtype.i32),
1876
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
1224
1877
  ];
1225
1878
  };
1226
1879
 
@@ -1245,13 +1898,14 @@ const generateUnary = (scope, decl) => {
1245
1898
 
1246
1899
  case '!':
1247
1900
  // !=
1248
- return falsy(scope, generate(scope, decl.argument));
1901
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
1249
1902
 
1250
1903
  case '~':
1904
+ // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
1251
1905
  return [
1252
1906
  ...generate(scope, decl.argument),
1253
1907
  Opcodes.i32_to,
1254
- [ Opcodes.i32_const, signedLEB128(-1) ],
1908
+ [ Opcodes.i32_const, ...signedLEB128(-1) ],
1255
1909
  [ Opcodes.i32_xor ],
1256
1910
  Opcodes.i32_from
1257
1911
  ];
@@ -1289,11 +1943,16 @@ const generateUnary = (scope, decl) => {
1289
1943
  return out;
1290
1944
 
1291
1945
  case 'typeof':
1292
- const type = getNodeType(scope, decl.argument);
1946
+ return typeSwitch(scope, getNodeType(scope, decl.argument), {
1947
+ [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
1948
+ [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
1949
+ [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
1950
+ [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
1951
+ [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
1293
1952
 
1294
- // for custom types, just return object
1295
- if (type > 0xffffffffffff7) return number(TYPES.object);
1296
- return number(type);
1953
+ // object and internal types
1954
+ default: makeString(scope, 'object', false, '#typeof_result'),
1955
+ });
1297
1956
 
1298
1957
  default:
1299
1958
  return todo(`unary operator ${decl.operator} not implemented yet`);
@@ -1332,9 +1991,9 @@ const generateUpdate = (scope, decl) => {
1332
1991
  };
1333
1992
 
1334
1993
  const generateIf = (scope, decl) => {
1335
- const out = truthy(scope, generate(scope, decl.test), decl.test);
1994
+ const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test), false, true);
1336
1995
 
1337
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1996
+ out.push([ Opcodes.if, Blocktype.void ]);
1338
1997
  depth.push('if');
1339
1998
 
1340
1999
  const consOut = generate(scope, decl.consequent);
@@ -1356,16 +2015,28 @@ const generateIf = (scope, decl) => {
1356
2015
  };
1357
2016
 
1358
2017
  const generateConditional = (scope, decl) => {
1359
- const out = [ ...generate(scope, decl.test) ];
2018
+ const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test), false, true);
1360
2019
 
1361
- out.push(Opcodes.i32_to, [ Opcodes.if, valtypeBinary ]);
2020
+ out.push([ Opcodes.if, valtypeBinary ]);
1362
2021
  depth.push('if');
1363
2022
 
1364
2023
  out.push(...generate(scope, decl.consequent));
1365
2024
 
2025
+ // note type
2026
+ out.push(
2027
+ ...getNodeType(scope, decl.consequent),
2028
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2029
+ );
2030
+
1366
2031
  out.push([ Opcodes.else ]);
1367
2032
  out.push(...generate(scope, decl.alternate));
1368
2033
 
2034
+ // note type
2035
+ out.push(
2036
+ ...getNodeType(scope, decl.alternate),
2037
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2038
+ );
2039
+
1369
2040
  out.push([ Opcodes.end ]);
1370
2041
  depth.pop();
1371
2042
 
@@ -1422,9 +2093,148 @@ const generateWhile = (scope, decl) => {
1422
2093
  return out;
1423
2094
  };
1424
2095
 
2096
+ const generateForOf = (scope, decl) => {
2097
+ const out = [];
2098
+
2099
+ // todo: for of inside for of might fuck up?
2100
+ const pointer = localTmp(scope, 'forof_base_pointer', Valtype.i32);
2101
+ const length = localTmp(scope, 'forof_length', Valtype.i32);
2102
+ const counter = localTmp(scope, 'forof_counter', Valtype.i32);
2103
+
2104
+ out.push(
2105
+ // set pointer as right
2106
+ ...generate(scope, decl.right),
2107
+ Opcodes.i32_to_u,
2108
+ [ Opcodes.local_set, pointer ],
2109
+
2110
+ // set counter as 0 (could be already used)
2111
+ ...number(0, Valtype.i32),
2112
+ [ Opcodes.local_set, counter ],
2113
+
2114
+ // get length
2115
+ [ Opcodes.local_get, pointer ],
2116
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
2117
+ [ Opcodes.local_set, length ]
2118
+ );
2119
+
2120
+ depth.push('forof');
2121
+
2122
+ // setup local for left
2123
+ generate(scope, decl.left);
2124
+
2125
+ const leftName = decl.left.declarations[0].id.name;
2126
+ const [ local, isGlobal ] = lookupName(scope, leftName);
2127
+
2128
+ depth.push('block');
2129
+ depth.push('block');
2130
+
2131
+ // // todo: we should only do this for strings but we don't know at compile-time :(
2132
+ // hack: this is naughty and will break things!
2133
+ let newOut = number(0, Valtype.f64), newPointer = -1;
2134
+ if (pages.hasString) {
2135
+ 0, [ newOut, newPointer ] = makeArray(scope, {
2136
+ rawElements: new Array(1)
2137
+ }, isGlobal, leftName, true, 'i16');
2138
+ }
2139
+
2140
+ // set type for local
2141
+ out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2142
+ [TYPES._array]: [
2143
+ ...setType(scope, leftName, TYPES.number),
2144
+
2145
+ [ Opcodes.loop, Blocktype.void ],
2146
+
2147
+ [ Opcodes.local_get, pointer ],
2148
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2149
+
2150
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2151
+
2152
+ [ Opcodes.block, Blocktype.void ],
2153
+ [ Opcodes.block, Blocktype.void ],
2154
+ ...generate(scope, decl.body),
2155
+ [ Opcodes.end ],
2156
+
2157
+ // increment iter pointer by valtype size
2158
+ [ Opcodes.local_get, pointer ],
2159
+ ...number(ValtypeSize[valtype], Valtype.i32),
2160
+ [ Opcodes.i32_add ],
2161
+ [ Opcodes.local_set, pointer ],
2162
+
2163
+ // increment counter by 1
2164
+ [ Opcodes.local_get, counter ],
2165
+ ...number(1, Valtype.i32),
2166
+ [ Opcodes.i32_add ],
2167
+ [ Opcodes.local_tee, counter ],
2168
+
2169
+ // loop if counter != length
2170
+ [ Opcodes.local_get, length ],
2171
+ [ Opcodes.i32_ne ],
2172
+ [ Opcodes.br_if, 1 ],
2173
+
2174
+ [ Opcodes.end ],
2175
+ [ Opcodes.end ]
2176
+ ],
2177
+ [TYPES.string]: [
2178
+ ...setType(scope, leftName, TYPES.string),
2179
+
2180
+ [ Opcodes.loop, Blocktype.void ],
2181
+
2182
+ // setup new/out array
2183
+ ...newOut,
2184
+ [ Opcodes.drop ],
2185
+
2186
+ ...number(0, Valtype.i32), // base 0 for store after
2187
+
2188
+ // load current string ind {arg}
2189
+ [ Opcodes.local_get, pointer ],
2190
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2191
+
2192
+ // store to new string ind 0
2193
+ [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2194
+
2195
+ // return new string (page)
2196
+ ...number(newPointer),
2197
+
2198
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2199
+
2200
+ [ Opcodes.block, Blocktype.void ],
2201
+ [ Opcodes.block, Blocktype.void ],
2202
+ ...generate(scope, decl.body),
2203
+ [ Opcodes.end ],
2204
+
2205
+ // increment iter pointer by valtype size
2206
+ [ Opcodes.local_get, pointer ],
2207
+ ...number(ValtypeSize.i16, Valtype.i32),
2208
+ [ Opcodes.i32_add ],
2209
+ [ Opcodes.local_set, pointer ],
2210
+
2211
+ // increment counter by 1
2212
+ [ Opcodes.local_get, counter ],
2213
+ ...number(1, Valtype.i32),
2214
+ [ Opcodes.i32_add ],
2215
+ [ Opcodes.local_tee, counter ],
2216
+
2217
+ // loop if counter != length
2218
+ [ Opcodes.local_get, length ],
2219
+ [ Opcodes.i32_ne ],
2220
+ [ Opcodes.br_if, 1 ],
2221
+
2222
+ [ Opcodes.end ],
2223
+ [ Opcodes.end ]
2224
+ ],
2225
+ default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2226
+ }, Blocktype.void));
2227
+
2228
+ depth.pop();
2229
+ depth.pop();
2230
+ depth.pop();
2231
+
2232
+ return out;
2233
+ };
2234
+
1425
2235
  const getNearestLoop = () => {
1426
2236
  for (let i = depth.length - 1; i >= 0; i--) {
1427
- if (depth[i] === 'while' || depth[i] === 'for') return i;
2237
+ if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
1428
2238
  }
1429
2239
 
1430
2240
  return -1;
@@ -1508,13 +2318,25 @@ const generateAssignPat = (scope, decl) => {
1508
2318
  };
1509
2319
 
1510
2320
  let pages = new Map();
1511
- const allocPage = reason => {
1512
- if (pages.has(reason)) return pages.get(reason);
2321
+ const allocPage = (reason, type) => {
2322
+ if (pages.has(reason)) return pages.get(reason).ind;
2323
+
2324
+ if (reason.startsWith('array:')) pages.hasArray = true;
2325
+ if (reason.startsWith('string:')) pages.hasString = true;
2326
+
2327
+ const ind = pages.size;
2328
+ pages.set(reason, { ind, type });
2329
+
2330
+ if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2331
+
2332
+ return ind;
2333
+ };
1513
2334
 
1514
- let ind = pages.size;
1515
- pages.set(reason, ind);
2335
+ const freePage = reason => {
2336
+ const { ind } = pages.get(reason);
2337
+ pages.delete(reason);
1516
2338
 
1517
- if (codeLog) log('codegen', `allocated new page of memory (${ind}) | ${reason}`);
2339
+ if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
1518
2340
 
1519
2341
  return ind;
1520
2342
  };
@@ -1528,7 +2350,7 @@ const itemTypeToValtype = {
1528
2350
  i16: 'i32'
1529
2351
  };
1530
2352
 
1531
- const storeOps = {
2353
+ const StoreOps = {
1532
2354
  i32: Opcodes.i32_store,
1533
2355
  i64: Opcodes.i64_store,
1534
2356
  f64: Opcodes.f64_store,
@@ -1537,13 +2359,32 @@ const storeOps = {
1537
2359
  i16: Opcodes.i32_store16
1538
2360
  };
1539
2361
 
2362
+ let data = [];
2363
+
2364
+ const compileBytes = (val, itemType, signed = true) => {
2365
+ // todo: this is a mess and needs confirming / ????
2366
+ switch (itemType) {
2367
+ case 'i8': return [ val % 256 ];
2368
+ case 'i16': return [ val % 256, Math.floor(val / 256) ];
2369
+
2370
+ case 'i32':
2371
+ case 'i64':
2372
+ return enforceFourBytes(signedLEB128(val));
2373
+
2374
+ case 'f64': return ieee754_binary64(val);
2375
+ }
2376
+ };
2377
+
1540
2378
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
1541
2379
  const out = [];
1542
2380
 
2381
+ let firstAssign = false;
1543
2382
  if (!arrays.has(name) || name === '$undeclared') {
2383
+ firstAssign = true;
2384
+
1544
2385
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1545
2386
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1546
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`) * pageSize);
2387
+ arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
1547
2388
  }
1548
2389
 
1549
2390
  const pointer = arrays.get(name);
@@ -1551,8 +2392,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1551
2392
  const useRawElements = !!decl.rawElements;
1552
2393
  const elements = useRawElements ? decl.rawElements : decl.elements;
1553
2394
 
2395
+ const valtype = itemTypeToValtype[itemType];
1554
2396
  const length = elements.length;
1555
2397
 
2398
+ if (firstAssign && useRawElements) {
2399
+ let bytes = compileBytes(length, 'i32');
2400
+
2401
+ if (!initEmpty) for (let i = 0; i < length; i++) {
2402
+ if (elements[i] == null) continue;
2403
+
2404
+ bytes.push(...compileBytes(elements[i], itemType));
2405
+ }
2406
+
2407
+ data.push({
2408
+ offset: pointer,
2409
+ bytes
2410
+ });
2411
+
2412
+ // local value as pointer
2413
+ out.push(...number(pointer));
2414
+
2415
+ return [ out, pointer ];
2416
+ }
2417
+
1556
2418
  // store length as 0th array
1557
2419
  out.push(
1558
2420
  ...number(0, Valtype.i32),
@@ -1560,8 +2422,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1560
2422
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
1561
2423
  );
1562
2424
 
1563
- const storeOp = storeOps[itemType];
1564
- const valtype = itemTypeToValtype[itemType];
2425
+ const storeOp = StoreOps[itemType];
1565
2426
 
1566
2427
  if (!initEmpty) for (let i = 0; i < length; i++) {
1567
2428
  if (elements[i] == null) continue;
@@ -1576,30 +2437,34 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1576
2437
  // local value as pointer
1577
2438
  out.push(...number(pointer));
1578
2439
 
1579
- scope.memory = true;
2440
+ return [ out, pointer ];
2441
+ };
1580
2442
 
1581
- return out;
2443
+ const makeString = (scope, str, global = false, name = '$undeclared') => {
2444
+ const rawElements = new Array(str.length);
2445
+ for (let i = 0; i < str.length; i++) {
2446
+ rawElements[i] = str.charCodeAt(i);
2447
+ }
2448
+
2449
+ return makeArray(scope, {
2450
+ rawElements
2451
+ }, global, name, false, 'i16')[0];
1582
2452
  };
1583
2453
 
1584
2454
  let arrays = new Map();
1585
2455
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
1586
- return makeArray(scope, decl, global, name, initEmpty, valtype);
2456
+ return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
1587
2457
  };
1588
2458
 
1589
2459
  export const generateMember = (scope, decl, _global, _name) => {
1590
- const type = getNodeType(scope, decl.object);
2460
+ const name = decl.object.name;
2461
+ const pointer = arrays.get(name);
2462
+
2463
+ const aotPointer = pointer != null;
1591
2464
 
1592
2465
  // hack: .length
1593
2466
  if (decl.property.name === 'length') {
1594
2467
  // 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
2468
  return [
1604
2469
  ...(aotPointer ? number(0, Valtype.i32) : [
1605
2470
  ...generate(scope, decl.object),
@@ -1611,18 +2476,17 @@ export const generateMember = (scope, decl, _global, _name) => {
1611
2476
  ];
1612
2477
  }
1613
2478
 
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;
2479
+ // // todo: we should only do this for strings but we don't know at compile-time :(
2480
+ // hack: this is naughty and will break things!
2481
+ let newOut = number(0, Valtype.f64), newPointer = -1;
2482
+ if (pages.hasString) {
2483
+ 0, [ newOut, newPointer ] = makeArray(scope, {
2484
+ rawElements: new Array(1)
2485
+ }, _global, _name, true, 'i16');
2486
+ }
1623
2487
 
1624
- if (type === TYPES._array) {
1625
- return [
2488
+ return typeSwitch(scope, getNodeType(scope, decl.object), {
2489
+ [TYPES._array]: [
1626
2490
  // get index as valtype
1627
2491
  ...generate(scope, decl.property),
1628
2492
 
@@ -1638,45 +2502,46 @@ export const generateMember = (scope, decl, _global, _name) => {
1638
2502
  ]),
1639
2503
 
1640
2504
  // read from memory
1641
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1642
- ];
1643
- }
2505
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
1644
2506
 
1645
- // string
2507
+ ...number(TYPES.number, Valtype.i32),
2508
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2509
+ ],
1646
2510
 
1647
- const newOut = makeArray(scope, {
1648
- rawElements: new Array(1)
1649
- }, _global, _name, true, 'i16');
1650
- const newPointer = arrays.get(_name ?? '$undeclared');
2511
+ [TYPES.string]: [
2512
+ // setup new/out array
2513
+ ...newOut,
2514
+ [ Opcodes.drop ],
1651
2515
 
1652
- return [
1653
- // setup new/out array
1654
- ...newOut,
1655
- [ Opcodes.drop ],
2516
+ ...number(0, Valtype.i32), // base 0 for store later
1656
2517
 
1657
- ...number(0, Valtype.i32), // base 0 for store later
2518
+ ...generate(scope, decl.property),
1658
2519
 
1659
- ...generate(scope, decl.property),
2520
+ Opcodes.i32_to_u,
2521
+ ...number(ValtypeSize.i16, Valtype.i32),
2522
+ [ Opcodes.i32_mul ],
1660
2523
 
1661
- Opcodes.i32_to_u,
1662
- ...number(ValtypeSize.i16, Valtype.i32),
1663
- [ Opcodes.i32_mul ],
2524
+ ...(aotPointer ? [] : [
2525
+ ...generate(scope, decl.object),
2526
+ Opcodes.i32_to_u,
2527
+ [ Opcodes.i32_add ]
2528
+ ]),
1664
2529
 
1665
- ...(aotPointer ? [] : [
1666
- ...generate(scope, decl.object),
1667
- Opcodes.i32_to_u,
1668
- [ Opcodes.i32_add ]
1669
- ]),
2530
+ // load current string ind {arg}
2531
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
1670
2532
 
1671
- // load current string ind {arg}
1672
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2533
+ // store to new string ind 0
2534
+ [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
1673
2535
 
1674
- // store to new string ind 0
1675
- [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2536
+ // return new string (page)
2537
+ ...number(newPointer),
1676
2538
 
1677
- // return new string (page)
1678
- ...number(newPointer)
1679
- ];
2539
+ ...number(TYPES.string, Valtype.i32),
2540
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2541
+ ],
2542
+
2543
+ default: [ [ Opcodes.unreachable ] ]
2544
+ });
1680
2545
  };
1681
2546
 
1682
2547
  const randId = () => Math.random().toString(16).slice(0, -4);
@@ -1728,15 +2593,14 @@ const generateFunc = (scope, decl) => {
1728
2593
  const innerScope = {
1729
2594
  locals: {},
1730
2595
  localInd: 0,
1731
- returns: [ valtypeBinary ],
1732
- memory: false,
2596
+ // value, type
2597
+ returns: [ valtypeBinary, Valtype.i32 ],
1733
2598
  throws: false,
1734
2599
  name
1735
2600
  };
1736
2601
 
1737
2602
  for (let i = 0; i < params.length; i++) {
1738
- const param = params[i];
1739
- innerScope.locals[param] = { idx: innerScope.localInd++, type: valtypeBinary };
2603
+ allocVar(innerScope, params[i], false);
1740
2604
  }
1741
2605
 
1742
2606
  let body = objectHack(decl.body);
@@ -1751,11 +2615,9 @@ const generateFunc = (scope, decl) => {
1751
2615
  const wasm = generate(innerScope, body);
1752
2616
  const func = {
1753
2617
  name,
1754
- params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
2618
+ params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
1755
2619
  returns: innerScope.returns,
1756
- returnType: innerScope.returnType ?? TYPES.number,
1757
2620
  locals: innerScope.locals,
1758
- memory: innerScope.memory,
1759
2621
  throws: innerScope.throws,
1760
2622
  index: currentFuncIndex++
1761
2623
  };
@@ -1768,8 +2630,13 @@ const generateFunc = (scope, decl) => {
1768
2630
  }
1769
2631
  }
1770
2632
 
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 ]);
2633
+ // add end return if not found
2634
+ if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
2635
+ wasm.push(
2636
+ ...number(0),
2637
+ ...number(TYPES.undefined, Valtype.i32),
2638
+ [ Opcodes.return ]
2639
+ );
1773
2640
  }
1774
2641
 
1775
2642
  // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
@@ -1780,9 +2647,7 @@ const generateFunc = (scope, decl) => {
1780
2647
  if (local.type === Valtype.v128) {
1781
2648
  vecParams++;
1782
2649
 
1783
- /* func.memory = true; // mark func as using memory
1784
-
1785
- wasm.unshift( // add v128 load for param
2650
+ /* wasm.unshift( // add v128 load for param
1786
2651
  [ Opcodes.i32_const, 0 ],
1787
2652
  [ ...Opcodes.v128_load, 0, i * 16 ],
1788
2653
  [ Opcodes.local_set, local.idx ]
@@ -1893,10 +2758,10 @@ const generateFunc = (scope, decl) => {
1893
2758
  };
1894
2759
 
1895
2760
  const generateCode = (scope, decl) => {
1896
- const out = [];
2761
+ let out = [];
1897
2762
 
1898
2763
  for (const x of decl.body) {
1899
- out.push(...generate(scope, x));
2764
+ out = out.concat(generate(scope, x));
1900
2765
  }
1901
2766
 
1902
2767
  return out;
@@ -1912,10 +2777,9 @@ const internalConstrs = {
1912
2777
 
1913
2778
  // new Array(n)
1914
2779
 
1915
- makeArray(scope, {
2780
+ const [ , pointer ] = makeArray(scope, {
1916
2781
  rawElements: new Array(0)
1917
2782
  }, global, name, true);
1918
- const pointer = arrays.get(name ?? '$undeclared');
1919
2783
 
1920
2784
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
1921
2785
 
@@ -1933,9 +2797,38 @@ const internalConstrs = {
1933
2797
  ];
1934
2798
  },
1935
2799
  type: TYPES._array
2800
+ },
2801
+
2802
+ __Array_of: {
2803
+ // this is not a constructor but best fits internal structure here
2804
+ generate: (scope, decl, global, name) => {
2805
+ // Array.of(i0, i1, ...)
2806
+ return generateArray(scope, {
2807
+ elements: decl.arguments
2808
+ }, global, name);
2809
+ },
2810
+ type: TYPES._array,
2811
+ notConstr: true
1936
2812
  }
1937
2813
  };
1938
2814
 
2815
+ // const _ = Array.prototype.push;
2816
+ // Array.prototype.push = function (a) {
2817
+ // const check = arr => {
2818
+ // for (const x of arr) {
2819
+ // if (x === undefined) {
2820
+ // console.trace(arr);
2821
+ // process.exit();
2822
+ // }
2823
+ // if (Array.isArray(x)) check(x);
2824
+ // }
2825
+ // };
2826
+ // if (Array.isArray(a) && !new Error().stack.includes('node:')) check(a);
2827
+ // // if (Array.isArray(a)) check(a);
2828
+
2829
+ // return _.apply(this, arguments);
2830
+ // };
2831
+
1939
2832
  export default program => {
1940
2833
  globals = {};
1941
2834
  globalInd = 0;
@@ -1944,9 +2837,9 @@ export default program => {
1944
2837
  funcs = [];
1945
2838
  funcIndex = {};
1946
2839
  depth = [];
1947
- typeStates = {};
1948
2840
  arrays = new Map();
1949
2841
  pages = new Map();
2842
+ data = [];
1950
2843
  currentFuncIndex = importedFuncs.length;
1951
2844
 
1952
2845
  globalThis.valtype = 'f64';
@@ -1996,18 +2889,20 @@ export default program => {
1996
2889
  body: program.body
1997
2890
  };
1998
2891
 
2892
+ if (process.argv.includes('-ast-log')) console.log(program.body.body);
2893
+
1999
2894
  generateFunc(scope, program);
2000
2895
 
2001
2896
  const main = funcs[funcs.length - 1];
2002
2897
  main.export = true;
2003
- main.returns = [ valtypeBinary ];
2898
+ main.returns = [ valtypeBinary, Valtype.i32 ];
2004
2899
 
2005
2900
  const lastInst = main.wasm[main.wasm.length - 1] ?? [ Opcodes.end ];
2006
2901
  if (lastInst[0] === Opcodes.drop) {
2007
2902
  main.wasm.splice(main.wasm.length - 1, 1);
2008
2903
 
2009
2904
  const finalStatement = program.body.body[program.body.body.length - 1];
2010
- main.returnType = getNodeType(main, finalStatement);
2905
+ main.wasm.push(...getNodeType(main, finalStatement));
2011
2906
  }
2012
2907
 
2013
2908
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
@@ -2023,5 +2918,5 @@ export default program => {
2023
2918
  // if blank main func and other exports, remove it
2024
2919
  if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
2025
2920
 
2026
- return { funcs, globals, tags, exceptions, pages };
2921
+ return { funcs, globals, tags, exceptions, pages, data };
2027
2922
  };