porffor 0.0.0-44bc2d8 → 0.0.0-5594e9d

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.
@@ -5,6 +5,7 @@ import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./bui
5
5
  import { PrototypeFuncs } from "./prototype.js";
6
6
  import { number, i32x4 } 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`);
183
+
184
+ if (!Array.isArray(inst)) inst = [ inst ];
185
+ const immediates = asm.slice(1).map(x => parseInt(x));
186
+
187
+ out.push([ ...inst, ...immediates ]);
188
+ }
189
+
190
+ return out;
191
+ },
175
192
 
176
- let inst = Opcodes[asm[0].replace('.', '_')];
177
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
193
+ __internal_print_type: str => {
194
+ const type = getType(scope, str) - TYPES.number;
178
195
 
179
- if (!Array.isArray(inst)) inst = [ inst ];
180
- const immediates = asm.slice(1).map(x => parseInt(x));
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,75 +588,100 @@ 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'));
@@ -703,21 +753,15 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
703
753
  ];
704
754
  };
705
755
 
706
- let binaryExpDepth = 0;
707
756
  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
- ];
757
+ 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
758
 
714
759
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
715
760
 
716
- binaryExpDepth--;
717
761
  return out;
718
762
  };
719
763
 
720
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, memory, localNames = [], globalNames = [] }) => {
764
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
721
765
  const existing = funcs.find(x => x.name === name);
722
766
  if (existing) return existing;
723
767
 
@@ -753,7 +797,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
753
797
  returns,
754
798
  returnType: TYPES[returnType ?? 'number'],
755
799
  wasm,
756
- memory,
757
800
  internal: true,
758
801
  index: currentFuncIndex++
759
802
  };
@@ -772,7 +815,7 @@ const includeBuiltin = (scope, builtin) => {
772
815
  };
773
816
 
774
817
  const generateLogicExp = (scope, decl) => {
775
- return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
818
+ return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
776
819
  };
777
820
 
778
821
  const TYPES = {
@@ -786,7 +829,8 @@ const TYPES = {
786
829
  bigint: 0xffffffffffff7,
787
830
 
788
831
  // these are not "typeof" types but tracked internally
789
- _array: 0xffffffffffff8
832
+ _array: 0xfffffffffff0f,
833
+ _regexp: 0xfffffffffff1f
790
834
  };
791
835
 
792
836
  const TYPE_NAMES = {
@@ -821,6 +865,8 @@ const getType = (scope, _name) => {
821
865
  const getNodeType = (scope, node) => {
822
866
  if (node.type === 'Literal') {
823
867
  if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
868
+ if (node.regex) return TYPES._regexp;
869
+
824
870
  return TYPES[typeof node.value];
825
871
  }
826
872
 
@@ -854,6 +900,11 @@ const getNodeType = (scope, node) => {
854
900
 
855
901
  // literal.func()
856
902
  if (!name && node.callee.type === 'MemberExpression') {
903
+ if (node.callee.object.regex) {
904
+ const funcName = node.callee.property.name;
905
+ return Rhemyn[funcName] ? TYPES.boolean : TYPES.undefined;
906
+ }
907
+
857
908
  const baseType = getNodeType(scope, node.callee.object);
858
909
 
859
910
  const func = node.callee.property.name;
@@ -897,6 +948,11 @@ const getNodeType = (scope, node) => {
897
948
  const generateLiteral = (scope, decl, global, name) => {
898
949
  if (decl.value === null) return number(NULL);
899
950
 
951
+ if (decl.regex) {
952
+ scope.regex[name] = decl.regex;
953
+ return number(1);
954
+ }
955
+
900
956
  switch (typeof decl.value) {
901
957
  case 'number':
902
958
  return number(decl.value);
@@ -936,7 +992,8 @@ const generateLiteral = (scope, decl, global, name) => {
936
992
  const countLeftover = wasm => {
937
993
  let count = 0, depth = 0;
938
994
 
939
- for (const inst of wasm) {
995
+ for (let i = 0; i < wasm.length; i++) {
996
+ const inst = wasm[i];
940
997
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
941
998
  if (inst[0] === Opcodes.if) count--;
942
999
  if (inst[1] !== Blocktype.void) count++;
@@ -957,6 +1014,8 @@ const countLeftover = wasm => {
957
1014
  } else count--;
958
1015
  if (func) count += func.returns.length;
959
1016
  } else count--;
1017
+
1018
+ // console.log(count, decompile([ inst ]).slice(0, -1));
960
1019
  }
961
1020
 
962
1021
  return count;
@@ -1040,7 +1099,7 @@ const generateCall = (scope, decl, _global, _name) => {
1040
1099
  }
1041
1100
 
1042
1101
  let out = [];
1043
- let protoFunc, protoName, baseType, baseName = '$undeclared';
1102
+ let protoFunc, protoName, baseType, baseName;
1044
1103
  // ident.func()
1045
1104
  if (name && name.startsWith('__')) {
1046
1105
  const spl = name.slice(2).split('_');
@@ -1055,6 +1114,25 @@ const generateCall = (scope, decl, _global, _name) => {
1055
1114
 
1056
1115
  // literal.func()
1057
1116
  if (!name && decl.callee.type === 'MemberExpression') {
1117
+ // megahack for /regex/.func()
1118
+ if (decl.callee.object.regex) {
1119
+ const funcName = decl.callee.property.name;
1120
+ const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1121
+
1122
+ funcIndex[func.name] = func.index;
1123
+ funcs.push(func);
1124
+
1125
+ return [
1126
+ // make string arg
1127
+ ...generate(scope, decl.arguments[0]),
1128
+
1129
+ // call regex func
1130
+ Opcodes.i32_to_u,
1131
+ [ Opcodes.call, func.index ],
1132
+ Opcodes.i32_from
1133
+ ];
1134
+ }
1135
+
1058
1136
  baseType = getNodeType(scope, decl.callee.object);
1059
1137
 
1060
1138
  const func = decl.callee.property.name;
@@ -1063,11 +1141,36 @@ const generateCall = (scope, decl, _global, _name) => {
1063
1141
 
1064
1142
  out = generate(scope, decl.callee.object);
1065
1143
  out.push([ Opcodes.drop ]);
1144
+
1145
+ baseName = [...arrays.keys()].pop();
1066
1146
  }
1067
1147
 
1068
- if (protoFunc) {
1069
- scope.memory = true;
1148
+ if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
1149
+ const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
1150
+
1151
+ funcIndex[func.name] = func.index;
1152
+ funcs.push(func);
1070
1153
 
1154
+ const pointer = arrays.get(baseName);
1155
+ const [ local, isGlobal ] = lookupName(scope, baseName);
1156
+
1157
+ return [
1158
+ ...out,
1159
+
1160
+ ...(pointer == null ? [
1161
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1162
+ Opcodes.i32_to_u,
1163
+ ] : [
1164
+ ...number(pointer, Valtype.i32)
1165
+ ]),
1166
+
1167
+ // call regex func
1168
+ [ Opcodes.call, func.index ],
1169
+ Opcodes.i32_from
1170
+ ];
1171
+ }
1172
+
1173
+ if (protoFunc) {
1071
1174
  let pointer = arrays.get(baseName);
1072
1175
 
1073
1176
  if (pointer == null) {
@@ -1075,7 +1178,7 @@ const generateCall = (scope, decl, _global, _name) => {
1075
1178
  if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
1076
1179
 
1077
1180
  // register array
1078
- const [ , pointer ] = makeArray(scope, {
1181
+ 0, [ , pointer ] = makeArray(scope, {
1079
1182
  rawElements: new Array(0)
1080
1183
  }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
1081
1184
 
@@ -1173,7 +1276,6 @@ const generateCall = (scope, decl, _global, _name) => {
1173
1276
  args = args.slice(0, func.params.length);
1174
1277
  }
1175
1278
 
1176
- if (func && func.memory) scope.memory = true;
1177
1279
  if (func && func.throws) scope.throws = true;
1178
1280
 
1179
1281
  for (const arg of args) {
@@ -1189,7 +1291,7 @@ const generateNew = (scope, decl, _global, _name) => {
1189
1291
  // hack: basically treat this as a normal call for builtins for now
1190
1292
  const name = mapName(decl.callee.name);
1191
1293
  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)})`);
