porffor 0.0.0-ba812f2 → 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,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`);
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
+ },
182
192
 
183
- let inst = Opcodes[asm[0].replace('.', '_')];
184
- 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;
185
195
 
186
- if (!Array.isArray(inst)) inst = [ inst ];
187
- const immediates = asm.slice(1).map(x => parseInt(x));
196
+ return [
197
+ ...number(type),
198
+ [ Opcodes.call, importedFuncs.print ],
188
199
 
189
- out.push([ ...inst, ...immediates ]);
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}!`);
@@ -665,29 +686,44 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
665
686
 
666
687
  if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
667
688
 
668
- // if strict (in)equal and known types mismatch, return false (===)/true (!==)
669
- 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
+
670
705
  return [
671
706
  ...left,
672
- ...right,
673
-
674
- // drop values
675
707
  [ Opcodes.drop ],
708
+
709
+ ...right,
676
710
  [ Opcodes.drop ],
677
711
 
678
- // return false (===)/true (!==)
679
- ...number(op === '===' ? 0 : 1, Valtype.i32)
712
+ // return true (!=/!==) or false (else)
713
+ ...number(op === '!=' || op === '!==' ? 1 : 0, Valtype.i32)
680
714
  ];
681
715
  }
682
716
 
717
+ // todo: niche null hell with 0
718
+
683
719
  if (leftType === TYPES.string || rightType === TYPES.string) {
684
720
  if (op === '+') {
685
721
  // string concat (a + b)
686
722
  return concatStrings(scope, left, right, _global, _name, assign);
687
723
  }
688
724
 
689
- // any other math op, NaN
690
- if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
725
+ // not an equality op, NaN
726
+ if (!eqOp) return number(NaN);
691
727
 
692
728
  // else leave bool ops
693
729
  // todo: convert string to number if string and number/bool
@@ -732,17 +768,11 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
732
768
  ];
733
769
  };
734
770
 
735
- let binaryExpDepth = 0;
736
771
  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
- ];
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);
742
773
 
743
774
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
744
775
 
745
- binaryExpDepth--;
746
776
  return out;
747
777
  };
748
778
 
@@ -999,6 +1029,8 @@ const countLeftover = wasm => {
999
1029
  } else count--;
1000
1030
  if (func) count += func.returns.length;
1001
1031
  } else count--;
1032
+
1033
+ // console.log(count, decompile([ inst ]).slice(0, -1));
1002
1034
  }
1003
1035
 
1004
1036
  return count;
@@ -1185,24 +1217,34 @@ const generateCall = (scope, decl, _global, _name) => {
1185
1217
  // use local for cached i32 length as commonly used
1186
1218
  let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1187
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
+
1188
1238
  return [
1189
1239
  ...out,
1190
1240
 
1191
- ...arrayUtil.getLengthI32(pointer),
1192
- [ Opcodes.local_set, lengthLocal ],
1241
+ ...(!lengthI32CacheUsed ? [] : [
1242
+ ...arrayUtil.getLengthI32(pointer),
1243
+ [ Opcodes.local_set, lengthLocal ],
1244
+ ]),
1193
1245
 
1194
1246
  [ 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
- }),
1247
+ ...protoOut,
1206
1248
  [ Opcodes.end ]
1207
1249
  ];
1208
1250
  }
@@ -1262,7 +1304,7 @@ const generateCall = (scope, decl, _global, _name) => {
1262
1304
  if (func && func.throws) scope.throws = true;
1263
1305
 
1264
1306
  for (const arg of args) {
1265
- out.push(...generate(scope, arg));
1307
+ out = out.concat(generate(scope, arg));
1266
1308
  }
1267
1309
 
1268
1310
  out.push([ Opcodes.call, idx ]);
@@ -1402,13 +1444,60 @@ const generateAssign = (scope, decl) => {
1402
1444
  ];
1403
1445
  }
1404
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
+
1405
1494
  const [ local, isGlobal ] = lookupName(scope, name);
1406
1495
 
