porffor 0.0.0-05f898f → 0.0.0-151f80e

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,9 +1,9 @@
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
8
  import * as Rhemyn from "../rhemyn/compile.js";
9
9
 
@@ -109,8 +109,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
109
109
  case 'WhileStatement':
110
110
  return generateWhile(scope, decl);
111
111
 
112
- /* case 'ForOfStatement':
113
- return generateForOf(scope, decl); */
112
+ case 'ForOfStatement':
113
+ return generateForOf(scope, decl);
114
114
 
115
115
  case 'BreakStatement':
116
116
  return generateBreak(scope, decl);
@@ -152,44 +152,65 @@ const generate = (scope, decl, global = false, name = undefined) => {
152
152
 
153
153
  return [];
154
154
 
155
- case 'TaggedTemplateExpression':
156
- // hack for inline asm
157
- if (decl.tag.name !== 'asm') return todo('tagged template expressions not implemented');
155
+ case 'TaggedTemplateExpression': {
156
+ const funcs = {
157
+ asm: str => {
158
+ let out = [];
158
159
 
159
- const str = decl.quasi.quasis[0].value.raw;
160
- let out = [];
160
+ for (const line of str.split('\n')) {
161
+ const asm = line.trim().split(';;')[0].split(' ');
162
+ if (asm[0] === '') continue; // blank
161
163
 
162
- for (const line of str.split('\n')) {
163
- const asm = line.trim().split(';;')[0].split(' ');
164
- 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
+ }
165
169
 
166
- if (asm[0] === 'local') {
167
- const [ name, idx, type ] = asm.slice(1);
168
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
169
- continue;
170
- }
170
+ if (asm[0] === 'returns') {
171
+ scope.returns = asm.slice(1).map(x => Valtype[x]);
172
+ continue;
173
+ }
171
174
 
172
- if (asm[0] === 'returns') {
173
- scope.returns = asm.slice(1).map(x => Valtype[x]);
174
- continue;
175
- }
175
+ if (asm[0] === 'memory') {
176
+ allocPage('asm instrinsic');
177
+ // todo: add to store/load offset insts
178
+ continue;
179
+ }
176
180
 
177
- if (asm[0] === 'memory') {
178
- allocPage('asm instrinsic');
179
- // todo: add to store/load offset insts
180
- continue;
181
- }
181
+ let inst = Opcodes[asm[0].replace('.', '_')];
182
+ if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
182
183
 
183
- let inst = Opcodes[asm[0].replace('.', '_')];
184
- 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));
185
186
 
186
- if (!Array.isArray(inst)) inst = [ inst ];
187
- const immediates = asm.slice(1).map(x => parseInt(x));
187
+ out.push([ ...inst, ...immediates ]);
188
+ }
188
189
 
189
- out.push([ ...inst, ...immediates ]);
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 ],
199
+
200
+ // newline
201
+ ...number(10),
202
+ [ Opcodes.call, importedFuncs.printChar ]
203
+ ];
204
+ }
190
205
  }
191
206
 
192
- 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
+ }
193
214
 
194
215
  default:
195
216
  return todo(`no generation for ${decl.type}!`);
@@ -305,6 +326,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
305
326
  return idx;
306
327
  };
307
328
 