1294
+ if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1193
1295
 
1194
1296
  return generateCall(scope, decl, _global, _name);
1195
1297
  };
@@ -1297,8 +1399,6 @@ const generateAssign = (scope, decl) => {
1297
1399
  const name = decl.left.object.name;
1298
1400
  const pointer = arrays.get(name);
1299
1401
 
1300
- scope.memory = true;
1301
-
1302
1402
  const aotPointer = pointer != null;
1303
1403
 
1304
1404
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
@@ -1319,6 +1419,47 @@ const generateAssign = (scope, decl) => {
1319
1419
  ];
1320
1420
  }
1321
1421
 
1422
+ if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1423
+ // arr[i] | str[i]
1424
+ const name = decl.left.object.name;
1425
+ const pointer = arrays.get(name);
1426
+
1427
+ const aotPointer = pointer != null;
1428
+
1429
+ const newValueTmp = localTmp(scope, '__member_setter_tmp');
1430
+
1431
+ const parentType = getNodeType(scope, decl.left.object);
1432
+
1433
+ const op = decl.operator.slice(0, -1);
1434
+ return [
1435
+ ...(aotPointer ? number(0, Valtype.i32) : [
1436
+ ...generate(scope, decl.left.object),
1437
+ Opcodes.i32_to_u
1438
+ ]),
1439
+
1440
+ // get index as valtype
1441
+ ...generate(scope, decl.left.property),
1442
+
1443
+ // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
1444
+ Opcodes.i32_to_u,
1445
+ ...number(ValtypeSize[valtype], Valtype.i32),
1446
+ [ Opcodes.i32_mul ],
1447
+ [ Opcodes.i32_add ],
1448
+
1449
+ ...(op === '' ? generate(scope, decl.right, false, name) : performOp(scope, op, generate(scope, decl.left), generate(scope, decl.right), parentType === TYPES._array ? TYPES.number : TYPES.string, getNodeType(scope, decl.right), false, name, true)),
1450
+ [ Opcodes.local_tee, newValueTmp ],
1451
+
1452
+ ...(parentType === TYPES._array ? [
1453
+ [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1454
+ ] : [
1455
+ Opcodes.i32_to_u,
1456
+ [ StoreOps.i16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1457
+ ]),
1458
+
1459
+ [ Opcodes.local_get, newValueTmp ]
1460
+ ];
1461
+ }
1462
+
1322
1463
  const [ local, isGlobal ] = lookupName(scope, name);
1323
1464
 
1324
1465
  if (local === undefined) {
@@ -1339,8 +1480,10 @@ const generateAssign = (scope, decl) => {
1339
1480
  ];
1340
1481
  }
1341
1482
 
1483
+ typeStates[name] = getNodeType(scope, decl.right);
1484
+
1342
1485
  if (decl.operator === '=') {
1343
- typeStates[name] = getNodeType(scope, decl.right);
1486
+ // typeStates[name] = getNodeType(scope, decl.right);
1344
1487
 
1345
1488
  return [
1346
1489
  ...generate(scope, decl.right, isGlobal, name),
@@ -1349,8 +1492,27 @@ const generateAssign = (scope, decl) => {
1349
1492
  ];
1350
1493
  }
1351
1494
 
1495
+ const op = decl.operator.slice(0, -1);
1496
+ if (op === '||' || op === '&&' || op === '??') {
1497
+ // todo: is this needed?
1498
+ // for logical assignment ops, it is not left @= right ~= left = left @ right
1499
+ // instead, left @ (left = right)
1500
+ // eg, x &&= y ~= x && (x = y)
1501
+
1502
+ return [
1503
+ ...performOp(scope, op, [
1504
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1505
+ ], [
1506
+ ...generate(scope, decl.right),
1507
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1508
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1509
+ ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1510
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1511
+ ];
1512
+ }
1513
+
1352
1514
  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),
1515
+ ...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
1516
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1355
1517
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1356
1518
  ];
@@ -1377,13 +1539,14 @@ const generateUnary = (scope, decl) => {
1377
1539
 
1378
1540
  case '!':
1379
1541
  // !=
1380
- return falsy(scope, generate(scope, decl.argument));
1542
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
1381
1543
 
1382
1544
  case '~':
1545
+ // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
1383
1546
  return [
1384
1547
  ...generate(scope, decl.argument),
1385
1548
  Opcodes.i32_to,
1386
- [ Opcodes.i32_const, signedLEB128(-1) ],
1549
+ [ Opcodes.i32_const, ...signedLEB128(-1) ],
1387
1550
  [ Opcodes.i32_xor ],
1388
1551
  Opcodes.i32_from
1389
1552
  ];
@@ -1464,7 +1627,7 @@ const generateUpdate = (scope, decl) => {
1464
1627
  };
1465
1628
 
1466
1629
  const generateIf = (scope, decl) => {
1467
- const out = truthy(scope, generate(scope, decl.test), decl.test);
1630
+ const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test));
1468
1631
 
1469
1632
  out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1470
1633
  depth.push('if');
@@ -1557,18 +1720,106 @@ const generateWhile = (scope, decl) => {
1557
1720
  const generateForOf = (scope, decl) => {
1558
1721
  const out = [];
1559
1722
 
1723
+ const rightType = getNodeType(scope, decl.right);
1724
+ const valtypeSize = rightType === TYPES._array ? ValtypeSize[valtype] : ValtypeSize.i16; // presume array (:()
1725
+
1726
+ // todo: for of inside for of might fuck up?
1727
+ const pointer = localTmp(scope, 'forof_base_pointer', Valtype.i32);
1728
+ const length = localTmp(scope, 'forof_length', Valtype.i32);
1729
+ const counter = localTmp(scope, 'forof_counter', Valtype.i32);
1730
+
1731
+ out.push(
1732
+ // set pointer as right
1733
+ ...generate(scope, decl.right),
1734
+ Opcodes.i32_to_u,
1735
+ [ Opcodes.local_set, pointer ],
1736
+
1737
+ // get length
1738
+ [ Opcodes.local_get, pointer ],
1739
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
1740
+ [ Opcodes.local_set, length ]
1741
+ );
1742
+
1560
1743
  out.push([ Opcodes.loop, Blocktype.void ]);
1561
- depth.push('while');
1744
+ depth.push('forof');
1562
1745
 
1563
- out.push(...generate(scope, decl.test));
1564
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1565
- depth.push('if');
1746
+ // setup local for left
1747
+ generate(scope, decl.left);
1566
1748
 
1567
- out.push(...generate(scope, decl.body));
1749
+ const leftName = decl.left.declarations[0].id.name;
1568
1750
 
1569
- out.push([ Opcodes.br, 1 ]);
1570
- out.push([ Opcodes.end ], [ Opcodes.end ]);
1571
- depth.pop(); depth.pop();
1751
+ // set type for local
1752
+ typeStates[leftName] = rightType === TYPES._array ? TYPES.number : TYPES.string;
1753
+
1754
+ const [ local, isGlobal ] = lookupName(scope, leftName);
1755
+
1756
+ if (rightType === TYPES._array) { // array
1757
+ out.push(
1758
+ [ Opcodes.local_get, pointer ],
1759
+ [ Opcodes.load, Math.log2(valtypeSize) - 1, ...unsignedLEB128(ValtypeSize.i32) ]
1760
+ );
1761
+ } else { // string
1762
+ const [ newOut, newPointer ] = makeArray(scope, {
1763
+ rawElements: new Array(1)
1764
+ }, isGlobal, leftName, true, 'i16');
1765
+
1766
+ out.push(
1767
+ // setup new/out array
1768
+ ...newOut,
1769
+ [ Opcodes.drop ],
1770
+
1771
+ ...number(0, Valtype.i32), // base 0 for store after
1772
+
1773
+ // load current string ind {arg}
1774
+ [ Opcodes.local_get, pointer ],
1775
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
1776
+
1777
+ // store to new string ind 0
1778
+ [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
1779
+
1780
+ // return new string (page)
1781
+ ...number(newPointer)
1782
+ );
1783
+ }
1784
+
1785
+ // set left value
1786
+ out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ]);
1787
+
1788
+ out.push(
1789
+ [ Opcodes.block, Blocktype.void ],
1790
+ [ Opcodes.block, Blocktype.void ]
1791
+ );
1792
+ depth.push('block');
1793
+ depth.push('block');
1794
+
1795
+ out.push(
1796
+ ...generate(scope, decl.body),
1797
+ [ Opcodes.end ]
1798
+ );
1799
+ depth.pop();
1800
+
1801
+ out.push(
1802
+ // increment iter pointer by valtype size
1803
+ [ Opcodes.local_get, pointer ],
1804
+ ...number(valtypeSize, Valtype.i32),
1805
+ [ Opcodes.i32_add ],
1806
+ [ Opcodes.local_set, pointer ],
1807
+
1808
+ // increment counter by 1
1809
+ [ Opcodes.local_get, counter ],
1810
+ ...number(1, Valtype.i32),
1811
+ [ Opcodes.i32_add ],
1812
+ [ Opcodes.local_tee, counter ],
1813
+
1814
+ // loop if counter != length
1815
+ [ Opcodes.local_get, length ],
1816
+ [ Opcodes.i32_ne ],
1817
+ [ Opcodes.br_if, 1 ],
1818
+
1819
+ [ Opcodes.end ], [ Opcodes.end ]
1820
+ );
1821
+ depth.pop();
1822
+ depth.pop();
1572
1823
 
1573
1824
  return out;
1574
1825
  };
@@ -1659,13 +1910,22 @@ const generateAssignPat = (scope, decl) => {
1659
1910
  };
1660
1911
 
1661
1912
  let pages = new Map();
1662
- const allocPage = reason => {
1663
- if (pages.has(reason)) return pages.get(reason);
1913
+ const allocPage = (reason, type) => {
1914
+ if (pages.has(reason)) return pages.get(reason).ind;
1915
+
1916
+ const ind = pages.size;
1917
+ pages.set(reason, { ind, type });
1918
+
1919
+ if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
1920
+
1921
+ return ind;
1922
+ };
1664
1923
 
1665
- let ind = pages.size;
1666
- pages.set(reason, ind);
1924
+ const freePage = reason => {
1925
+ const { ind } = pages.get(reason);
1926
+ pages.delete(reason);
1667
1927
 
1668
- if (codeLog) log('codegen', `allocated new page of memory (${ind}) | ${reason}`);
1928
+ if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
1669
1929
 
1670
1930
  return ind;
1671
1931
  };
@@ -1679,7 +1939,7 @@ const itemTypeToValtype = {
1679
1939
  i16: 'i32'
1680
1940
  };
1681
1941
 
1682
- const storeOps = {
1942
+ const StoreOps = {
1683
1943
  i32: Opcodes.i32_store,
1684
1944
  i64: Opcodes.i64_store,
1685
1945
  f64: Opcodes.f64_store,
@@ -1694,7 +1954,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1694
1954
  if (!arrays.has(name) || name === '$undeclared') {
1695
1955
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1696
1956
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1697
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`) * pageSize);
1957
+ arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
1698
1958
  }
1699
1959
 
1700
1960
  const pointer = arrays.get(name);
@@ -1711,7 +1971,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1711
1971
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
1712
1972
  );
1713
1973
 
1714
- const storeOp = storeOps[itemType];
1974
+ const storeOp = StoreOps[itemType];
1715
1975
  const valtype = itemTypeToValtype[itemType];
1716
1976
 
1717
1977
  if (!initEmpty) for (let i = 0; i < length; i++) {
@@ -1727,8 +1987,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1727
1987
  // local value as pointer
1728
1988
  out.push(...number(pointer));
1729
1989
 
1730
- scope.memory = true;
1731
-
1732
1990
  return [ out, pointer ];
1733
1991
  };
1734
1992
 
@@ -1747,8 +2005,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1747
2005
  const name = decl.object.name;
1748
2006
  const pointer = arrays.get(name);
1749
2007
 
1750
- scope.memory = true;
1751
-
1752
2008
  const aotPointer = pointer != null;
1753
2009
 
1754
2010
  return [
@@ -1768,8 +2024,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1768
2024
  const name = decl.object.name;
1769
2025
  const pointer = arrays.get(name);
1770
2026
 
1771
- scope.memory = true;
1772
-
1773
2027
  const aotPointer = pointer != null;
1774
2028
 
1775
2029
  if (type === TYPES._array) {
@@ -1879,7 +2133,7 @@ const generateFunc = (scope, decl) => {
1879
2133
  locals: {},
1880
2134
  localInd: 0,
1881
2135
  returns: [ valtypeBinary ],
1882
- memory: false,
2136
+ returnType: null,
1883
2137
  throws: false,
1884
2138
  name
1885
2139
  };
@@ -1905,7 +2159,6 @@ const generateFunc = (scope, decl) => {
1905
2159
  returns: innerScope.returns,
1906
2160
  returnType: innerScope.returnType,
1907
2161
  locals: innerScope.locals,
1908
- memory: innerScope.memory,
1909
2162
  throws: innerScope.throws,
1910
2163
  index: currentFuncIndex++
1911
2164
  };
@@ -1920,6 +2173,8 @@ const generateFunc = (scope, decl) => {
1920
2173
 
1921
2174
  if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
1922
2175
  wasm.push(...number(0), [ Opcodes.return ]);
2176
+
2177
+ if (func.returnType === null) func.returnType = TYPES.undefined;
1923
2178
  }
1924
2179
 
1925
2180
  // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
@@ -1930,9 +2185,7 @@ const generateFunc = (scope, decl) => {
1930
2185
  if (local.type === Valtype.v128) {
1931
2186
  vecParams++;
1932
2187
 
1933
- /* func.memory = true; // mark func as using memory
1934
-
1935
- wasm.unshift( // add v128 load for param
2188
+ /* wasm.unshift( // add v128 load for param
1936
2189
  [ Opcodes.i32_const, 0 ],
1937
2190
  [ ...Opcodes.v128_load, 0, i * 16 ],
1938
2191
  [ Opcodes.local_set, local.idx ]