porffor 0.0.0-bddcdc3 → 0.0.0-beff13f

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,8 +109,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
101
109
  case 'WhileStatement':
102
110
  return generateWhile(scope, decl);
103
111
 
104
- /* case 'ForOfStatement':
105
- return generateForOf(scope, decl); */
112
+ case 'ForOfStatement':
113
+ return generateForOf(scope, decl);
106
114
 
107
115
  case 'BreakStatement':
108
116
  return generateBreak(scope, decl);
@@ -144,45 +152,65 @@ const generate = (scope, decl, global = false, name = undefined) => {
144
152
 
145
153
  return [];
146
154
 
147
- case 'TaggedTemplateExpression':
148
- // hack for inline asm
149
- if (decl.tag.name !== 'asm') return todo('tagged template expressions not implemented');
155
+ case 'TaggedTemplateExpression': {
156
+ const funcs = {
157
+ asm: str => {
158
+ let out = [];
150
159
 
151
- const str = decl.quasi.quasis[0].value.raw;
152
- let out = [];
160
+ for (const line of str.split('\n')) {
161
+ const asm = line.trim().split(';;')[0].split(' ');
162
+ if (asm[0] === '') continue; // blank
153
163
 
154
- for (const line of str.split('\n')) {
155
- const asm = line.trim().split(';;')[0].split(' ');
156
- 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
+ }
157
169
 
158
- if (asm[0] === 'local') {
159
- const [ name, idx, type ] = asm.slice(1);
160
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
161
- continue;
162
- }
170
+ if (asm[0] === 'returns') {
171
+ scope.returns = asm.slice(1).map(x => Valtype[x]);
172
+ continue;
173
+ }
163
174
 
164
- if (asm[0] === 'returns') {
165
- scope.returns = asm.slice(1).map(x => Valtype[x]);
166
- continue;
167
- }
175
+ if (asm[0] === 'memory') {
176
+ allocPage('asm instrinsic');
177
+ // todo: add to store/load offset insts
178
+ continue;
179
+ }
168
180
 
169
- if (asm[0] === 'memory') {
170
- scope.memory = true;
171
- allocPage('asm instrinsic');
172
- // todo: add to store/load offset insts
173
- continue;
174
- }
181
+ let inst = Opcodes[asm[0].replace('.', '_')];
182
+ if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
175
183
 
176
- let inst = Opcodes[asm[0].replace('.', '_')];
177
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
184
+ if (!Array.isArray(inst)) inst = [ inst ];
185
+ const immediates = asm.slice(1).map(x => parseInt(x));
178
186
 
179
- if (!Array.isArray(inst)) inst = [ inst ];
180
- const immediates = asm.slice(1).map(x => parseInt(x));
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;
195
+
196
+ return [
197
+ ...number(type),
198
+ [ Opcodes.call, importedFuncs.print ],
181
199
 
182
- out.push([ ...inst, ...immediates ]);
200
+ // newline
201
+ ...number(10),
202
+ [ Opcodes.call, importedFuncs.printChar ]
203
+ ];
204
+ }
183
205
  }
184
206
 
185
- 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
+ }
186
214
 
187
215
  default:
188
216
  return todo(`no generation for ${decl.type}!`);
@@ -281,7 +309,7 @@ const generateReturn = (scope, decl) => {
281
309
  ];
282
310
  }
283
311
 
284
- if (!scope.returnType) scope.returnType = getNodeType(scope, decl.argument);
312
+ scope.returnType = getNodeType(scope, decl.argument);
285
313
 
286
314
  return [
287
315
  ...generate(scope, decl.argument),
@@ -298,11 +326,11 @@ const localTmp = (scope, name, type = valtypeBinary) => {
298
326
  return idx;
299
327
  };
300
328
 
301
- const performLogicOp = (scope, op, left, right) => {
329
+ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
302
330
  const checks = {
303
- '||': Opcodes.eqz,
304
- '&&': [ Opcodes.i32_to ]
305
- // todo: ??
331
+ '||': falsy,
332
+ '&&': truthy,
333
+ '??': nullish
306
334
  };
307
335
 
308
336
  if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
@@ -313,7 +341,8 @@ const performLogicOp = (scope, op, left, right) => {
313
341
  return [
314
342
  ...left,
315
343
  [ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
316
- ...checks[op],
344
+ ...checks[op](scope, [], leftType),
345
+ Opcodes.i32_to,
317
346
  [ Opcodes.if, valtypeBinary ],
318
347
  ...right,
319
348
  [ Opcodes.else ],
@@ -328,8 +357,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
328
357
  // todo: optimize by looking up names in arrays and using that if exists?
329
358
  // todo: optimize this if using literals/known lengths?
330
359
 
331
- scope.memory = true;
332
-
333
360
  const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
334
361
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
335
362
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
@@ -464,8 +491,6 @@ const compareStrings = (scope, left, right) => {
464
491
  // todo: optimize by looking up names in arrays and using that if exists?
465
492
  // todo: optimize this if using literals/known lengths?
466
493
 
467
- scope.memory = true;
468
-
469
494
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
470
495
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
471
496
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
@@ -563,102 +588,142 @@ const compareStrings = (scope, left, right) => {
563
588
  ];
564
589
  };
565
590
 
566
- const falsy = (scope, wasm, type) => {
591
+ const truthy = (scope, wasm, type) => {
567
592
  // arrays are always truthy
568
593
  if (type === TYPES._array) return [
569
594
  ...wasm,
570
595
  [ Opcodes.drop ],
571
- number(0)
596
+ ...number(1)
572
597
  ];
573
598
 
574
599
  if (type === TYPES.string) {
575
- // if "" (length = 0)
600
+ // if not "" (length = 0)
576
601
  return [
577
602
  // pointer
578
603
  ...wasm,
604
+ Opcodes.i32_to_u,
579
605
 
580
606
  // get length
581
607
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
582
608
 
583
- // if length == 0
584
- [ Opcodes.i32_eqz ],
609
+ // if length != 0
610
+ /* [ Opcodes.i32_eqz ],
611
+ [ Opcodes.i32_eqz ], */
585
612
  Opcodes.i32_from_u
586
613
  ]
587
614
  }
588
615
 
589
- // if = 0
616
+ // if != 0
590
617
  return [
591
618
  ...wasm,
592
619
 
593
- ...Opcodes.eqz,
594
- Opcodes.i32_from_u
620
+ /* Opcodes.eqz,
621
+ [ Opcodes.i32_eqz ],
622
+ Opcodes.i32_from */
595
623
  ];
596
624
  };
597
625
 
598
- const truthy = (scope, wasm, type) => {
626
+ const falsy = (scope, wasm, type) => {
599
627
  // arrays are always truthy
600
628
  if (type === TYPES._array) return [
601
629
  ...wasm,
602
630
  [ Opcodes.drop ],
603
- number(1)
631
+ ...number(0)
604
632
  ];
605
633
 
606
634
  if (type === TYPES.string) {
607
- // if not "" (length = 0)
635
+ // if "" (length = 0)
608
636
  return [
609
637
  // pointer
610
638
  ...wasm,
639
+ Opcodes.i32_to_u,
611
640
 
612
641
  // get length
613
642
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
614
643
 
615
- // if length != 0
616
- /* [ Opcodes.i32_eqz ],
617
- [ Opcodes.i32_eqz ], */
644
+ // if length == 0
645
+ [ Opcodes.i32_eqz ],
618
646
  Opcodes.i32_from_u
619
647
  ]
620
648
  }
621
649
 
622
- // if != 0
650
+ // if = 0
623
651
  return [
624
652
  ...wasm,
625
653
 
626
- /* Opcodes.eqz,
627
- [ Opcodes.i32_eqz ],
628
- Opcodes.i32_from */
654
+ ...Opcodes.eqz,
655
+ Opcodes.i32_from_u
656
+ ];
657
+ };
658
+
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)
629
679
  ];
630
680
  };
631
681
 
632
682
  const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
633
683
  if (op === '||' || op === '&&' || op === '??') {
634
- return performLogicOp(scope, op, left, right);
684
+ return performLogicOp(scope, op, left, right, leftType, rightType);
635
685
  }
636
686
 
637
687
  if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
638
688
 
639
- // if strict (in)equal and known types mismatch, return false (===)/true (!==)
640
- if ((op === '===' || op === '!==') && leftType && rightType && leftType !== rightType) {
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
+
641
705
  return [
642
706
  ...left,
643
- ...right,
644
-
645
- // drop values
646
707
  [ Opcodes.drop ],
708
+
709
+ ...right,
647
710
  [ Opcodes.drop ],
648
711
 
649
- // return false (===)/true (!==)
650
- ...number(op === '===' ? 0 : 1, Valtype.i32)
712
+ // return true (!=/!==) or false (else)
713
+ ...number(op === '!=' || op === '!==' ? 1 : 0, Valtype.i32)
651
714
  ];
652
715
  }
653
716
 
717
+ // todo: niche null hell with 0
718
+
654
719
  if (leftType === TYPES.string || rightType === TYPES.string) {
655
720
  if (op === '+') {
656
721
  // string concat (a + b)
657
722
  return concatStrings(scope, left, right, _global, _name, assign);
658
723
  }
659
724
 
660
- // any other math op, NaN
661
- if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
725
+ // not an equality op, NaN
726
+ if (!eqOp) return number(NaN);
662
727
 
663
728
  // else leave bool ops
664
729
  // todo: convert string to number if string and number/bool
@@ -703,21 +768,15 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
703
768
  ];
704
769
  };
705
770
 
706
- let binaryExpDepth = 0;
707
771
  const generateBinaryExp = (scope, decl, _global, _name) => {
708
- binaryExpDepth++;
709
-
710
- const out = [
711
- ...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
712
- ];
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);
713
773
 
714
774
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
715
775
 
716
- binaryExpDepth--;
717
776
  return out;
718
777
  };
719
778
 
720
- 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 = [] }) => {
721
780
  const existing = funcs.find(x => x.name === name);
722
781
  if (existing) return existing;
723
782
 
@@ -753,7 +812,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
753
812
  returns,
754
813
  returnType: TYPES[returnType ?? 'number'],
755
814
  wasm,
756
- memory,
757
815
  internal: true,
758
816
  index: currentFuncIndex++
759
817
  };
@@ -772,7 +830,7 @@ const includeBuiltin = (scope, builtin) => {
772
830
  };
773
831
 
774
832
  const generateLogicExp = (scope, decl) => {
775
- 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));
776
834
  };