329
+ const isIntOp = op => op[0] >= 0xb7 && op[0] <= 0xba;
330
+
308
331
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
309
332
  const checks = {
310
333
  '||': falsy,
@@ -317,6 +340,32 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
317
340
  // generic structure for {a} OP {b}
318
341
  // -->
319
342
  // _ = {a}; if (OP_CHECK) {b} else _
343
+
344
+ // if we can, use int tmp and convert at the end to help prevent unneeded conversions
345
+ // (like if we are in an if condition - very common)
346
+ const leftIsInt = isIntOp(left[left.length - 1]);
347
+ const rightIsInt = isIntOp(right[right.length - 1]);
348
+
349
+ const canInt = leftIsInt && rightIsInt;
350
+
351
+ if (canInt) {
352
+ // remove int -> float conversions from left and right
353
+ left.pop();
354
+ right.pop();
355
+
356
+ return [
357
+ ...left,
358
+ [ Opcodes.local_tee, localTmp(scope, 'logictmpi', Valtype.i32) ],
359
+ ...checks[op](scope, [], leftType, true),
360
+ [ Opcodes.if, Valtype.i32 ],
361
+ ...right,
362
+ [ Opcodes.else ],
363
+ [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
364
+ [ Opcodes.end ],
365
+ Opcodes.i32_from
366
+ ];
367
+ }
368
+
320
369
  return [
321
370
  ...left,
322
371
  [ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
@@ -340,6 +389,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
340
389
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
341
390
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
342
391
 
392
+ const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
393
+ if (aotWFA) addVarMeta(name, { wellFormed: undefined });
394
+
343
395
  if (assign) {
344
396
  const pointer = arrays.get(name ?? '$undeclared');
345
397
 
@@ -567,12 +619,12 @@ const compareStrings = (scope, left, right) => {
567
619
  ];
568
620
  };
569
621
 
570
- const truthy = (scope, wasm, type) => {
622
+ const truthy = (scope, wasm, type, int = false) => {
571
623
  // arrays are always truthy
572
624
  if (type === TYPES._array) return [
573
625
  ...wasm,
574
626
  [ Opcodes.drop ],
575
- ...number(1)
627
+ ...number(1, int ? Valtype.i32 : valtypeBinary)
576
628
  ];
577
629
 
578
630
  if (type === TYPES.string) {
@@ -588,8 +640,8 @@ const truthy = (scope, wasm, type) => {
588
640
  // if length != 0
589
641
  /* [ Opcodes.i32_eqz ],
590
642
  [ Opcodes.i32_eqz ], */
591
- Opcodes.i32_from_u
592
- ]
643
+ ...(int ? [] : [ Opcodes.i32_from_u ])
644
+ ];
593
645
  }
594
646
 
595
647
  // if != 0
@@ -602,12 +654,12 @@ const truthy = (scope, wasm, type) => {
602
654
  ];
603
655
  };
604
656
 
605
- const falsy = (scope, wasm, type) => {
657
+ const falsy = (scope, wasm, type, int = false) => {
606
658
  // arrays are always truthy
607
659
  if (type === TYPES._array) return [
608
660
  ...wasm,
609
661
  [ Opcodes.drop ],
610
- ...number(0)
662
+ ...number(0, int ? Valtype.i32 : valtypeBinary)
611
663
  ];
612
664
 
613
665
  if (type === TYPES.string) {
@@ -622,7 +674,7 @@ const falsy = (scope, wasm, type) => {
622
674
 
623
675
  // if length == 0
624
676
  [ Opcodes.i32_eqz ],
625
- Opcodes.i32_from_u
677
+ ...(int ? [] : [ Opcodes.i32_from_u ])
626
678
  ]
627
679
  }
628
680
 
@@ -630,31 +682,29 @@ const falsy = (scope, wasm, type) => {
630
682
  return [
631
683
  ...wasm,
632
684
 
633
- ...Opcodes.eqz,
634
- Opcodes.i32_from_u
685
+ ...(int ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz, Opcodes.i32_from_u ])
635
686
  ];
636
687
  };
637
688
 
638
- const nullish = (scope, wasm, type) => {
689
+ const nullish = (scope, wasm, type, int = false) => {
639
690
  // undefined
640
691
  if (type === TYPES.undefined) return [
641
692
  ...wasm,
642
693
  [ Opcodes.drop ],
643
- ...number(1)
694
+ ...number(1, int ? Valtype.i32 : valtypeBinary)
644
695
  ];
645
696
 
646
697
  // null (if object and = "0")
647
698
  if (type === TYPES.object) return [
648
699
  ...wasm,
649
- ...Opcodes.eqz,
650
- Opcodes.i32_from_u
700
+ ...(int ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz, Opcodes.i32_from_u ])
651
701
  ];
652
702
 
653
703
  // not
654
704
  return [
655
705
  ...wasm,
656
706
  [ Opcodes.drop ],
657
- ...number(0)
707
+ ...number(0, int ? Valtype.i32 : valtypeBinary)
658
708
  ];
659
709
  };
660
710
 
@@ -665,29 +715,44 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
665
715
 
666
716
  if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
667
717
 
668
- // if strict (in)equal and known types mismatch, return false (===)/true (!==)
669
- if ((op === '===' || op === '!==') && leftType && rightType && leftType !== rightType) {
718
+ const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
719
+
720
+ if (leftType && rightType && (
721
+ // if strict (in)equal and known types mismatch, return false (===)/true (!==)
722
+ ((op === '===' || op === '!==') && leftType !== rightType) ||
723
+
724
+ // if equality op and an operand is undefined, return false
725
+ (eqOp && leftType === TYPES.undefined ^ rightType === TYPES.undefined)
726
+ )) {
727
+ // undefined == null
728
+ if (((leftType === TYPES.undefined && rightType === TYPES.object) || (leftType === TYPES.object && rightType === TYPES.undefined)) && (op === '==' || op === '!=')) return [
729
+ ...(leftType === TYPES.object ? left : right),
730
+ ...Opcodes.eqz,
731
+ ...(op === '!=' ? [ [ Opcodes.i32_eqz ] ] : [])
732
+ ];
733
+
670
734
  return [
671
735
  ...left,
672
- ...right,
673
-
674
- // drop values
675
736
  [ Opcodes.drop ],
737
+
738
+ ...right,
676
739
  [ Opcodes.drop ],
677
740
 
678
- // return false (===)/true (!==)
679
- ...number(op === '===' ? 0 : 1, Valtype.i32)
741
+ // return true (!=/!==) or false (else)
742
+ ...number(op === '!=' || op === '!==' ? 1 : 0, Valtype.i32)
680
743
  ];
681
744
  }
682
745
 
746
+ // todo: niche null hell with 0
747
+
683
748
  if (leftType === TYPES.string || rightType === TYPES.string) {
684
749
  if (op === '+') {
685
750
  // string concat (a + b)
686
751
  return concatStrings(scope, left, right, _global, _name, assign);
687
752
  }
688
753
 
689
- // any other math op, NaN
690
- if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
754
+ // not an equality op, NaN
755
+ if (!eqOp) return number(NaN);
691
756
 
692
757
  // else leave bool ops
693
758
  // todo: convert string to number if string and number/bool
@@ -732,17 +797,11 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
732
797
  ];
733
798
  };
734
799
 
735
- let binaryExpDepth = 0;
736
800
  const generateBinaryExp = (scope, decl, _global, _name) => {
737
- binaryExpDepth++;
738
-
739
- const out = [
740
- ...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
741
- ];
801
+ const out = performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name);
742
802
 
743
803
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
744
804
 
745
- binaryExpDepth--;
746
805
  return out;
747
806
  };
748
807
 
@@ -959,12 +1018,38 @@ const generateLiteral = (scope, decl, global, name) => {
959
1018
  case 'bigint': return number(TYPES.bigint);
960
1019
  }
961
1020
 
1021
+ const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
1022
+ let wellFormed = aotWFA ? true : undefined;
1023
+
962
1024
  const str = decl.value;
963
1025
  const rawElements = new Array(str.length);
1026
+ let j = 0;
964
1027
  for (let i = 0; i < str.length; i++) {
965
1028
  rawElements[i] = str.charCodeAt(i);
1029
+
1030
+ if (wellFormed) {
1031
+ // check if surrogate
1032
+ if ((str.charCodeAt(j) & 0xF800) === 0xD800) {
1033
+ // unpaired trailing surrogate
1034
+ if (str.charCodeAt(j) >= 0xDC00) {
1035
+ wellFormed = false;
1036
+ }
1037
+
1038
+ // unpaired leading surrogate
1039
+ // if (++j >= str.length || (str.charCodeAt(j) & 0xFC00) != 0xDC00) {
1040
+ if ((str.charCodeAt(++j) & 0xFC00) != 0xDC00) {
1041
+ wellFormed = false;
1042
+ }
1043
+ }
1044
+
1045
+ j++;
1046
+ }
966
1047
  }
967
1048
 
1049
+ // console.log(wellFormed, str);
1050
+
1051
+ if (aotWFA) addVarMeta(name, { wellFormed });
1052
+
968
1053
  return makeArray(scope, {
969
1054
  rawElements
970
1055
  }, global, name, false, 'i16')[0];
@@ -999,6 +1084,8 @@ const countLeftover = wasm => {
999
1084
  } else count--;
1000
1085
  if (func) count += func.returns.length;
1001
1086
  } else count--;
1087
+
1088
+ // console.log(count, decompile([ inst ]).slice(0, -1));
1002
1089
  }
1003
1090
 
1004
1091
  return count;
@@ -1181,28 +1268,39 @@ const generateCall = (scope, decl, _global, _name) => {
1181
1268
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) return arrayUtil.getLength(pointer)
1182
1269
 
1183
1270
  let protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp`, protoFunc.local) : -1;
1271
+ let protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp2`, protoFunc.local2) : -1;
1184
1272
 
1185
1273
  // use local for cached i32 length as commonly used
1186
1274
  let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1187
1275
 
1276
+ let lengthI32CacheUsed = false;
1277
+
1278
+ const protoOut = protoFunc(pointer, {
1279
+ getCachedI32: () => {
1280
+ lengthI32CacheUsed = true;
1281
+ return [ [ Opcodes.local_get, lengthLocal ] ]
1282
+ },
1283
+ setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
1284
+ get: () => arrayUtil.getLength(pointer),
1285
+ getI32: () => arrayUtil.getLengthI32(pointer),
1286
+ set: value => arrayUtil.setLength(pointer, value),
1287
+ setI32: value => arrayUtil.setLengthI32(pointer, value)
1288
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1289
+ return makeArray(scope, {
1290
+ rawElements: new Array(length)
1291
+ }, _global, _name, true, itemType);
1292
+ }, varMetadata.get(baseName));
1293
+
1188
1294
  return [
1189
1295
  ...out,
1190
1296
 
1191
- ...arrayUtil.getLengthI32(pointer),
1192
- [ Opcodes.local_set, lengthLocal ],
1297
+ ...(!lengthI32CacheUsed ? [] : [
1298
+ ...arrayUtil.getLengthI32(pointer),
1299
+ [ Opcodes.local_set, lengthLocal ],
1300
+ ]),
1193
1301
 
1194
1302
  [ Opcodes.block, valtypeBinary ],
1195
- ...protoFunc(pointer, {
1196
- cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
1197
- get: arrayUtil.getLength(pointer),
1198
- getI32: arrayUtil.getLengthI32(pointer),
1199
- set: value => arrayUtil.setLength(pointer, value),
1200
- setI32: value => arrayUtil.setLengthI32(pointer, value)
1201
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
1202
- return makeArray(scope, {
1203
- rawElements: new Array(length)
1204
- }, _global, _name, true, itemType);
1205
- }),
1303
+ ...protoOut,
1206
1304
  [ Opcodes.end ]
1207
1305
  ];
1208
1306
  }
