porffor 0.0.0-650350 → 0.0.0-679c4ea

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,11 @@
1
1
  import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
2
- import { signedLEB128, unsignedLEB128 } from "./encoding.js";
2
+ import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
3
3
  import { operatorOpcode } from "./expression.js";
4
4
  import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
5
5
  import { PrototypeFuncs } from "./prototype.js";
6
- import { number, i32x4 } from "./embedding.js";
6
+ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enforceEightBytes } from "./embedding.js";
7
7
  import parse from "./parse.js";
8
+ import * as Rhemyn from "../rhemyn/compile.js";
8
9
 
9
10
  let globals = {};
10
11
  let globalInd = 0;
@@ -35,7 +36,14 @@ const debug = str => {
35
36
  };
36
37
 
37
38
  const todo = msg => {
38
- throw new Error(`todo: ${msg}`);
39
+ class TodoError extends Error {
40
+ constructor(message) {
41
+ super(message);
42
+ this.name = 'TodoError';
43
+ }
44
+ }
45
+
46
+ throw new TodoError(`todo: ${msg}`);
39
47
 
40
48
  const code = [];
41
49
 
@@ -101,6 +109,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
101
109
  case 'WhileStatement':
102
110
  return generateWhile(scope, decl);
103
111
 
112
+ case 'ForOfStatement':
113
+ return generateForOf(scope, decl);
114
+
104
115
  case 'BreakStatement':
105
116
  return generateBreak(scope, decl);
106
117
 
@@ -141,45 +152,65 @@ const generate = (scope, decl, global = false, name = undefined) => {
141
152
 
142
153
  return [];
143
154
 
144
- case 'TaggedTemplateExpression':
145
- // hack for inline asm
146
- if (decl.tag.name !== 'asm') return todo('tagged template expressions not implemented');
155
+ case 'TaggedTemplateExpression': {
156
+ const funcs = {
157
+ asm: str => {
158
+ let out = [];
147
159
 
148
- const str = decl.quasi.quasis[0].value.raw;
149
- let out = [];
160
+ for (const line of str.split('\n')) {
161
+ const asm = line.trim().split(';;')[0].split(' ');
162
+ if (asm[0] === '') continue; // blank
150
163
 
151
- for (const line of str.split('\n')) {
152
- const asm = line.trim().split(';;')[0].split(' ');
153
- if (asm[0] === '') continue; // blank
164
+ if (asm[0] === 'local') {
165
+ const [ name, idx, type ] = asm.slice(1);
166
+ scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
167
+ continue;
168
+ }
154
169
 
155
- if (asm[0] === 'local') {
156
- const [ name, idx, type ] = asm.slice(1);
157
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
158
- continue;
159
- }
170
+ if (asm[0] === 'returns') {
171
+ scope.returns = asm.slice(1).map(x => Valtype[x]);
172
+ continue;
173
+ }
160
174
 
161
- if (asm[0] === 'returns') {
162
- scope.returns = asm.slice(1).map(x => Valtype[x]);
163
- continue;
164
- }
175
+ if (asm[0] === 'memory') {
176
+ allocPage('asm instrinsic');
177
+ // todo: add to store/load offset insts
178
+ continue;
179
+ }
165
180
 
166
- if (asm[0] === 'memory') {
167
- scope.memory = true;
168
- allocPage('asm instrinsic');
169
- // todo: add to store/load offset insts
170
- continue;
171
- }
181
+ let inst = Opcodes[asm[0].replace('.', '_')];
182
+ if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
183
+
184
+ if (!Array.isArray(inst)) inst = [ inst ];
185
+ const immediates = asm.slice(1).map(x => parseInt(x));
172
186
 
173
- let inst = Opcodes[asm[0].replace('.', '_')];
174
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
187
+ out.push([ ...inst, ...immediates ]);
188
+ }
189
+
190
+ return out;
191
+ },
192
+
193
+ __internal_print_type: str => {
194
+ const type = getType(scope, str) - TYPES.number;
175
195
 
176
- if (!Array.isArray(inst)) inst = [ inst ];
177
- const immediates = asm.slice(1).map(x => parseInt(x));
196
+ return [
197
+ ...number(type),
198
+ [ Opcodes.call, importedFuncs.print ],
178
199
 
179
- out.push([ ...inst, ...immediates ]);
200
+ // newline
201
+ ...number(10),
202
+ [ Opcodes.call, importedFuncs.printChar ]
203
+ ];
204
+ }
180
205
  }
181
206
 
182
- return out;
207
+ const name = decl.tag.name;
208
+ // hack for inline asm
209
+ if (!funcs[name]) return todo('tagged template expressions not implemented');
210
+
211
+ const str = decl.quasi.quasis[0].value.raw;
212
+ return funcs[name](str);
213
+ }
183
214
 
184
215
  default:
185
216
  return todo(`no generation for ${decl.type}!`);
@@ -278,7 +309,7 @@ const generateReturn = (scope, decl) => {
278
309
  ];
279
310
  }
280
311
 
281
- if (!scope.returnType) scope.returnType = getNodeType(scope, decl.argument);
312
+ scope.returnType = getNodeType(scope, decl.argument);
282
313
 
283
314
  return [
284
315
  ...generate(scope, decl.argument),
@@ -295,11 +326,11 @@ const localTmp = (scope, name, type = valtypeBinary) => {
295
326
  return idx;
296
327
  };
297
328
 
298
- const performLogicOp = (scope, op, left, right) => {
329
+ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
299
330
  const checks = {
300
- '||': Opcodes.eqz,
301
- '&&': [ Opcodes.i32_to ]
302
- // todo: ??
331
+ '||': falsy,
332
+ '&&': truthy,
333
+ '??': nullish
303
334
  };
304
335
 
305
336
  if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
@@ -310,7 +341,8 @@ const performLogicOp = (scope, op, left, right) => {
310
341
  return [
311
342
  ...left,
312
343
  [ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
313
- ...checks[op],
344
+ ...checks[op](scope, [], leftType),
345
+ Opcodes.i32_to,
314
346
  [ Opcodes.if, valtypeBinary ],
315
347
  ...right,
316
348
  [ Opcodes.else ],
@@ -325,15 +357,13 @@ const concatStrings = (scope, left, right, global, name, assign) => {
325
357
  // todo: optimize by looking up names in arrays and using that if exists?
326
358
  // todo: optimize this if using literals/known lengths?
327
359
 
328
- scope.memory = true;
329
-
330
- const pointer = arrays.get(name ?? '$undeclared');
331
-
332
360
  const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
333
361
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
334
362
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
335
363
 
336
364
  if (assign) {
365
+ const pointer = arrays.get(name ?? '$undeclared');
366
+
337
367
  return [
338
368
  // setup right
339
369
  ...right,
@@ -384,15 +414,12 @@ const concatStrings = (scope, left, right, global, name, assign) => {
384
414
 
385
415
  const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
386
416
 
387
- const newOut = makeArray(scope, {
417
+ // alloc/assign array
418
+ const [ , pointer ] = makeArray(scope, {
388
419
  rawElements: new Array(0)
389
420
  }, global, name, true, 'i16');
390
421
 
391
422
  return [
392
- // setup new/out array
393
- ...newOut,
394
- [ Opcodes.drop ],
395
-
396
423
  // setup left
397
424
  ...left,
398
425
  Opcodes.i32_to_u,
@@ -458,89 +485,261 @@ const concatStrings = (scope, left, right, global, name, assign) => {
458
485
  ];
459
486
  };
460
487
 
461
- const falsy = (scope, wasm, type) => {
488
+ const compareStrings = (scope, left, right) => {
489
+ // todo: this should be rewritten into a built-in/func: String.prototype.concat
490
+ // todo: convert left and right to strings if not
491
+ // todo: optimize by looking up names in arrays and using that if exists?
492
+ // todo: optimize this if using literals/known lengths?
493
+
494
+ const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
495
+ const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
496
+ const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
497
+ const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
498
+
499
+ const index = localTmp(scope, 'compare_index', Valtype.i32);
500
+ const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
501
+
502
+ return [
503
+ // setup left
504
+ ...left,
505
+ Opcodes.i32_to_u,
506
+ [ Opcodes.local_tee, leftPointer ],
507
+
508
+ // setup right
509
+ ...right,
510
+ Opcodes.i32_to_u,
511
+ [ Opcodes.local_tee, rightPointer ],
512
+
513
+ // fast path: check leftPointer == rightPointer
514
+ // use if (block) for everything after to "return" a value early
515
+ [ Opcodes.i32_ne ],
516
+ [ Opcodes.if, Valtype.i32 ],
517
+
518
+ // get lengths
519
+ [ Opcodes.local_get, leftPointer ],
520
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
521
+ [ Opcodes.local_tee, leftLength ],
522
+
523
+ [ Opcodes.local_get, rightPointer ],
524
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
525
+ [ Opcodes.local_tee, rightLength ],
526
+
527
+ // fast path: check leftLength != rightLength
528
+ [ Opcodes.i32_ne ],
529
+ [ Opcodes.if, Blocktype.void ],
530
+ ...number(0, Valtype.i32),
531
+ [ Opcodes.br, 1 ],
532
+ [ Opcodes.end ],
533
+
534
+ // no fast path for length = 0 as it would probably be slower for most of the time?
535
+
536
+ // setup index end as length * sizeof i16 (2)
537
+ // we do this instead of having to do mul/div each iter for perf™
538
+ [ Opcodes.local_get, leftLength ],
539
+ ...number(ValtypeSize.i16, Valtype.i32),
540
+ [ Opcodes.i32_mul ],
541
+ [ Opcodes.local_set, indexEnd ],
542
+
543
+ // iterate over each char and check if eq
544
+ [ Opcodes.loop, Blocktype.void ],
545
+
546
+ // fetch left
547
+ [ Opcodes.local_get, index ],
548
+ [ Opcodes.local_get, leftPointer ],
549
+ [ Opcodes.i32_add ],
550
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
551
+
552
+ // fetch right
553
+ [ Opcodes.local_get, index ],
554
+ [ Opcodes.local_get, rightPointer ],
555
+ [ Opcodes.i32_add ],
556
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
557
+
558
+ // not equal, "return" false
559
+ [ Opcodes.i32_ne ],
560
+ [ Opcodes.if, Blocktype.void ],
561
+ ...number(0, Valtype.i32),
562
+ [ Opcodes.br, 2 ],
563
+ [ Opcodes.end ],
564
+
565
+ // index += sizeof i16 (2)
566
+ [ Opcodes.local_get, index ],
567
+ ...number(ValtypeSize.i16, Valtype.i32),
568
+ [ Opcodes.i32_add ],
569
+ [ Opcodes.local_tee, index ],
570
+
571
+ // if index != index end (length * sizeof 16), loop
572
+ [ Opcodes.local_get, indexEnd ],
573
+ [ Opcodes.i32_ne ],
574
+ [ Opcodes.br_if, 0 ],
575
+ [ Opcodes.end ],
576
+
577
+ // no failed checks, so true!
578
+ ...number(1, Valtype.i32),
579
+
580
+ // pointers match, so true
581
+ [ Opcodes.else ],
582
+ ...number(1, Valtype.i32),
583
+ [ Opcodes.end ],
584
+
585
+ // convert i32 result to valtype
586
+ // do not do as automatically added by binary exp gen for equality ops
587
+ // Opcodes.i32_from_u
588
+ ];
589
+ };
590
+
591
+ const truthy = (scope, wasm, type) => {
462
592
  // arrays are always truthy
463
593
  if (type === TYPES._array) return [
464
594
  ...wasm,
465
595
  [ Opcodes.drop ],
466
- number(0)
596
+ ...number(1)
467
597
  ];
468
598
 
469
599
  if (type === TYPES.string) {
470
- // if "" (length = 0)
600
+ // if not "" (length = 0)
471
601
  return [
472
602
  // pointer
473
603
  ...wasm,
604
+ Opcodes.i32_to_u,
474
605
 
475
606
  // get length
476
607
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
477
608
 
478
- // if length == 0
479
- [ Opcodes.i32_eqz ],
609
+ // if length != 0
610
+ /* [ Opcodes.i32_eqz ],
611
+ [ Opcodes.i32_eqz ], */
480
612
  Opcodes.i32_from_u
481
613
  ]
482
614
  }
483
615
 
484
- // if = 0
616
+ // if != 0
485
617
  return [
486
618
  ...wasm,
487
619
 
488
- ...Opcodes.eqz,
489
- Opcodes.i32_from_u
620
+ /* Opcodes.eqz,
621
+ [ Opcodes.i32_eqz ],
622
+ Opcodes.i32_from */
490
623
  ];
491
624
  };
492
625
 
493
- const truthy = (scope, wasm, type) => {
626
+ const falsy = (scope, wasm, type) => {
494
627
  // arrays are always truthy
495
628
  if (type === TYPES._array) return [
496
629
  ...wasm,
497
630
  [ Opcodes.drop ],
498
- number(1)
631
+ ...number(0)
499
632
  ];
500
633
 
501
634
  if (type === TYPES.string) {
502
- // if not "" (length = 0)
635
+ // if "" (length = 0)
503
636
  return [
504
637
  // pointer
505
638
  ...wasm,
639
+ Opcodes.i32_to_u,
506
640
 
507
641
  // get length
508
642
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
509
643
 
510
- // if length != 0
511
- /* [ Opcodes.i32_eqz ],
512
- [ Opcodes.i32_eqz ], */
644
+ // if length == 0
645
+ [ Opcodes.i32_eqz ],
513
646
  Opcodes.i32_from_u
514
647
  ]
515
648
  }
516
649
 
517
- // if != 0
650
+ // if = 0
518
651
  return [
519
652
  ...wasm,
520
653
 
521
- /* Opcodes.eqz,
522
- [ Opcodes.i32_eqz ],
523
- Opcodes.i32_from */
654
+ ...Opcodes.eqz,
655
+ Opcodes.i32_from_u
524
656
  ];
525
657
  };
526
658
 
527
- const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$unspecified', assign = false) => {
659
+ const nullish = (scope, wasm, type) => {
660
+ // undefined
661
+ if (type === TYPES.undefined) return [
662
+ ...wasm,
663
+ [ Opcodes.drop ],
664
+ ...number(1)
665
+ ];
666
+
667
+ // null (if object and = "0")
668
+ if (type === TYPES.object) return [
669
+ ...wasm,
670
+ ...Opcodes.eqz,
671
+ Opcodes.i32_from_u
672
+ ];
673
+
674
+ // not
675
+ return [
676
+ ...wasm,
677
+ [ Opcodes.drop ],
678
+ ...number(0)
679
+ ];
680
+ };
681
+
682
+ const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
528
683
  if (op === '||' || op === '&&' || op === '??') {
529
- return performLogicOp(scope, op, left, right);
684
+ return performLogicOp(scope, op, left, right, leftType, rightType);
685
+ }
686
+
687
+ if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
688
+
689
+ const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
690
+
691
+ if (leftType && rightType && (
692
+ // if strict (in)equal and known types mismatch, return false (===)/true (!==)
693
+ ((op === '===' || op === '!==') && leftType !== rightType) ||
694
+
695
+ // if equality op and an operand is undefined, return false
696
+ (eqOp && leftType === TYPES.undefined ^ rightType === TYPES.undefined)
697
+ )) {
698
+ // undefined == null
699
+ if (((leftType === TYPES.undefined && rightType === TYPES.object) || (leftType === TYPES.object && rightType === TYPES.undefined)) && (op === '==' || op === '!=')) return [
700
+ ...(leftType === TYPES.object ? left : right),
701
+ ...Opcodes.eqz,
702
+ ...(op === '!=' ? [ [ Opcodes.i32_eqz ] ] : [])
703
+ ];
704
+
705
+ return [
706
+ ...left,
707
+ [ Opcodes.drop ],
708
+
709
+ ...right,
710
+ [ Opcodes.drop ],
711
+
712
+ // return true (!=/!==) or false (else)
713
+ ...number(op === '!=' || op === '!==' ? 1 : 0, Valtype.i32)
714
+ ];
530
715
  }
531
716
 
717
+ // todo: niche null hell with 0
718
+
532
719
  if (leftType === TYPES.string || rightType === TYPES.string) {
533
720
  if (op === '+') {
534
721
  // string concat (a + b)
535
722
  return concatStrings(scope, left, right, _global, _name, assign);
536
723
  }
537
724
 
538
- // any other math op, NaN
539
- if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
725
+ // not an equality op, NaN
726
+ if (!eqOp) return number(NaN);
540
727
 
541
728
  // else leave bool ops
542
- // todo: convert string to number if string and number or le/ge op
543
- // todo: string equality
729
+ // todo: convert string to number if string and number/bool
730
+ // todo: string (>|>=|<|<=) string
731
+
732
+ // string comparison
733
+ if (op === '===' || op === '==') {
734
+ return compareStrings(scope, left, right);
735
+ }
736
+
737
+ if (op === '!==' || op === '!=') {
738
+ return [
739
+ ...compareStrings(scope, left, right),
740
+ [ Opcodes.i32_eqz ]
741
+ ];
742
+ }
544
743
  }
545
744
 
546
745
  let ops = operatorOpcode[valtype][op];
@@ -570,16 +769,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
570
769
  };
571
770
 
572
771
  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
- ];
772
+ 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
773
 
577
774
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
578
775
 
579
776
  return out;
580
777
  };
581
778
 
582
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, memory, localNames = [], globalNames = [] }) => {
779
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
583
780
  const existing = funcs.find(x => x.name === name);
584
781
  if (existing) return existing;
585
782
 
@@ -615,7 +812,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
615
812
  returns,
616
813
  returnType: TYPES[returnType ?? 'number'],
617
814
  wasm,
618
- memory,
619
815
  internal: true,
620
816
  index: currentFuncIndex++
621
817
  };
@@ -634,7 +830,7 @@ const includeBuiltin = (scope, builtin) => {
634
830
  };
635
831
 
636
832
  const generateLogicExp = (scope, decl) => {
637
- return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
833
+ return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
638
834
  };
639
835
 
640
836
  const TYPES = {
@@ -648,7 +844,8 @@ const TYPES = {
648
844
  bigint: 0xffffffffffff7,
649
845
 
650
846
  // these are not "typeof" types but tracked internally
651
- _array: 0xffffffffffff8
847
+ _array: 0xfffffffffff0f,
848
+ _regexp: 0xfffffffffff1f
652
849
  };
653
850
 
654
851
  const TYPE_NAMES = {
@@ -682,6 +879,9 @@ const getType = (scope, _name) => {
682
879
 
683
880
  const getNodeType = (scope, node) => {
684
881
  if (node.type === 'Literal') {
882
+ if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
883
+ if (node.regex) return TYPES._regexp;
884
+
685
885
  return TYPES[typeof node.value];
686
886
  }
687
887
 
@@ -696,7 +896,7 @@ const getNodeType = (scope, node) => {
696
896
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
697
897
  const name = node.callee.name;
698
898
  const func = funcs.find(x => x.name === name);
699
- if (func) return func.returnType ?? TYPES.number;
899
+ if (func) return func.returnType;
700
900
 
701
901
  if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
702
902
  if (internalConstrs[name]) return internalConstrs[name].type;
@@ -715,15 +915,18 @@ const getNodeType = (scope, node) => {
715
915
 
716
916
  // literal.func()
717
917
  if (!name && node.callee.type === 'MemberExpression') {
918
+ if (node.callee.object.regex) {
919
+ const funcName = node.callee.property.name;
920
+ return Rhemyn[funcName] ? TYPES.boolean : TYPES.undefined;
921
+ }
922
+
718
923
  const baseType = getNodeType(scope, node.callee.object);
719
924
 
720
925
  const func = node.callee.property.name;
721
926
  protoFunc = prototypeFuncs[baseType]?.[func];
722
927
  }
723
928
 
724
- if (protoFunc) return protoFunc.returnType ?? TYPES.number;
725
-
726
- return TYPES.number;
929
+ if (protoFunc) return protoFunc.returnType;
727
930
  }
728
931
 
729
932
  if (node.type === 'ExpressionStatement') {
@@ -755,14 +958,16 @@ const getNodeType = (scope, node) => {
755
958
 
756
959
  if (objectType === TYPES.string && node.computed) return TYPES.string;
757
960
  }
758
-
759
- // default to number
760
- return TYPES.number;
761
961
  };
762
962
 
763
963
  const generateLiteral = (scope, decl, global, name) => {
764
964
  if (decl.value === null) return number(NULL);
765
965
 
966
+ if (decl.regex) {
967
+ scope.regex[name] = decl.regex;
968
+ return number(1);
969
+ }
970
+
766
971
  switch (typeof decl.value) {
767
972
  case 'number':
768
973
  return number(decl.value);
@@ -792,7 +997,7 @@ const generateLiteral = (scope, decl, global, name) => {
792
997
 
793
998
  return makeArray(scope, {
794
999
  rawElements
795
- }, global, name, false, 'i16');
1000
+ }, global, name, false, 'i16')[0];
796
1001
 
797
1002
  default:
798
1003
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -802,7 +1007,8 @@ const generateLiteral = (scope, decl, global, name) => {
802
1007
  const countLeftover = wasm => {
803
1008
  let count = 0, depth = 0;
804
1009
 
805
- for (const inst of wasm) {
1010
+ for (let i = 0; i < wasm.length; i++) {
1011
+ const inst = wasm[i];
806
1012
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
807
1013
  if (inst[0] === Opcodes.if) count--;
808
1014
  if (inst[1] !== Blocktype.void) count++;
@@ -823,6 +1029,8 @@ const countLeftover = wasm => {
823
1029
  } else count--;
824
1030
  if (func) count += func.returns.length;
825
1031
  } else count--;
1032
+
1033
+ // console.log(count, decompile([ inst ]).slice(0, -1));
826
1034
  }
827
1035
 
828
1036
  return count;
@@ -906,7 +1114,7 @@ const generateCall = (scope, decl, _global, _name) => {
906
1114
  }
907
1115
 
908
1116
  let out = [];
909
- let protoFunc, protoName, baseType, baseName = '$undeclared';
1117
+ let protoFunc, protoName, baseType, baseName;
910
1118
  // ident.func()
911
1119
  if (name && name.startsWith('__')) {
912
1120
  const spl = name.slice(2).split('_');
@@ -921,6 +1129,25 @@ const generateCall = (scope, decl, _global, _name) => {
921
1129
 
922
1130
  // literal.func()
923
1131
  if (!name && decl.callee.type === 'MemberExpression') {
1132
+ // megahack for /regex/.func()
1133
+ if (decl.callee.object.regex) {
1134
+ const funcName = decl.callee.property.name;
1135
+ const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1136
+
1137
+ funcIndex[func.name] = func.index;
1138
+ funcs.push(func);
1139
+
1140
+ return [
1141
+ // make string arg
1142
+ ...generate(scope, decl.arguments[0]),
1143
+
1144
+ // call regex func
1145
+ Opcodes.i32_to_u,
1146
+ [ Opcodes.call, func.index ],
1147
+ Opcodes.i32_from
1148
+ ];
1149
+ }
1150
+
924
1151
  baseType = getNodeType(scope, decl.callee.object);
925
1152
 
926
1153
  const func = decl.callee.property.name;
@@ -929,11 +1156,36 @@ const generateCall = (scope, decl, _global, _name) => {
929
1156
 
930
1157
  out = generate(scope, decl.callee.object);
931
1158
  out.push([ Opcodes.drop ]);
1159
+
1160
+ baseName = [...arrays.keys()].pop();
932
1161
  }
933
1162
 
934
- if (protoFunc) {
935
- scope.memory = true;
1163
+ if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
1164
+ const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
1165
+
1166
+ funcIndex[func.name] = func.index;
1167
+ funcs.push(func);
1168
+
1169
+ const pointer = arrays.get(baseName);
1170
+ const [ local, isGlobal ] = lookupName(scope, baseName);
1171
+
1172
+ return [
1173
+ ...out,
1174
+
1175
+ ...(pointer == null ? [
1176
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1177
+ Opcodes.i32_to_u,
1178
+ ] : [
1179
+ ...number(pointer, Valtype.i32)
1180
+ ]),
936
1181
 
1182
+ // call regex func
1183
+ [ Opcodes.call, func.index ],
1184
+ Opcodes.i32_from
1185
+ ];
1186
+ }
1187
+
1188
+ if (protoFunc) {
937
1189
  let pointer = arrays.get(baseName);
938
1190
 
939
1191
  if (pointer == null) {
@@ -941,10 +1193,9 @@ const generateCall = (scope, decl, _global, _name) => {
941
1193
  if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
942
1194
 
943
1195
  // register array
944
- makeArray(scope, {
1196
+ 0, [ , pointer ] = makeArray(scope, {
945
1197
  rawElements: new Array(0)
946
1198
  }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
947
- pointer = arrays.get(baseName);
948
1199
 
949
1200
  const [ local, isGlobal ] = lookupName(scope, baseName);
950
1201
 
@@ -966,25 +1217,34 @@ const generateCall = (scope, decl, _global, _name) => {
966
1217
  // use local for cached i32 length as commonly used
967
1218
  let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
968
1219
 
1220
+ let lengthI32CacheUsed = false;
1221
+
1222
+ const protoOut = protoFunc(pointer, {
1223
+ getCachedI32: () => {
1224
+ lengthI32CacheUsed = true;
1225
+ return [ [ Opcodes.local_get, lengthLocal ] ]
1226
+ },
1227
+ setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
1228
+ get: () => arrayUtil.getLength(pointer),
1229
+ getI32: () => arrayUtil.getLengthI32(pointer),
1230
+ set: value => arrayUtil.setLength(pointer, value),
1231
+ setI32: value => arrayUtil.setLengthI32(pointer, value)
1232
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
1233
+ return makeArray(scope, {
1234
+ rawElements: new Array(length)
1235
+ }, _global, _name, true, itemType);
1236
+ });
1237
+
969
1238
  return [
970
1239
  ...out,
971
1240
 
972
- ...arrayUtil.getLengthI32(pointer),
973
- [ Opcodes.local_set, lengthLocal ],
1241
+ ...(!lengthI32CacheUsed ? [] : [
1242
+ ...arrayUtil.getLengthI32(pointer),
1243
+ [ Opcodes.local_set, lengthLocal ],
1244
+ ]),
974
1245
 
975
1246
  [ 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
- }),
1247
+ ...protoOut,
988
1248
  [ Opcodes.end ]
989
1249
  ];
990
1250
  }
@@ -1041,11 +1301,10 @@ const generateCall = (scope, decl, _global, _name) => {
1041
1301
  args = args.slice(0, func.params.length);
1042
1302
  }
1043
1303
 
1044
- if (func && func.memory) scope.memory = true;
1045
1304
  if (func && func.throws) scope.throws = true;
1046
1305
 
1047
1306
  for (const arg of args) {
1048
- out.push(...generate(scope, arg));
1307
+ out = out.concat(generate(scope, arg));
1049
1308
  }
1050
1309
 
1051
1310
  out.push([ Opcodes.call, idx ]);
@@ -1056,8 +1315,8 @@ const generateCall = (scope, decl, _global, _name) => {
1056
1315
  const generateNew = (scope, decl, _global, _name) => {
1057
1316
  // hack: basically treat this as a normal call for builtins for now
1058
1317
  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)})`);
1318
+ if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1319
+ if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1061
1320
 
1062
1321
  return generateCall(scope, decl, _global, _name);
1063
1322
  };
@@ -1165,8 +1424,6 @@ const generateAssign = (scope, decl) => {
1165
1424
  const name = decl.left.object.name;
1166
1425
  const pointer = arrays.get(name);
1167
1426
 
1168
- scope.memory = true;
1169
-
1170
1427
  const aotPointer = pointer != null;
1171
1428
 
1172
1429
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
@@ -1187,13 +1444,60 @@ const generateAssign = (scope, decl) => {
1187
1444
  ];
1188
1445
  }
1189
1446
 
1447
+ const op = decl.operator.slice(0, -1) || '=';
1448
+
1449
+ // arr[i] | str[i]
1450
+ if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1451
+ const name = decl.left.object.name;
1452
+ const pointer = arrays.get(name);
1453
+
1454
+ const aotPointer = pointer != null;
1455
+
1456
+ const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1457
+ const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1458
+
1459
+ const parentType = getNodeType(scope, decl.left.object);
1460
+
1461
+ return [
1462
+ ...(aotPointer ? [] : [
1463
+ ...generate(scope, decl.left.object),
1464
+ Opcodes.i32_to_u
1465
+ ]),
1466
+
1467
+ // get index as valtype
1468
+ ...generate(scope, decl.left.property),
1469
+
1470
+ // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
1471
+ Opcodes.i32_to_u,
1472
+ ...number(ValtypeSize[valtype], Valtype.i32),
1473
+ [ Opcodes.i32_mul ],
1474
+ ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
1475
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1476
+
1477
+ ...(op === '=' ? generate(scope, decl.right, false, name) : performOp(scope, op, [
1478
+ [ Opcodes.local_get, pointerTmp ],
1479
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1480
+ ], generate(scope, decl.right), parentType === TYPES._array ? TYPES.number : TYPES.string, getNodeType(scope, decl.right), false, name, true)),
1481
+ [ Opcodes.local_tee, newValueTmp ],
1482
+
1483
+ ...(parentType === TYPES._array ? [
1484
+ [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1485
+ ] : [
1486
+ Opcodes.i32_to_u,
1487
+ [ StoreOps.i16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1488
+ ]),
1489
+
1490
+ [ Opcodes.local_get, newValueTmp ]
1491
+ ];
1492
+ }
1493
+
1190
1494
  const [ local, isGlobal ] = lookupName(scope, name);
1191
1495
 
1192
1496
  if (local === undefined) {
1193
1497
  // todo: this should be a devtools/repl/??? only thing
1194
1498
 
1195
1499
  // only allow = for this
1196
- if (decl.operator !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1500
+ if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1197
1501
 
1198
1502
  if (builtinVars[name]) {
1199
1503
  // just return rhs (eg `NaN = 2`)
@@ -1207,8 +1511,10 @@ const generateAssign = (scope, decl) => {
1207
1511
  ];
1208
1512
  }
1209
1513
 
1210
- if (decl.operator === '=') {
1211
- typeStates[name] = getNodeType(scope, decl.right);
1514
+ typeStates[name] = getNodeType(scope, decl.right);
1515
+
1516
+ if (op === '=') {
1517
+ // typeStates[name] = getNodeType(scope, decl.right);
1212
1518
 
1213
1519
  return [
1214
1520
  ...generate(scope, decl.right, isGlobal, name),
@@ -1217,8 +1523,26 @@ const generateAssign = (scope, decl) => {
1217
1523
  ];
1218
1524
  }
1219
1525
 
1526
+ if (op === '||' || op === '&&' || op === '??') {
1527
+ // todo: is this needed?
1528
+ // for logical assignment ops, it is not left @= right ~= left = left @ right
1529
+ // instead, left @ (left = right)
1530
+ // eg, x &&= y ~= x && (x = y)
1531
+
1532
+ return [
1533
+ ...performOp(scope, op, [
1534
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1535
+ ], [
1536
+ ...generate(scope, decl.right),
1537
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1538
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1539
+ ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1540
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1541
+ ];
1542
+ }
1543
+
1220
1544
  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),
1545
+ ...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
1546
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1223
1547
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1224
1548
  ];
@@ -1245,13 +1569,14 @@ const generateUnary = (scope, decl) => {
1245
1569
 
1246
1570
  case '!':
1247
1571
  // !=
1248
- return falsy(scope, generate(scope, decl.argument));
1572
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
1249
1573
 
1250
1574
  case '~':
1575
+ // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
1251
1576
  return [
1252
1577
  ...generate(scope, decl.argument),
1253
1578
  Opcodes.i32_to,
1254
- [ Opcodes.i32_const, signedLEB128(-1) ],
1579
+ [ Opcodes.i32_const, ...signedLEB128(-1) ],
1255
1580
  [ Opcodes.i32_xor ],
1256
1581
  Opcodes.i32_from
1257
1582
  ];
@@ -1289,7 +1614,7 @@ const generateUnary = (scope, decl) => {
1289
1614
  return out;
1290
1615
 
1291
1616
  case 'typeof':
1292
- const type = getNodeType(scope, decl.argument);
1617
+ const type = getNodeType(scope, decl.argument) ?? TYPES.number;
1293
1618
 
1294
1619
  // for custom types, just return object
1295
1620
  if (type > 0xffffffffffff7) return number(TYPES.object);
@@ -1332,7 +1657,7 @@ const generateUpdate = (scope, decl) => {
1332
1657
  };
1333
1658
 
1334
1659
  const generateIf = (scope, decl) => {
1335
- const out = truthy(scope, generate(scope, decl.test), decl.test);
1660
+ const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test));
1336
1661
 
1337
1662
  out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1338
1663
  depth.push('if');
@@ -1422,9 +1747,116 @@ const generateWhile = (scope, decl) => {
1422
1747
  return out;
1423
1748
  };
1424
1749
 
1750
+ const generateForOf = (scope, decl) => {
1751
+ const out = [];
1752
+
1753
+ const rightType = getNodeType(scope, decl.right);
1754
+ const valtypeSize = rightType === TYPES._array ? ValtypeSize[valtype] : ValtypeSize.i16; // presume array (:()
1755
+
1756
+ // todo: for of inside for of might fuck up?
1757
+ const pointer = localTmp(scope, 'forof_base_pointer', Valtype.i32);
1758
+ const length = localTmp(scope, 'forof_length', Valtype.i32);
1759
+ const counter = localTmp(scope, 'forof_counter', Valtype.i32);
1760
+
1761
+ out.push(
1762
+ // set pointer as right
1763
+ ...generate(scope, decl.right),
1764
+ Opcodes.i32_to_u,
1765
+ [ Opcodes.local_set, pointer ],
1766
+
1767
+ // get length
1768
+ [ Opcodes.local_get, pointer ],
1769
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
1770
+ [ Opcodes.local_set, length ]
1771
+ );
1772
+
1773
+ out.push([ Opcodes.loop, Blocktype.void ]);
1774
+ depth.push('forof');
1775
+
1776
+ // setup local for left
1777
+ generate(scope, decl.left);
1778
+
1779
+ const leftName = decl.left.declarations[0].id.name;
1780
+
1781
+ // set type for local
1782
+ typeStates[leftName] = rightType === TYPES._array ? TYPES.number : TYPES.string;
1783
+
1784
+ const [ local, isGlobal ] = lookupName(scope, leftName);
1785
+
1786
+ if (rightType === TYPES._array) { // array
1787
+ out.push(
1788
+ [ Opcodes.local_get, pointer ],
1789
+ [ Opcodes.load, Math.log2(valtypeSize) - 1, ...unsignedLEB128(ValtypeSize.i32) ]
1790
+ );
1791
+ } else { // string
1792
+ const [ newOut, newPointer ] = makeArray(scope, {
1793
+ rawElements: new Array(1)
1794
+ }, isGlobal, leftName, true, 'i16');
1795
+
1796
+ out.push(
1797
+ // setup new/out array
1798
+ ...newOut,
1799
+ [ Opcodes.drop ],
1800
+
1801
+ ...number(0, Valtype.i32), // base 0 for store after
1802
+
1803
+ // load current string ind {arg}
1804
+ [ Opcodes.local_get, pointer ],
1805
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
1806
+
1807
+ // store to new string ind 0
1808
+ [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
1809
+
1810
+ // return new string (page)
1811
+ ...number(newPointer)
1812
+ );
1813
+ }
1814
+
1815
+ // set left value
1816
+ out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ]);
1817
+
1818
+ out.push(
1819
+ [ Opcodes.block, Blocktype.void ],
1820
+ [ Opcodes.block, Blocktype.void ]
1821
+ );
1822
+ depth.push('block');
1823
+ depth.push('block');
1824
+
1825
+ out.push(
1826
+ ...generate(scope, decl.body),
1827
+ [ Opcodes.end ]
1828
+ );
1829
+ depth.pop();
1830
+
1831
+ out.push(
1832
+ // increment iter pointer by valtype size
1833
+ [ Opcodes.local_get, pointer ],
1834
+ ...number(valtypeSize, Valtype.i32),
1835
+ [ Opcodes.i32_add ],
1836
+ [ Opcodes.local_set, pointer ],
1837
+
1838
+ // increment counter by 1
1839
+ [ Opcodes.local_get, counter ],
1840
+ ...number(1, Valtype.i32),
1841
+ [ Opcodes.i32_add ],
1842
+ [ Opcodes.local_tee, counter ],
1843
+
1844
+ // loop if counter != length
1845
+ [ Opcodes.local_get, length ],
1846
+ [ Opcodes.i32_ne ],
1847
+ [ Opcodes.br_if, 1 ],
1848
+
1849
+ [ Opcodes.end ], [ Opcodes.end ]
1850
+ );
1851
+ depth.pop();
1852
+ depth.pop();
1853
+
1854
+ return out;
1855
+ };
1856
+
1425
1857
  const getNearestLoop = () => {
1426
1858
  for (let i = depth.length - 1; i >= 0; i--) {
1427
- if (depth[i] === 'while' || depth[i] === 'for') return i;
1859
+ if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
1428
1860
  }
1429
1861
 
1430
1862
  return -1;
@@ -1508,13 +1940,22 @@ const generateAssignPat = (scope, decl) => {
1508
1940
  };
1509
1941
 
1510
1942
  let pages = new Map();
1511
- const allocPage = reason => {
1512
- if (pages.has(reason)) return pages.get(reason);
1943
+ const allocPage = (reason, type) => {
1944
+ if (pages.has(reason)) return pages.get(reason).ind;
1945
+
1946
+ const ind = pages.size;
1947
+ pages.set(reason, { ind, type });
1513
1948
 
1514
- let ind = pages.size;
1515
- pages.set(reason, ind);
1949
+ if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
1516
1950
 
1517
- if (codeLog) log('codegen', `allocated new page of memory (${ind}) | ${reason}`);
1951
+ return ind;
1952
+ };
1953
+
1954
+ const freePage = reason => {
1955
+ const { ind } = pages.get(reason);
1956
+ pages.delete(reason);
1957
+
1958
+ if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
1518
1959
 
1519
1960
  return ind;
1520
1961
  };
@@ -1528,7 +1969,7 @@ const itemTypeToValtype = {
1528
1969
  i16: 'i32'
1529
1970
  };
1530
1971
 
1531
- const storeOps = {
1972
+ const StoreOps = {
1532
1973
  i32: Opcodes.i32_store,
1533
1974
  i64: Opcodes.i64_store,
1534
1975
  f64: Opcodes.f64_store,
@@ -1537,13 +1978,28 @@ const storeOps = {
1537
1978
  i16: Opcodes.i32_store16
1538
1979
  };
1539
1980
 
1981
+ let data = [];
1982
+
1983
+ const compileBytes = (val, itemType, signed = true) => {
1984
+ switch (itemType) {
1985
+ case 'i8': return enforceOneByte(unsignedLEB128(val));
1986
+ case 'i16': return enforceTwoBytes(unsignedLEB128(val));
1987
+ case 'i32': return enforceFourBytes(signedLEB128(val));
1988
+ case 'i64': return enforceEightBytes(signedLEB128(val));
1989
+ case 'f64': return enforceEightBytes(ieee754_binary64(val));
1990
+ }
1991
+ };
1992
+
1540
1993
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
1541
1994
  const out = [];
1542
1995
 
1996
+ let firstAssign = false;
1543
1997
  if (!arrays.has(name) || name === '$undeclared') {
1998
+ firstAssign = true;
1999
+
1544
2000
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1545
2001
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1546
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`) * pageSize);
2002
+ arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
1547
2003
  }
1548
2004
 
1549
2005
  const pointer = arrays.get(name);
@@ -1551,8 +2007,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1551
2007
  const useRawElements = !!decl.rawElements;
1552
2008
  const elements = useRawElements ? decl.rawElements : decl.elements;
1553
2009
 
2010
+ const valtype = itemTypeToValtype[itemType];
1554
2011
  const length = elements.length;
1555
2012
 
2013
+ if (firstAssign && useRawElements) {
2014
+ let bytes = compileBytes(length, 'i32');
2015
+
2016
+ if (!initEmpty) for (let i = 0; i < length; i++) {
2017
+ if (elements[i] == null) continue;
2018
+
2019
+ bytes.push(...compileBytes(elements[i], itemType));
2020
+ }
2021
+
2022
+ data.push({
2023
+ offset: pointer,
2024
+ bytes
2025
+ });
2026
+
2027
+ // local value as pointer
2028
+ out.push(...number(pointer));
2029
+
2030
+ return [ out, pointer ];
2031
+ }
2032
+
1556
2033
  // store length as 0th array
1557
2034
  out.push(
1558
2035
  ...number(0, Valtype.i32),
@@ -1560,8 +2037,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1560
2037
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
1561
2038
  );
1562
2039
 
1563
- const storeOp = storeOps[itemType];
1564
- const valtype = itemTypeToValtype[itemType];
2040
+ const storeOp = StoreOps[itemType];
1565
2041
 
1566
2042
  if (!initEmpty) for (let i = 0; i < length; i++) {
1567
2043
  if (elements[i] == null) continue;
@@ -1576,14 +2052,12 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1576
2052
  // local value as pointer
1577
2053
  out.push(...number(pointer));
1578
2054
 
1579
- scope.memory = true;
1580
-
1581
- return out;
2055
+ return [ out, pointer ];
1582
2056
  };
1583
2057
 
1584
2058
  let arrays = new Map();
1585
2059
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
1586
- return makeArray(scope, decl, global, name, initEmpty, valtype);
2060
+ return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
1587
2061
  };
1588
2062
 
1589
2063
  export const generateMember = (scope, decl, _global, _name) => {
@@ -1596,8 +2070,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1596
2070
  const name = decl.object.name;
1597
2071
  const pointer = arrays.get(name);
1598
2072
 
1599
- scope.memory = true;
1600
-
1601
2073
  const aotPointer = pointer != null;
1602
2074
 
1603
2075
  return [
@@ -1617,8 +2089,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1617
2089
  const name = decl.object.name;
1618
2090
  const pointer = arrays.get(name);
1619
2091
 
1620
- scope.memory = true;
1621
-
1622
2092
  const aotPointer = pointer != null;
1623
2093
 
1624
2094
  if (type === TYPES._array) {
@@ -1644,10 +2114,9 @@ export const generateMember = (scope, decl, _global, _name) => {
1644
2114
 
1645
2115
  // string
1646
2116
 
1647
- const newOut = makeArray(scope, {
2117
+ const [ newOut, newPointer ] = makeArray(scope, {
1648
2118
  rawElements: new Array(1)
1649
2119
  }, _global, _name, true, 'i16');
1650
- const newPointer = arrays.get(_name ?? '$undeclared');
1651
2120
 
1652
2121
  return [
1653
2122
  // setup new/out array
@@ -1729,7 +2198,7 @@ const generateFunc = (scope, decl) => {
1729
2198
  locals: {},
1730
2199
  localInd: 0,
1731
2200
  returns: [ valtypeBinary ],
1732
- memory: false,
2201
+ returnType: null,
1733
2202
  throws: false,
1734
2203
  name
1735
2204
  };
@@ -1753,9 +2222,8 @@ const generateFunc = (scope, decl) => {
1753
2222
  name,
1754
2223
  params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
1755
2224
  returns: innerScope.returns,
1756
- returnType: innerScope.returnType ?? TYPES.number,
2225
+ returnType: innerScope.returnType,
1757
2226
  locals: innerScope.locals,
1758
- memory: innerScope.memory,
1759
2227
  throws: innerScope.throws,
1760
2228
  index: currentFuncIndex++
1761
2229
  };
@@ -1770,6 +2238,8 @@ const generateFunc = (scope, decl) => {
1770
2238
 
1771
2239
  if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
1772
2240
  wasm.push(...number(0), [ Opcodes.return ]);
2241
+
2242
+ if (func.returnType === null) func.returnType = TYPES.undefined;
1773
2243
  }
1774
2244
 
1775
2245
  // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
@@ -1780,9 +2250,7 @@ const generateFunc = (scope, decl) => {
1780
2250
  if (local.type === Valtype.v128) {
1781
2251
  vecParams++;
1782
2252
 
1783
- /* func.memory = true; // mark func as using memory
1784
-
1785
- wasm.unshift( // add v128 load for param
2253
+ /* wasm.unshift( // add v128 load for param
1786
2254
  [ Opcodes.i32_const, 0 ],
1787
2255
  [ ...Opcodes.v128_load, 0, i * 16 ],
1788
2256
  [ Opcodes.local_set, local.idx ]
@@ -1893,10 +2361,10 @@ const generateFunc = (scope, decl) => {
1893
2361
  };
1894
2362
 
1895
2363
  const generateCode = (scope, decl) => {
1896
- const out = [];
2364
+ let out = [];
1897
2365
 
1898
2366
  for (const x of decl.body) {
1899
- out.push(...generate(scope, x));
2367
+ out = out.concat(generate(scope, x));
1900
2368
  }
1901
2369
 
1902
2370
  return out;
@@ -1912,10 +2380,9 @@ const internalConstrs = {
1912
2380
 
1913
2381
  // new Array(n)
1914
2382
 
1915
- makeArray(scope, {
2383
+ const [ , pointer ] = makeArray(scope, {
1916
2384
  rawElements: new Array(0)
1917
2385
  }, global, name, true);
1918
- const pointer = arrays.get(name ?? '$undeclared');
1919
2386
 
1920
2387
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
1921
2388
 
@@ -1933,6 +2400,18 @@ const internalConstrs = {
1933
2400
  ];
1934
2401
  },
1935
2402
  type: TYPES._array
2403
+ },
2404
+
2405
+ __Array_of: {
2406
+ // this is not a constructor but best fits internal structure here
2407
+ generate: (scope, decl, global, name) => {
2408
+ // Array.of(i0, i1, ...)
2409
+ return generateArray(scope, {
2410
+ elements: decl.arguments
2411
+ }, global, name);
2412
+ },
2413
+ type: TYPES._array,
2414
+ notConstr: true
1936
2415
  }
1937
2416
  };
1938
2417
 
@@ -1947,6 +2426,7 @@ export default program => {
1947
2426
  typeStates = {};
1948
2427
  arrays = new Map();
1949
2428
  pages = new Map();
2429
+ data = [];
1950
2430
  currentFuncIndex = importedFuncs.length;
1951
2431
 
1952
2432
  globalThis.valtype = 'f64';
@@ -2023,5 +2503,5 @@ export default program => {
2023
2503
  // if blank main func and other exports, remove it
2024
2504
  if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
2025
2505
 
2026
- return { funcs, globals, tags, exceptions, pages };
2506
+ return { funcs, globals, tags, exceptions, pages, data };
2027
2507
  };