777
835
 
778
836
  const TYPES = {
@@ -786,7 +844,8 @@ const TYPES = {
786
844
  bigint: 0xffffffffffff7,
787
845
 
788
846
  // these are not "typeof" types but tracked internally
789
- _array: 0xffffffffffff8
847
+ _array: 0xfffffffffff0f,
848
+ _regexp: 0xfffffffffff1f
790
849
  };
791
850
 
792
851
  const TYPE_NAMES = {
@@ -821,6 +880,8 @@ const getType = (scope, _name) => {
821
880
  const getNodeType = (scope, node) => {
822
881
  if (node.type === 'Literal') {
823
882
  if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
883
+ if (node.regex) return TYPES._regexp;
884
+
824
885
  return TYPES[typeof node.value];
825
886
  }
826
887
 
@@ -854,6 +915,11 @@ const getNodeType = (scope, node) => {
854
915
 
855
916
  // literal.func()
856
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
+
857
923
  const baseType = getNodeType(scope, node.callee.object);
858
924
 
859
925
  const func = node.callee.property.name;
@@ -897,6 +963,11 @@ const getNodeType = (scope, node) => {
897
963
  const generateLiteral = (scope, decl, global, name) => {
898
964
  if (decl.value === null) return number(NULL);
899
965
 
966
+ if (decl.regex) {
967
+ scope.regex[name] = decl.regex;
968
+ return number(1);
969
+ }
970
+
900
971
  switch (typeof decl.value) {
901
972
  case 'number':
902
973
  return number(decl.value);
@@ -936,7 +1007,8 @@ const generateLiteral = (scope, decl, global, name) => {
936
1007
  const countLeftover = wasm => {
937
1008
  let count = 0, depth = 0;
938
1009
 
939
- for (const inst of wasm) {
1010
+ for (let i = 0; i < wasm.length; i++) {
1011
+ const inst = wasm[i];
940
1012
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
941
1013
  if (inst[0] === Opcodes.if) count--;
942
1014
  if (inst[1] !== Blocktype.void) count++;
@@ -957,6 +1029,8 @@ const countLeftover = wasm => {
957
1029
  } else count--;
958
1030
  if (func) count += func.returns.length;
959
1031
  } else count--;
1032
+
1033
+ // console.log(count, decompile([ inst ]).slice(0, -1));
960
1034
  }
961
1035
 
962
1036
  return count;
@@ -1040,7 +1114,7 @@ const generateCall = (scope, decl, _global, _name) => {
1040
1114
  }
1041
1115
 
1042
1116
  let out = [];
1043
- let protoFunc, protoName, baseType, baseName = '$undeclared';
1117
+ let protoFunc, protoName, baseType, baseName;
1044
1118
  // ident.func()
1045
1119
  if (name && name.startsWith('__')) {
1046
1120
  const spl = name.slice(2).split('_');
@@ -1055,6 +1129,25 @@ const generateCall = (scope, decl, _global, _name) => {
1055
1129
 
1056
1130
  // literal.func()
1057
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
+
1058
1151
  baseType = getNodeType(scope, decl.callee.object);
1059
1152
 
1060
1153
  const func = decl.callee.property.name;
@@ -1063,11 +1156,36 @@ const generateCall = (scope, decl, _global, _name) => {
1063
1156
 
1064
1157
  out = generate(scope, decl.callee.object);
1065
1158
  out.push([ Opcodes.drop ]);
1159
+
1160
+ baseName = [...arrays.keys()].pop();
1066
1161
  }
1067
1162
 
1068
- if (protoFunc) {
1069
- 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);
1070
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
+ ]),
1181
+
1182
+ // call regex func
1183
+ [ Opcodes.call, func.index ],
1184
+ Opcodes.i32_from
1185
+ ];
1186
+ }
1187
+
1188
+ if (protoFunc) {
1071
1189
  let pointer = arrays.get(baseName);
1072
1190
 
1073
1191
  if (pointer == null) {
@@ -1075,7 +1193,7 @@ const generateCall = (scope, decl, _global, _name) => {
1075
1193
  if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
1076
1194
 
1077
1195
  // register array
1078
- const [ , pointer ] = makeArray(scope, {
1196
+ 0, [ , pointer ] = makeArray(scope, {
1079
1197
  rawElements: new Array(0)
1080
1198
  }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
1081
1199
 
@@ -1099,24 +1217,34 @@ const generateCall = (scope, decl, _global, _name) => {
1099
1217
  // use local for cached i32 length as commonly used
1100
1218
  let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1101
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
+
1102
1238
  return [
1103
1239
  ...out,
1104
1240
 
1105
- ...arrayUtil.getLengthI32(pointer),
1106
- [ Opcodes.local_set, lengthLocal ],
1241
+ ...(!lengthI32CacheUsed ? [] : [
1242
+ ...arrayUtil.getLengthI32(pointer),
1243
+ [ Opcodes.local_set, lengthLocal ],
1244
+ ]),
1107
1245
 
1108
1246
  [ Opcodes.block, valtypeBinary ],
1109
- ...protoFunc(pointer, {
1110
- cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
1111
- get: arrayUtil.getLength(pointer),
1112
- getI32: arrayUtil.getLengthI32(pointer),
1113
- set: value => arrayUtil.setLength(pointer, value),
1114
- setI32: value => arrayUtil.setLengthI32(pointer, value)
1115
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
1116
- return makeArray(scope, {
1117
- rawElements: new Array(length)
1118
- }, _global, _name, true, itemType);
1119
- }),
1247
+ ...protoOut,
1120
1248
  [ Opcodes.end ]
1121
1249
  ];
1122
1250
  }
@@ -1173,11 +1301,10 @@ const generateCall = (scope, decl, _global, _name) => {
1173
1301
  args = args.slice(0, func.params.length);
1174
1302
  }
1175
1303
 
1176
- if (func && func.memory) scope.memory = true;
1177
1304
  if (func && func.throws) scope.throws = true;
1178
1305
 
1179
1306
  for (const arg of args) {
1180
- out.push(...generate(scope, arg));
1307
+ out = out.concat(generate(scope, arg));
1181
1308
  }
1182
1309
 
1183
1310
  out.push([ Opcodes.call, idx ]);
@@ -1189,7 +1316,7 @@ const generateNew = (scope, decl, _global, _name) => {
1189
1316
  // hack: basically treat this as a normal call for builtins for now
1190
1317
  const name = mapName(decl.callee.name);
1191
1318
  if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1192
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1319
+ if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1193
1320
 
1194
1321
  return generateCall(scope, decl, _global, _name);
1195
1322
  };
@@ -1297,8 +1424,6 @@ const generateAssign = (scope, decl) => {
1297
1424
  const name = decl.left.object.name;
1298
1425
  const pointer = arrays.get(name);
1299
1426
 
1300
- scope.memory = true;
1301
-
1302
1427
  const aotPointer = pointer != null;
1303
1428
 
1304
1429
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
@@ -1319,13 +1444,60 @@ const generateAssign = (scope, decl) => {
1319
1444
  ];
1320
1445
  }
1321
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
+
1322
1494
  const [ local, isGlobal ] = lookupName(scope, name);
1323
1495
 
1324
1496
  if (local === undefined) {
1325
1497
  // todo: this should be a devtools/repl/??? only thing
1326
1498
 
1327
1499
  // only allow = for this
1328
- if (decl.operator !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1500
+ if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1329
1501
 
1330
1502
  if (builtinVars[name]) {
1331
1503
  // just return rhs (eg `NaN = 2`)
@@ -1339,8 +1511,10 @@ const generateAssign = (scope, decl) => {
1339
1511
  ];
1340
1512
  }
1341
1513
 
1342
- if (decl.operator === '=') {
1343
- typeStates[name] = getNodeType(scope, decl.right);
1514
+ typeStates[name] = getNodeType(scope, decl.right);
1515
+
1516
+ if (op === '=') {
1517
+ // typeStates[name] = getNodeType(scope, decl.right);
1344
1518
 
1345
1519
  return [
1346
1520
  ...generate(scope, decl.right, isGlobal, name),
@@ -1349,8 +1523,26 @@ const generateAssign = (scope, decl) => {
1349
1523
  ];
1350
1524
  }
1351
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
+
1352
1544
  return [
1353
- ...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),
1354
1546
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1355
1547
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1356
1548
  ];
@@ -1377,13 +1569,14 @@ const generateUnary = (scope, decl) => {
1377
1569
 
1378
1570
  case '!':
1379
1571
  // !=
1380
- return falsy(scope, generate(scope, decl.argument));
1572
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
1381
1573
 
1382
1574
  case '~':
1575
+ // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
1383
1576
  return [
1384
1577
  ...generate(scope, decl.argument),
1385
1578
  Opcodes.i32_to,
1386
- [ Opcodes.i32_const, signedLEB128(-1) ],
1579
+ [ Opcodes.i32_const, ...signedLEB128(-1) ],
1387
1580
  [ Opcodes.i32_xor ],
1388
1581
  Opcodes.i32_from
1389
1582
  ];
@@ -1464,7 +1657,7 @@ const generateUpdate = (scope, decl) => {
1464
1657
  };
1465
1658
 
1466
1659
  const generateIf = (scope, decl) => {
1467
- const out = truthy(scope, generate(scope, decl.test), decl.test);
1660
+ const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test));
1468
1661
 
1469
1662
  out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1470
1663
  depth.push('if');
@@ -1557,18 +1750,106 @@ const generateWhile = (scope, decl) => {
1557
1750
  const generateForOf = (scope, decl) => {
1558
1751
  const out = [];
1559
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
+
1560
1773
  out.push([ Opcodes.loop, Blocktype.void ]);
1561
- depth.push('while');
1774
+ depth.push('forof');
1562
1775
 
1563
- out.push(...generate(scope, decl.test));
1564
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1565
- depth.push('if');
1776
+ // setup local for left
1777
+ generate(scope, decl.left);
1566
1778
 
1567
- out.push(...generate(scope, decl.body));
1779
+ const leftName = decl.left.declarations[0].id.name;
1568
1780
 
1569
- out.push([ Opcodes.br, 1 ]);
1570
- out.push([ Opcodes.end ], [ Opcodes.end ]);
1571
- depth.pop(); depth.pop();
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();
1572
1853
 
1573
1854
  return out;
1574
1855
  };
@@ -1659,13 +1940,22 @@ const generateAssignPat = (scope, decl) => {
1659
1940
  };
1660
1941
 
1661
1942
  let pages = new Map();
1662
- const allocPage = reason => {
1663
- 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 });
1948
+
1949
+ if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
1950
+
1951
+ return ind;
1952
+ };
1664
1953
 
1665
- let ind = pages.size;
1666
- pages.set(reason, ind);
1954
+ const freePage = reason => {
1955
+ const { ind } = pages.get(reason);
1956
+ pages.delete(reason);
1667
1957
 
1668
- if (codeLog) log('codegen', `allocated new page of memory (${ind}) | ${reason}`);
1958
+ if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
1669
1959
 
1670
1960
  return ind;
1671
1961
  };
@@ -1679,7 +1969,7 @@ const itemTypeToValtype = {
1679
1969
  i16: 'i32'
1680
1970
  };
1681
1971
 
1682
- const storeOps = {
1972
+ const StoreOps = {
1683
1973
  i32: Opcodes.i32_store,
1684
1974
  i64: Opcodes.i64_store,
1685
1975
  f64: Opcodes.f64_store,
@@ -1688,13 +1978,28 @@ const storeOps = {
1688
1978
  i16: Opcodes.i32_store16
1689
1979
  };
1690
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
+
1691
1993
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
1692
1994
  const out = [];
1693
1995
 
1996
+ let firstAssign = false;
1694
1997
  if (!arrays.has(name) || name === '$undeclared') {
1998
+ firstAssign = true;
1999
+
1695
2000
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1696
2001
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1697
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`) * pageSize);
2002
+ arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
1698
2003
  }
1699
2004
 
1700
2005
  const pointer = arrays.get(name);
@@ -1702,8 +2007,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1702
2007
  const useRawElements = !!decl.rawElements;
1703
2008
  const elements = useRawElements ? decl.rawElements : decl.elements;
1704
2009
 
2010
+ const valtype = itemTypeToValtype[itemType];
1705
2011
  const length = elements.length;
1706
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
+
1707
2033
  // store length as 0th array
1708
2034
  out.push(
1709
2035
  ...number(0, Valtype.i32),
@@ -1711,8 +2037,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1711
2037
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
1712
2038
  );
1713
2039
 
1714
- const storeOp = storeOps[itemType];
1715
- const valtype = itemTypeToValtype[itemType];
2040
+ const storeOp = StoreOps[itemType];
1716
2041
 
1717
2042
  if (!initEmpty) for (let i = 0; i < length; i++) {
1718
2043
  if (elements[i] == null) continue;
@@ -1727,8 +2052,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1727
2052
  // local value as pointer
1728
2053
  out.push(...number(pointer));
1729
2054
 
1730
- scope.memory = true;
1731
-
1732
2055
  return [ out, pointer ];
1733
2056
  };
1734
2057
 
@@ -1747,8 +2070,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1747
2070
  const name = decl.object.name;
1748
2071
  const pointer = arrays.get(name);
1749
2072
 
1750
- scope.memory = true;
1751
-
1752
2073
  const aotPointer = pointer != null;
1753
2074
 
1754
2075
  return [
@@ -1768,8 +2089,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1768
2089
  const name = decl.object.name;
1769
2090
  const pointer = arrays.get(name);
1770
2091
 
1771
- scope.memory = true;
1772
-
1773
2092
  const aotPointer = pointer != null;
1774
2093
 
1775
2094
  if (type === TYPES._array) {
@@ -1879,7 +2198,7 @@ const generateFunc = (scope, decl) => {
1879
2198
  locals: {},
1880
2199
  localInd: 0,
1881
2200
  returns: [ valtypeBinary ],
1882
- memory: false,
2201
+ returnType: null,
1883
2202
  throws: false,
1884
2203
  name
1885
2204
  };
@@ -1905,7 +2224,6 @@ const generateFunc = (scope, decl) => {
1905
2224
  returns: innerScope.returns,
1906
2225
  returnType: innerScope.returnType,
1907
2226
  locals: innerScope.locals,
1908
- memory: innerScope.memory,
1909
2227
  throws: innerScope.throws,
1910
2228
  index: currentFuncIndex++
1911
2229
  };
@@ -1920,6 +2238,8 @@ const generateFunc = (scope, decl) => {
1920
2238
 
1921
2239
  if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
1922
2240
  wasm.push(...number(0), [ Opcodes.return ]);
2241
+
2242
+ if (func.returnType === null) func.returnType = TYPES.undefined;
1923
2243
  }
1924
2244
 
1925
2245
  // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
@@ -1930,9 +2250,7 @@ const generateFunc = (scope, decl) => {
1930
2250
  if (local.type === Valtype.v128) {
1931
2251
  vecParams++;
1932
2252
 
1933
- /* func.memory = true; // mark func as using memory
1934
-
1935
- wasm.unshift( // add v128 load for param
2253
+ /* wasm.unshift( // add v128 load for param
1936
2254
  [ Opcodes.i32_const, 0 ],
1937
2255
  [ ...Opcodes.v128_load, 0, i * 16 ],
1938
2256
  [ Opcodes.local_set, local.idx ]
@@ -2043,10 +2361,10 @@ const generateFunc = (scope, decl) => {
2043
2361
  };
2044
2362
 
2045
2363
  const generateCode = (scope, decl) => {
2046
- const out = [];
2364
+ let out = [];
2047
2365
 
2048
2366
  for (const x of decl.body) {
2049
- out.push(...generate(scope, x));
2367
+ out = out.concat(generate(scope, x));
2050
2368
  }
2051
2369
 
2052
2370
  return out;
@@ -2096,6 +2414,7 @@ export default program => {
2096
2414
  typeStates = {};
2097
2415
  arrays = new Map();
2098
2416
  pages = new Map();
2417
+ data = [];
2099
2418
  currentFuncIndex = importedFuncs.length;
2100
2419
 
2101
2420
  globalThis.valtype = 'f64';
@@ -2172,5 +2491,5 @@ export default program => {
2172
2491
  // if blank main func and other exports, remove it
2173
2492
  if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
2174
2493
 
2175
- return { funcs, globals, tags, exceptions, pages };
2494
+ return { funcs, globals, tags, exceptions, pages, data };
2176
2495
  };