1407
1496
  if (local === undefined) {
1408
1497
  // todo: this should be a devtools/repl/??? only thing
1409
1498
 
1410
1499
  // only allow = for this
1411
- if (decl.operator !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1500
+ if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1412
1501
 
1413
1502
  if (builtinVars[name]) {
1414
1503
  // just return rhs (eg `NaN = 2`)
@@ -1422,8 +1511,10 @@ const generateAssign = (scope, decl) => {
1422
1511
  ];
1423
1512
  }
1424
1513
 
1425
- if (decl.operator === '=') {
1426
- typeStates[name] = getNodeType(scope, decl.right);
1514
+ typeStates[name] = getNodeType(scope, decl.right);
1515
+
1516
+ if (op === '=') {
1517
+ // typeStates[name] = getNodeType(scope, decl.right);
1427
1518
 
1428
1519
  return [
1429
1520
  ...generate(scope, decl.right, isGlobal, name),
@@ -1432,7 +1523,6 @@ const generateAssign = (scope, decl) => {
1432
1523
  ];
1433
1524
  }
1434
1525
 
1435
- const op = decl.operator.slice(0, -1);
1436
1526
  if (op === '||' || op === '&&' || op === '??') {
1437
1527
  // todo: is this needed?
1438
1528
  // for logical assignment ops, it is not left @= right ~= left = left @ right
@@ -1567,7 +1657,7 @@ const generateUpdate = (scope, decl) => {
1567
1657
  };
1568
1658
 
1569
1659
  const generateIf = (scope, decl) => {
1570
- const out = truthy(scope, generate(scope, decl.test), decl.test);
1660
+ const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test));
1571
1661
 
1572
1662
  out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1573
1663
  depth.push('if');
@@ -1660,18 +1750,106 @@ const generateWhile = (scope, decl) => {
1660
1750
  const generateForOf = (scope, decl) => {
1661
1751
  const out = [];
1662
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
+
1663
1773
  out.push([ Opcodes.loop, Blocktype.void ]);
1664
- depth.push('while');
1774
+ depth.push('forof');
1665
1775
 
1666
- out.push(...generate(scope, decl.test));
1667
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1668
- depth.push('if');
1776
+ // setup local for left
1777
+ generate(scope, decl.left);
1669
1778
 
1670
- out.push(...generate(scope, decl.body));
1779
+ const leftName = decl.left.declarations[0].id.name;
1671
1780
 
1672
- out.push([ Opcodes.br, 1 ]);
1673
- out.push([ Opcodes.end ], [ Opcodes.end ]);
1674
- 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();
1675
1853
 
1676
1854
  return out;
1677
1855
  };
@@ -1791,7 +1969,7 @@ const itemTypeToValtype = {
1791
1969
  i16: 'i32'
1792
1970
  };
1793
1971
 
1794
- const storeOps = {
1972
+ const StoreOps = {
1795
1973
  i32: Opcodes.i32_store,
1796
1974
  i64: Opcodes.i64_store,
1797
1975
  f64: Opcodes.f64_store,
@@ -1800,10 +1978,25 @@ const storeOps = {
1800
1978
  i16: Opcodes.i32_store16
1801
1979
  };
1802
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
+
1803
1993
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
1804
1994
  const out = [];
1805
1995
 
1996
+ let firstAssign = false;
1806
1997
  if (!arrays.has(name) || name === '$undeclared') {
1998
+ firstAssign = true;
1999
+
1807
2000
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1808
2001
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1809
2002
  arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
@@ -1814,8 +2007,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1814
2007
  const useRawElements = !!decl.rawElements;
1815
2008
  const elements = useRawElements ? decl.rawElements : decl.elements;
1816
2009
 
2010
+ const valtype = itemTypeToValtype[itemType];
1817
2011
  const length = elements.length;
1818
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
+
1819
2033
  // store length as 0th array
1820
2034
  out.push(
1821
2035
  ...number(0, Valtype.i32),
@@ -1823,8 +2037,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1823
2037
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
1824
2038
  );
1825
2039
 
1826
- const storeOp = storeOps[itemType];
1827
- const valtype = itemTypeToValtype[itemType];
2040
+ const storeOp = StoreOps[itemType];
1828
2041
 
1829
2042
  if (!initEmpty) for (let i = 0; i < length; i++) {
1830
2043
  if (elements[i] == null) continue;
@@ -2148,10 +2361,10 @@ const generateFunc = (scope, decl) => {
2148
2361
  };
2149
2362
 
2150
2363
  const generateCode = (scope, decl) => {
2151
- const out = [];
2364
+ let out = [];
2152
2365
 
2153
2366
  for (const x of decl.body) {
2154
- out.push(...generate(scope, x));
2367
+ out = out.concat(generate(scope, x));
2155
2368
  }
2156
2369
 
2157
2370
  return out;
@@ -2201,6 +2414,7 @@ export default program => {
2201
2414
  typeStates = {};
2202
2415
  arrays = new Map();
2203
2416
  pages = new Map();
2417
+ data = [];
2204
2418
  currentFuncIndex = importedFuncs.length;
2205
2419
 
2206
2420
  globalThis.valtype = 'f64';
@@ -2277,5 +2491,5 @@ export default program => {
2277
2491
  // if blank main func and other exports, remove it
2278
2492
  if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
2279
2493
 
2280
- return { funcs, globals, tags, exceptions, pages };
2494
+ return { funcs, globals, tags, exceptions, pages, data };
2281
2495
  };
@@ -9,11 +9,15 @@ export const number = (n, valtype = valtypeBinary) => {
9
9
  }
10
10
  };
11
11
 
12
- const enforceTwoBytes = arr => [ arr[0] ?? 0, arr[1] ?? 0, arr[2] ?? 0, arr[3] ?? 0 ];
12
+ export const enforceOneByte = arr => [ arr[0] ?? 0 ];
13
+ export const enforceTwoBytes = arr => [ arr[0] ?? 0, arr[1] ?? 0 ];
14
+ export const enforceFourBytes = arr => [ arr[0] ?? 0, arr[1] ?? 0, arr[2] ?? 0, arr[3] ?? 0 ];
15
+ export const enforceEightBytes = arr => [ arr[0] ?? 0, arr[1] ?? 0, arr[2] ?? 0, arr[3] ?? 0, arr[4] ?? 0, arr[5] ?? 0, arr[6] ?? 0, arr[7] ?? 0 ];
16
+
13
17
  export const i32x4 = (a, b, c, d) => [ [
14
18
  ...Opcodes.v128_const,
15
- ...enforceTwoBytes(signedLEB128(a)),
16
- ...enforceTwoBytes(signedLEB128(b)),
17
- ...enforceTwoBytes(signedLEB128(c)),
18
- ...enforceTwoBytes(signedLEB128(d))
19
+ ...enforceFourBytes(signedLEB128(a)),
20
+ ...enforceFourBytes(signedLEB128(b)),
21
+ ...enforceFourBytes(signedLEB128(c)),
22
+ ...enforceFourBytes(signedLEB128(d))
19
23
  ] ];
package/compiler/index.js CHANGED
@@ -59,7 +59,7 @@ export default (code, flags) => {
59
59
  if (flags.includes('info')) console.log(`1. parsed in ${(performance.now() - t0).toFixed(2)}ms`);
60
60
 
61
61
  const t1 = performance.now();
62
- const { funcs, globals, tags, exceptions, pages } = codeGen(program);
62
+ const { funcs, globals, tags, exceptions, pages, data } = codeGen(program);
63
63
  if (flags.includes('info')) console.log(`2. generated code in ${(performance.now() - t1).toFixed(2)}ms`);
64
64
 
65
65
  if (process.argv.includes('-funcs')) logFuncs(funcs, globals, exceptions);
@@ -71,14 +71,14 @@ export default (code, flags) => {
71
71
  if (process.argv.includes('-opt-funcs')) logFuncs(funcs, globals, exceptions);
72
72
 
73
73
  const t3 = performance.now();
74
- const sections = produceSections(funcs, globals, tags, pages, flags);
74
+ const sections = produceSections(funcs, globals, tags, pages, data, flags);
75
75
  if (flags.includes('info')) console.log(`4. produced sections in ${(performance.now() - t3).toFixed(2)}ms`);
76
76
 
77
77
  if (allocLog) {
78
78
  const wasmPages = Math.ceil((pages.size * pageSize) / 65536);
79
79
  const bytes = wasmPages * 65536;
80
80
  log('alloc', `\x1B[1mallocated ${bytes / 1024}KiB\x1B[0m for ${pages.size} things using ${wasmPages} Wasm page${wasmPages === 1 ? '' : 's'}`);
81
- // console.log([...pages.keys()].map(x => `\x1B[36m - ${x}\x1B[0m`).join('\n'));
81
+ console.log([...pages.keys()].map(x => `\x1B[36m - ${x}\x1B[0m`).join('\n') + '\n');
82
82
  }
83
83
 
84
84
  const out = { wasm: sections, funcs, globals, tags, exceptions, pages };
@@ -100,10 +100,10 @@ export default (code, flags) => {
100
100
 
101
101
  if (target === 'native') {
102
102
  const compiler = getArg('compiler') ?? 'clang';
103
- const cO = getArg('cO') ?? 'O3';
103
+ const cO = getArg('cO') ?? 'Ofast';
104
104
 
105
105
  const tmpfile = 'tmp.c';
106
- const args = [ compiler, tmpfile, '-o', outFile ?? (process.platform === 'win32' ? 'out.exe' : 'out'), '-' + cO ];
106
+ const args = [ compiler, tmpfile, '-o', outFile ?? (process.platform === 'win32' ? 'out.exe' : 'out'), '-' + cO, '-march=native' ];
107
107
 
108
108
  const c = toc(out);
109
109
  writeFileSync(tmpfile, c);
package/compiler/opt.js CHANGED
@@ -317,28 +317,24 @@ export default (funcs, globals) => {
317
317
  continue;
318
318
  }
319
319
 
320
- if (i < 2) continue;
321
- const lastLastInst = wasm[i - 2];
320
+ // remove unneeded before get with update exprs (n++, etc) when value is unused
321
+ if (i < wasm.length - 4 && lastInst[1] === inst[1] && lastInst[0] === Opcodes.local_get && inst[0] === Opcodes.local_get && wasm[i + 1][0] === Opcodes.const && [Opcodes.add, Opcodes.sub].includes(wasm[i + 2][0]) && wasm[i + 3][0] === Opcodes.local_set && wasm[i + 3][1] === inst[1] && (wasm[i + 4][0] === Opcodes.drop || wasm[i + 4][0] === Opcodes.br)) {
322
+ // local.get 1
323
+ // local.get 1
324
+ // -->
325
+ // local.get 1
322
326
 
323
- if (depth.length === 2) {
324
- // hack to remove unneeded before get in for loops with (...; i++)
325
- if (lastLastInst[0] === Opcodes.end && lastInst[1] === inst[1] && lastInst[0] === Opcodes.local_get && inst[0] === Opcodes.local_get) {
326
- // local.get 1
327
- // local.get 1
328
- // -->
329
- // local.get 1
330
-
331
- // remove drop at the end as well
332
- if (wasm[i + 4][0] === Opcodes.drop) {
333
- wasm.splice(i + 4, 1);
334
- }
327
+ // remove drop at the end as well
328
+ if (wasm[i + 4][0] === Opcodes.drop) wasm.splice(i + 4, 1);
335
329
 
336
- wasm.splice(i, 1); // remove this inst (second get)
337
- i--;
338
- continue;
339
- }
330
+ wasm.splice(i, 1); // remove this inst (second get)
331
+ i--;
332
+ continue;
340
333
  }
341
334
 
335
+ if (i < 2) continue;
336
+ const lastLastInst = wasm[i - 2];
337
+
342
338
  if (lastLastInst[1] === inst[1] && inst[0] === Opcodes.local_get && lastInst[0] === Opcodes.local_tee && lastLastInst[0] === Opcodes.local_set) {
343
339
  // local.set x
344
340
  // local.tee y
package/compiler/parse.js CHANGED
@@ -1,5 +1,5 @@
1
- import { parse } from 'acorn';
2
- // const { parse } = (await import(globalThis.document ? 'https://esm.sh/acorn' : 'acorn'));
1
+ // import { parse } from 'acorn';
2
+ const { parse } = (await import(globalThis.document ? 'https://esm.sh/acorn' : 'acorn'));
3
3
 
4
4
  export default (input, flags) => {
5
5
  return parse(input, {