@@ -1262,7 +1360,7 @@ const generateCall = (scope, decl, _global, _name) => {
1262
1360
  if (func && func.throws) scope.throws = true;
1263
1361
 
1264
1362
  for (const arg of args) {
1265
- out.push(...generate(scope, arg));
1363
+ out = out.concat(generate(scope, arg));
1266
1364
  }
1267
1365
 
1268
1366
  out.push([ Opcodes.call, idx ]);
@@ -1273,7 +1371,7 @@ const generateCall = (scope, decl, _global, _name) => {
1273
1371
  const generateNew = (scope, decl, _global, _name) => {
1274
1372
  // hack: basically treat this as a normal call for builtins for now
1275
1373
  const name = mapName(decl.callee.name);
1276
- if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1374
+ if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1277
1375
  if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1278
1376
 
1279
1377
  return generateCall(scope, decl, _global, _name);
@@ -1291,12 +1389,12 @@ const unhackName = name => {
1291
1389
  };
1292
1390
 
1293
1391
  const generateVar = (scope, decl) => {
1294
- const out = [];
1392
+ let out = [];
1295
1393
 
1296
1394
  const topLevel = scope.name === 'main';
1297
1395
 
1298
1396
  // global variable if in top scope (main) and var ..., or if wanted
1299
- const global = decl.kind === 'var';
1397
+ const global = topLevel || decl._bare; // decl.kind === 'var';
1300
1398
  const target = global ? globals : scope.locals;
1301
1399
 
1302
1400
  for (const x of decl.declarations) {
@@ -1333,7 +1431,7 @@ const generateVar = (scope, decl) => {
1333
1431
 
1334
1432
  // x.init ??= DEFAULT_VALUE;
1335
1433
  if (x.init) {
1336
- out.push(...generate(scope, x.init, global, name));
1434
+ out = out.concat(generate(scope, x.init, global, name));
1337
1435
 
1338
1436
  // if our value is the result of a function, infer the type from that func's return value
1339
1437
  if (out[out.length - 1][0] === Opcodes.call) {
@@ -1402,13 +1500,60 @@ const generateAssign = (scope, decl) => {
1402
1500
  ];
1403
1501
  }
1404
1502
 
1503
+ const op = decl.operator.slice(0, -1) || '=';
1504
+
1505
+ // arr[i] | str[i]
1506
+ if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1507
+ const name = decl.left.object.name;
1508
+ const pointer = arrays.get(name);
1509
+
1510
+ const aotPointer = pointer != null;
1511
+
1512
+ const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1513
+ const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1514
+
1515
+ const parentType = getNodeType(scope, decl.left.object);
1516
+
1517
+ return [
1518
+ ...(aotPointer ? [] : [
1519
+ ...generate(scope, decl.left.object),
1520
+ Opcodes.i32_to_u
1521
+ ]),
1522
+
1523
+ // get index as valtype
1524
+ ...generate(scope, decl.left.property),
1525
+
1526
+ // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
1527
+ Opcodes.i32_to_u,
1528
+ ...number(ValtypeSize[valtype], Valtype.i32),
1529
+ [ Opcodes.i32_mul ],
1530
+ ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
1531
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1532
+
1533
+ ...(op === '=' ? generate(scope, decl.right, false, name) : performOp(scope, op, [
1534
+ [ Opcodes.local_get, pointerTmp ],
1535
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1536
+ ], generate(scope, decl.right), parentType === TYPES._array ? TYPES.number : TYPES.string, getNodeType(scope, decl.right), false, name, true)),
1537
+ [ Opcodes.local_tee, newValueTmp ],
1538
+
1539
+ ...(parentType === TYPES._array ? [
1540
+ [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1541
+ ] : [
1542
+ Opcodes.i32_to_u,
1543
+ [ StoreOps.i16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1544
+ ]),
1545
+
1546
+ [ Opcodes.local_get, newValueTmp ]
1547
+ ];
1548
+ }
1549
+
1405
1550
  const [ local, isGlobal ] = lookupName(scope, name);
1406
1551
 
1407
1552
  if (local === undefined) {
1408
- // todo: this should be a devtools/repl/??? only thing
1553
+ // todo: this should be a sloppy mode only thing
1409
1554
 
1410
1555
  // only allow = for this
1411
- if (decl.operator !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1556
+ if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1412
1557
 
1413
1558
  if (builtinVars[name]) {
1414
1559
  // just return rhs (eg `NaN = 2`)
@@ -1417,13 +1562,15 @@ const generateAssign = (scope, decl) => {
1417
1562
 
1418
1563
  // set global and return (eg a = 2)
1419
1564
  return [
1420
- ...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
1565
+ ...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
1421
1566
  [ Opcodes.global_get, globals[name].idx ]
1422
1567
  ];
1423
1568
  }
1424
1569
 
1425
- if (decl.operator === '=') {
1426
- typeStates[name] = getNodeType(scope, decl.right);
1570
+ typeStates[name] = getNodeType(scope, decl.right);
1571
+
1572
+ if (op === '=') {
1573
+ // typeStates[name] = getNodeType(scope, decl.right);
1427
1574
 
1428
1575
  return [
1429
1576
  ...generate(scope, decl.right, isGlobal, name),
@@ -1432,7 +1579,6 @@ const generateAssign = (scope, decl) => {
1432
1579
  ];
1433
1580
  }
1434
1581
 
1435
- const op = decl.operator.slice(0, -1);
1436
1582
  if (op === '||' || op === '&&' || op === '??') {
1437
1583
  // todo: is this needed?
1438
1584
  // for logical assignment ops, it is not left @= right ~= left = left @ right
@@ -1567,7 +1713,7 @@ const generateUpdate = (scope, decl) => {
1567
1713
  };
1568
1714
 
1569
1715
  const generateIf = (scope, decl) => {
1570
- const out = truthy(scope, generate(scope, decl.test), decl.test);
1716
+ const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test));
1571
1717
 
1572
1718
  out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1573
1719
  depth.push('if');
@@ -1660,18 +1806,106 @@ const generateWhile = (scope, decl) => {
1660
1806
  const generateForOf = (scope, decl) => {
1661
1807
  const out = [];
1662
1808
 
1809
+ const rightType = getNodeType(scope, decl.right);
1810
+ const valtypeSize = rightType === TYPES._array ? ValtypeSize[valtype] : ValtypeSize.i16; // presume array (:()
1811
+
1812
+ // todo: for of inside for of might fuck up?
1813
+ const pointer = localTmp(scope, 'forof_base_pointer', Valtype.i32);
1814
+ const length = localTmp(scope, 'forof_length', Valtype.i32);
1815
+ const counter = localTmp(scope, 'forof_counter', Valtype.i32);
1816
+
1817
+ out.push(
1818
+ // set pointer as right
1819
+ ...generate(scope, decl.right),
1820
+ Opcodes.i32_to_u,
1821
+ [ Opcodes.local_set, pointer ],
1822
+
1823
+ // get length
1824
+ [ Opcodes.local_get, pointer ],
1825
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
1826
+ [ Opcodes.local_set, length ]
1827
+ );
1828
+
1663
1829
  out.push([ Opcodes.loop, Blocktype.void ]);
1664
- depth.push('while');
1830
+ depth.push('forof');
1665
1831
 
1666
- out.push(...generate(scope, decl.test));
1667
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1668
- depth.push('if');
1832
+ // setup local for left
1833
+ generate(scope, decl.left);
1669
1834
 
1670
- out.push(...generate(scope, decl.body));
1835
+ const leftName = decl.left.declarations[0].id.name;
1671
1836
 
1672
- out.push([ Opcodes.br, 1 ]);
1673
- out.push([ Opcodes.end ], [ Opcodes.end ]);
1674
- depth.pop(); depth.pop();
1837
+ // set type for local
1838
+ typeStates[leftName] = rightType === TYPES._array ? TYPES.number : TYPES.string;
1839
+
1840
+ const [ local, isGlobal ] = lookupName(scope, leftName);
1841
+
1842
+ if (rightType === TYPES._array) { // array
1843
+ out.push(
1844
+ [ Opcodes.local_get, pointer ],
1845
+ [ Opcodes.load, Math.log2(valtypeSize) - 1, ...unsignedLEB128(ValtypeSize.i32) ]
1846
+ );
1847
+ } else { // string
1848
+ const [ newOut, newPointer ] = makeArray(scope, {
1849
+ rawElements: new Array(1)
1850
+ }, isGlobal, leftName, true, 'i16');
1851
+
1852
+ out.push(
1853
+ // setup new/out array
1854
+ ...newOut,
1855
+ [ Opcodes.drop ],
1856
+
1857
+ ...number(0, Valtype.i32), // base 0 for store after
1858
+
1859
+ // load current string ind {arg}
1860
+ [ Opcodes.local_get, pointer ],
1861
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
1862
+
1863
+ // store to new string ind 0
1864
+ [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
1865
+
1866
+ // return new string (page)
1867
+ ...number(newPointer)
1868
+ );
1869
+ }
1870
+
1871
+ // set left value
1872
+ out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ]);
1873
+
1874
+ out.push(
1875
+ [ Opcodes.block, Blocktype.void ],
1876
+ [ Opcodes.block, Blocktype.void ]
1877
+ );
1878
+ depth.push('block');
1879
+ depth.push('block');
1880
+
1881
+ out.push(
1882
+ ...generate(scope, decl.body),
1883
+ [ Opcodes.end ]
1884
+ );
1885
+ depth.pop();
1886
+
1887
+ out.push(
1888
+ // increment iter pointer by valtype size
1889
+ [ Opcodes.local_get, pointer ],
1890
+ ...number(valtypeSize, Valtype.i32),
1891
+ [ Opcodes.i32_add ],
1892
+ [ Opcodes.local_set, pointer ],
1893
+
1894
+ // increment counter by 1
1895
+ [ Opcodes.local_get, counter ],
1896
+ ...number(1, Valtype.i32),
1897
+ [ Opcodes.i32_add ],
1898
+ [ Opcodes.local_tee, counter ],
1899
+
1900
+ // loop if counter != length
1901
+ [ Opcodes.local_get, length ],
1902
+ [ Opcodes.i32_ne ],
1903
+ [ Opcodes.br_if, 1 ],
1904
+
1905
+ [ Opcodes.end ], [ Opcodes.end ]
1906
+ );
1907
+ depth.pop();
1908
+ depth.pop();
1675
1909
 
1676
1910
  return out;
1677
1911
  };
@@ -1791,7 +2025,7 @@ const itemTypeToValtype = {
1791
2025
  i16: 'i32'
1792
2026
  };
1793
2027
 
1794
- const storeOps = {
2028
+ const StoreOps = {
1795
2029
  i32: Opcodes.i32_store,
1796
2030
  i64: Opcodes.i64_store,
1797
2031
  f64: Opcodes.f64_store,
@@ -1800,10 +2034,28 @@ const storeOps = {
1800
2034
  i16: Opcodes.i32_store16
1801
2035
  };
1802
2036
 
2037
+ let data = [];
2038
+
2039
+ const compileBytes = (val, itemType, signed = true) => {
2040
+ switch (itemType) {
2041
+ case 'i8': return [ val % 256 ];
2042
+ case 'i16': return [ val % 256, Math.floor(val / 256) ];
2043
+
2044
+ case 'i32':
2045
+ case 'i64':
2046
+ return enforceFourBytes(signedLEB128(val));
2047
+
2048
+ case 'f64': return ieee754_binary64(val);
2049
+ }
2050
+ };
2051
+
1803
2052
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
1804
2053
  const out = [];
1805
2054
 
2055
+ let firstAssign = false;
1806
2056
  if (!arrays.has(name) || name === '$undeclared') {
2057
+ firstAssign = true;
2058
+
1807
2059
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1808
2060
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1809
2061
  arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
@@ -1814,8 +2066,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1814
2066
  const useRawElements = !!decl.rawElements;
1815
2067
  const elements = useRawElements ? decl.rawElements : decl.elements;
1816
2068
 
2069
+ const valtype = itemTypeToValtype[itemType];
1817
2070
  const length = elements.length;
1818
2071
 
2072
+ if (firstAssign && useRawElements) {
2073
+ let bytes = compileBytes(length, 'i32');
2074
+
2075
+ if (!initEmpty) for (let i = 0; i < length; i++) {
2076
+ if (elements[i] == null) continue;
2077
+
2078
+ bytes.push(...compileBytes(elements[i], itemType));
2079
+ }
2080
+
2081
+ data.push({
2082
+ offset: pointer,
2083
+ bytes
2084
+ });
2085
+
2086
+ // local value as pointer
2087
+ out.push(...number(pointer));
2088
+
2089
+ return [ out, pointer ];
2090
+ }
2091
+
1819
2092
  // store length as 0th array
1820
2093
  out.push(
1821
2094
  ...number(0, Valtype.i32),
@@ -1823,8 +2096,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1823
2096
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
1824
2097
  );
1825
2098
 
1826
- const storeOp = storeOps[itemType];
1827
- const valtype = itemTypeToValtype[itemType];
2099
+ const storeOp = StoreOps[itemType];
1828
2100
 
1829
2101
  if (!initEmpty) for (let i = 0; i < length; i++) {
1830
2102
  if (elements[i] == null) continue;
@@ -1847,6 +2119,17 @@ const generateArray = (scope, decl, global = false, name = '$undeclared', initEm
1847
2119
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
1848
2120
  };
1849
2121
 
2122
+ let varMetadata = new Map();
2123
+ const addVarMeta = (_name, obj) => {
2124
+ const name = _name ?? '$undeclared';
2125
+ if (!varMetadata.has(name)) varMetadata.set(name, {});
2126
+
2127
+ const meta = varMetadata.get(name);
2128
+ for (const k in obj) {
2129
+ meta[k] = obj[k];
2130
+ }
2131
+ };
2132
+
1850
2133
  export const generateMember = (scope, decl, _global, _name) => {
1851
2134
  const type = getNodeType(scope, decl.object);
1852
2135
 
@@ -2148,10 +2431,10 @@ const generateFunc = (scope, decl) => {
2148
2431
  };
2149
2432
 
2150
2433
  const generateCode = (scope, decl) => {
2151
- const out = [];
2434
+ let out = [];
2152
2435
 
2153
2436
  for (const x of decl.body) {
2154
- out.push(...generate(scope, x));
2437
+ out = out.concat(generate(scope, x));
2155
2438
  }
2156
2439
 
2157
2440
  return out;
@@ -2187,6 +2470,18 @@ const internalConstrs = {
2187
2470
  ];
2188
2471
  },
2189
2472
  type: TYPES._array
2473
+ },
2474
+
2475
+ __Array_of: {
2476
+ // this is not a constructor but best fits internal structure here
2477
+ generate: (scope, decl, global, name) => {
2478
+ // Array.of(i0, i1, ...)
2479
+ return generateArray(scope, {
2480
+ elements: decl.arguments
2481
+ }, global, name);
2482
+ },
2483
+ type: TYPES._array,
2484
+ notConstr: true
2190
2485
  }
2191
2486
  };
2192
2487
 
@@ -2200,7 +2495,9 @@ export default program => {
2200
2495
  depth = [];
2201
2496
  typeStates = {};
2202
2497
  arrays = new Map();
2498
+ varMetadata = new Map();
2203
2499
  pages = new Map();
2500
+ data = [];
2204
2501
  currentFuncIndex = importedFuncs.length;
2205
2502
 
2206
2503
  globalThis.valtype = 'f64';
@@ -2277,5 +2574,5 @@ export default program => {
2277
2574
  // if blank main func and other exports, remove it
2278
2575
  if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
2279
2576
 
2280
- return { funcs, globals, tags, exceptions, pages };
2577
+ return { funcs, globals, tags, exceptions, pages, data };
2281
2578
  };