porffor 0.0.0-05f898f → 0.0.0-1b0a5c6

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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # porffor
2
2
  a basic experimental wip *aot* optimizing js -> wasm/c engine/compiler/runtime in js. not serious/intended for (real) use. (this is a straight forward, honest readme)<br>
3
- age: ~1 month
3
+ age: ~2 months
4
4
 
5
5
  ## design
6
6
  porffor is a very unique js engine, due a very different approach. it is seriously limited, but what it can do, it does pretty well. key differences:
@@ -83,6 +83,8 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
83
83
  - truthy/falsy (eg `!'' == true`)
84
84
  - string comparison (eg `'a' == 'a'`, `'a' != 'b'`)
85
85
  - nullish coalescing operator (`??`)
86
+ - `for...of` (arrays and strings)
87
+ - array member setting (`arr[0] = 2`, `arr[0] += 2`, etc)
86
88
 
87
89
  ### built-ins
88
90
 
@@ -110,11 +112,9 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
110
112
  no particular order and no guarentees, just what could happen soon™
111
113
 
112
114
  - arrays
113
- - member setting (`arr[0] = 2`)
114
115
  - more of `Array` prototype
115
116
  - arrays/strings inside arrays
116
117
  - destructuring
117
- - for .. of
118
118
  - strings
119
119
  - member setting
120
120
  - objects
@@ -135,7 +135,7 @@ no particular order and no guarentees, just what could happen soon™
135
135
  - use data segments for initing arrays
136
136
 
137
137
  ## porfformance
138
- *for the things it supports*, porffor is blazingly faster compared to most interpreters, and engines running without JIT. for those with JIT, it is not that much slower like a traditional interpreter would be.
138
+ *for the things it supports most of the time*, porffor is blazingly fast compared to most interpreters, and common engines running without JIT. for those with JIT, it is not that much slower like a traditional interpreter would be; mostly the same or a bit faster/slower depending on what.
139
139
 
140
140
  ![Screenshot of comparison chart](https://github.com/CanadaHonk/porffor/assets/19228318/76c75264-cc68-4be1-8891-c06dc389d97a)
141
141
 
@@ -26,6 +26,12 @@ export const importedFuncs = [
26
26
  import: 't',
27
27
  params: 0,
28
28
  returns: 1
29
+ },
30
+ {
31
+ name: 'printStr',
32
+ import: 's',
33
+ params: 1,
34
+ returns: 0
29
35
  }
30
36
  ];
31
37
 
@@ -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
+ }
182
189
 
183
- let inst = Opcodes[asm[0].replace('.', '_')];
184
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
190
+ return out;
191
+ },
185
192
 
186
- if (!Array.isArray(inst)) inst = [ inst ];
187
- const immediates = asm.slice(1).map(x => parseInt(x));
193
+ __internal_print_type: str => {
194
+ const type = getType(scope, str) - TYPES.number;
188
195
 
189
- out.push([ ...inst, ...immediates ]);
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}!`);
@@ -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,
@@ -1823,7 +2001,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1823
2001
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
1824
2002
  );
1825
2003
 
1826
- const storeOp = storeOps[itemType];
2004
+ const storeOp = StoreOps[itemType];
1827
2005
  const valtype = itemTypeToValtype[itemType];
1828
2006
 
1829
2007
  if (!initEmpty) for (let i = 0; i < length; i++) {
@@ -2148,10 +2326,10 @@ const generateFunc = (scope, decl) => {
2148
2326
  };
2149
2327
 
2150
2328
  const generateCode = (scope, decl) => {
2151
- const out = [];
2329
+ let out = [];
2152
2330
 
2153
2331
  for (const x of decl.body) {
2154
- out.push(...generate(scope, x));
2332
+ out = out.concat(generate(scope, x));
2155
2333
  }
2156
2334
 
2157
2335
  return out;
package/compiler/index.js CHANGED
@@ -78,7 +78,7 @@ export default (code, flags) => {
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 };
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
@@ -23,6 +23,9 @@ const TYPES = {
23
23
 
24
24
  export const PrototypeFuncs = function() {
25
25
  const noUnlikelyChecks = process.argv.includes('-funsafe-no-unlikely-proto-checks');
26
+ let zeroChecks = process.argv.find(x => x.startsWith('-funsafe-zero-proto-checks='));
27
+ if (zeroChecks) zeroChecks = zeroChecks.split('=')[1].split(',').reduce((acc, x) => { acc[x.toLowerCase()] = true; return acc; }, {});
28
+ else zeroChecks = {};
26
29
 
27
30
  this[TYPES._array] = {
28
31
  // lX = local accessor of X ({ get, set }), iX = local index of X, wX = wasm ops of X
@@ -36,7 +39,7 @@ export const PrototypeFuncs = function() {
36
39
  [ Opcodes.i32_lt_s ],
37
40
  [ Opcodes.if, Blocktype.void ],
38
41
  [ Opcodes.local_get, iTmp ],
39
- ...length.cachedI32,
42
+ ...length.getCachedI32(),
40
43
  [ Opcodes.i32_add ],
41
44
  [ Opcodes.local_set, iTmp ],
42
45
  [ Opcodes.end ],
@@ -47,7 +50,7 @@ export const PrototypeFuncs = function() {
47
50
  [ Opcodes.i32_lt_s ],
48
51
 
49
52
  [ Opcodes.local_get, iTmp ],
50
- ...length.cachedI32,
53
+ ...length.getCachedI32(),
51
54
  [ Opcodes.i32_ge_s ],
52
55
  [ Opcodes.i32_or ],
53
56
 
@@ -67,7 +70,7 @@ export const PrototypeFuncs = function() {
67
70
  // todo: only for 1 argument
68
71
  push: (pointer, length, wNewMember) => [
69
72
  // get memory offset of array at last index (length)
70
- ...length.cachedI32,
73
+ ...length.getCachedI32(),
71
74
  ...number(ValtypeSize[valtype], Valtype.i32),
72
75
  [ Opcodes.i32_mul ],
73
76
 
@@ -79,17 +82,17 @@ export const PrototypeFuncs = function() {
79
82
 
80
83
  // bump array length by 1 and return it
81
84
  ...length.setI32([
82
- ...length.cachedI32,
85
+ ...length.getCachedI32(),
83
86
  ...number(1, Valtype.i32),
84
87
  [ Opcodes.i32_add ]
85
88
  ]),
86
89
 
87
- ...length.get
90
+ ...length.get()
88
91
  ],
89
92
 
90
93
  pop: (pointer, length) => [
91
94
  // if length == 0, noop
92
- ...length.cachedI32,
95
+ ...length.getCachedI32(),
93
96
  [ Opcodes.i32_eqz ],
94
97
  [ Opcodes.if, Blocktype.void ],
95
98
  ...number(UNDEFINED),
@@ -100,13 +103,13 @@ export const PrototypeFuncs = function() {
100
103
 
101
104
  // decrement length by 1
102
105
  ...length.setI32([
103
- ...length.cachedI32,
106
+ ...length.getCachedI32(),
104
107
  ...number(1, Valtype.i32),
105
108
  [ Opcodes.i32_sub ]
106
109
  ]),
107
110
 
108
111
  // load last element
109
- ...length.cachedI32,
112
+ ...length.getCachedI32(),
110
113
  ...number(ValtypeSize[valtype], Valtype.i32),
111
114
  [ Opcodes.i32_mul ],
112
115
 
@@ -115,7 +118,7 @@ export const PrototypeFuncs = function() {
115
118
 
116
119
  shift: (pointer, length) => [
117
120
  // if length == 0, noop
118
- ...length.cachedI32,
121
+ ...length.getCachedI32(),
119
122
  Opcodes.i32_eqz,
120
123
  [ Opcodes.if, Blocktype.void ],
121
124
  ...number(UNDEFINED),
@@ -126,7 +129,7 @@ export const PrototypeFuncs = function() {
126
129
 
127
130
  // decrement length by 1
128
131
  ...length.setI32([
129
- ...length.cachedI32,
132
+ ...length.getCachedI32(),
130
133
  ...number(1, Valtype.i32),
131
134
  [ Opcodes.i32_sub ]
132
135
  ]),
@@ -140,11 +143,66 @@ export const PrototypeFuncs = function() {
140
143
  ...number(pointer + ValtypeSize.i32 + ValtypeSize[valtype], Valtype.i32), // src = base array index + length size + an index
141
144
  ...number(pageSize - ValtypeSize.i32 - ValtypeSize[valtype], Valtype.i32), // size = PageSize - length size - an index
142
145
  [ ...Opcodes.memory_copy, 0x00, 0x00 ]
146
+ ],
147
+
148
+ fill: (pointer, length, wElement, iTmp) => [
149
+ ...wElement,
150
+ [ Opcodes.local_set, iTmp ],
151
+
152
+ // use cached length i32 as pointer
153
+ ...length.getCachedI32(),
154
+
155
+ // length - 1 for indexes
156
+ ...number(1, Valtype.i32),
157
+ [ Opcodes.i32_sub ],
158
+
159
+ // * sizeof value
160
+ ...number(ValtypeSize[valtype], Valtype.i32),
161
+ [ Opcodes.i32_mul ],
162
+
163
+ ...length.setCachedI32(),
164
+
165
+ ...(noUnlikelyChecks ? [] : [
166
+ ...length.getCachedI32(),
167
+ ...number(0, Valtype.i32),
168
+ [ Opcodes.i32_lt_s ],
169
+ [ Opcodes.if, Blocktype.void ],
170
+ ...number(pointer),
171
+ [ Opcodes.br, 1 ],
172
+ [ Opcodes.end ]
173
+ ]),
174
+
175
+ [ Opcodes.loop, Blocktype.void ],
176
+
177
+ // set element using pointer
178
+ ...length.getCachedI32(),
179
+ [ Opcodes.local_get, iTmp ],
180
+ [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32) ],
181
+
182
+ // pointer - sizeof value
183
+ ...length.getCachedI32(),
184
+ ...number(ValtypeSize[valtype], Valtype.i32),
185
+ [ Opcodes.i32_sub ],
186
+
187
+ ...length.setCachedI32(),
188
+
189
+ // if pointer >= 0, loop
190
+ ...length.getCachedI32(),
191
+ ...number(0, Valtype.i32),
192
+ [ Opcodes.i32_ge_s ],
193
+ [ Opcodes.br_if, 0 ],
194
+
195
+ [ Opcodes.end ],
196
+
197
+ // return this array
198
+ ...number(pointer)
143
199
  ]
144
200
  };
145
201
 
146
202
  this[TYPES._array].at.local = Valtype.i32;
147
203
  this[TYPES._array].push.noArgRetLength = true;
204
+ this[TYPES._array].fill.local = valtypeBinary;
205
+ this[TYPES._array].fill.returnType = TYPES._array;
148
206
 
149
207
  this[TYPES.string] = {
150
208
  at: (pointer, length, wIndex, iTmp, arrayShell) => {
@@ -166,7 +224,7 @@ export const PrototypeFuncs = function() {
166
224
  [ Opcodes.i32_lt_s ],
167
225
  [ Opcodes.if, Blocktype.void ],
168
226
  [ Opcodes.local_get, iTmp ],
169
- ...length.cachedI32,
227
+ ...length.getCachedI32(),
170
228
  [ Opcodes.i32_add ],
171
229
  [ Opcodes.local_set, iTmp ],
172
230
  [ Opcodes.end ],
@@ -177,7 +235,7 @@ export const PrototypeFuncs = function() {
177
235
  [ Opcodes.i32_lt_s ],
178
236
 
179
237
  [ Opcodes.local_get, iTmp ],
180
- ...length.cachedI32,
238
+ ...length.getCachedI32(),
181
239
  [ Opcodes.i32_ge_s ],
182
240
  [ Opcodes.i32_or ],
183
241
 
@@ -233,27 +291,31 @@ export const PrototypeFuncs = function() {
233
291
  return [
234
292
  ...wIndex,
235
293
  Opcodes.i32_to,
236
- [ Opcodes.local_set, iTmp ],
237
294
 
238
- // index < 0
239
- ...(noUnlikelyChecks ? [] : [
295
+ ...(zeroChecks.charcodeat ? [] : [
296
+ [ Opcodes.local_set, iTmp ],
297
+
298
+ // index < 0
299
+ ...(noUnlikelyChecks ? [] : [
300
+ [ Opcodes.local_get, iTmp ],
301
+ ...number(0, Valtype.i32),
302
+ [ Opcodes.i32_lt_s ],
303
+ ]),
304
+
305
+ // index >= length
240
306
  [ Opcodes.local_get, iTmp ],
241
- ...number(0, Valtype.i32),
242
- [ Opcodes.i32_lt_s ],
243
- ]),
307
+ ...length.getCachedI32(),
308
+ [ Opcodes.i32_ge_s ],
244
309
 
245
- // index >= length
246
- [ Opcodes.local_get, iTmp ],
247
- ...length.cachedI32,
248
- [ Opcodes.i32_ge_s ],
310
+ ...(noUnlikelyChecks ? [] : [ [ Opcodes.i32_or ] ]),
311
+ [ Opcodes.if, Blocktype.void ],
312
+ ...number(NaN),
313
+ [ Opcodes.br, 1 ],
314
+ [ Opcodes.end ],
249
315
 
250
- ...(noUnlikelyChecks ? [] : [ [ Opcodes.i32_or ] ]),
251
- [ Opcodes.if, Blocktype.void ],
252
- ...number(NaN),
253
- [ Opcodes.br, 1 ],
254
- [ Opcodes.end ],
316
+ [ Opcodes.local_get, iTmp ],
317
+ ]),
255
318
 
256
- [ Opcodes.local_get, iTmp ],
257
319
  ...number(ValtypeSize.i16, Valtype.i32),
258
320
  [ Opcodes.i32_mul ],
259
321
 
package/compiler/wrap.js CHANGED
@@ -34,11 +34,18 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
34
34
  times.push(performance.now() - t1);
35
35
  if (flags.includes('info')) console.log(bold(`compiled in ${times[0].toFixed(2)}ms`));
36
36
 
37
+ const getString = pointer => {
38
+ const length = new Int32Array(memory.buffer, pointer, 1);
39
+
40
+ return Array.from(new Uint16Array(memory.buffer, pointer + 4, length)).map(x => String.fromCharCode(x)).join('');
41
+ };
42
+
37
43
  const t2 = performance.now();
38
44
  const { instance } = await WebAssembly.instantiate(wasm, {
39
45
  '': {
40
46
  p: valtype === 'i64' ? i => print(Number(i).toString()) : i => print(i.toString()),
41
47
  c: valtype === 'i64' ? i => print(String.fromCharCode(Number(i))) : i => print(String.fromCharCode(i)),
48
+ s: valtype === 'i64' ? i => print(getString(Number(i))) : i => print(getString(i)),
42
49
  a: c => { if (!Number(c)) throw new Error(`assert failed`); },
43
50
  t: _ => performance.now(),
44
51
  ...customImports
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "porffor",
3
3
  "description": "a basic experimental wip aot optimizing js -> wasm engine/compiler/runtime in js",
4
- "version": "0.0.0-05f898f",
4
+ "version": "0.0.0-1b0a5c6",
5
5
  "author": "CanadaHonk",
6
6
  "license": "MIT",
7
7
  "dependencies": {
package/r.js CHANGED
@@ -1 +1,39 @@
1
- print(performance.now());
1
+ compareArray.isSameValue = function(a, b) {
2
+ if (a === 0 && b === 0) return 1 / a === 1 / b;
3
+ if (a !== a && b !== b) return true;
4
+
5
+ return a === b;
6
+ };
7
+
8
+ function compareArray(a, b) {
9
+ // if either are nullish
10
+ if (a == null || b == null) return false;
11
+
12
+ // megahack: all arrays from now on will be >0 pointer
13
+ const _hack = '';
14
+
15
+ // hack: enforce type inference of being arrays
16
+ a ??= [];
17
+ b ??= [];
18
+
19
+ if (b.length !== a.length) {
20
+ return false;
21
+ }
22
+
23
+ for (var i = 0; i < a.length; i++) {
24
+ if (!compareArray.isSameValue(b[i], a[i])) {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ return true;
30
+ }
31
+
32
+ console.log(compareArray(null, []));
33
+ console.log(compareArray(undefined, []));
34
+
35
+ console.log(compareArray([], []));
36
+ console.log(compareArray([ 1 ], []));
37
+ console.log(compareArray([ 1 ], [ 1 ]));
38
+ console.log(compareArray([ 1, 2 ], [ 1 ]));
39
+ console.log(compareArray([ 1, 2 ], [ 1, 2 ]));
package/rhemyn/compile.js CHANGED
@@ -21,7 +21,7 @@ const generate = (node, negated = false, get = true, func = 'test') => {
21
21
  out = [
22
22
  // set length local
23
23
  [ Opcodes.local_get, BasePointer ],
24
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(0) ],
24
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
25
25
  [ Opcodes.local_set, Length ],
26
26
 
27
27
  // set iter pointer local as base + sizeof i32 initially
package/runner/index.js CHANGED
@@ -34,12 +34,14 @@ const source = fs.readFileSync(file, 'utf8');
34
34
 
35
35
  let cache = '';
36
36
  const print = str => {
37
- cache += str;
37
+ /* cache += str;
38
38
 
39
39
  if (str === '\n') {
40
40
  process.stdout.write(cache);
41
41
  cache = '';
42
- } else print('\n');
42
+ } */
43
+
44
+ process.stdout.write(str);
43
45
  };
44
46
 
45
47
  try {
@@ -49,5 +51,5 @@ try {
49
51
  if (cache) process.stdout.write(cache);
50
52
  } catch (e) {
51
53
  if (cache) process.stdout.write(cache);
52
- console.error(`${e.constructor.name}: ${e.message}`);
54
+ console.error(process.argv.includes('-i') ? e : `${e.constructor.name}: ${e.message}`);
53
55
  }
package/runner/info.js CHANGED
@@ -36,7 +36,7 @@ const print = str => {
36
36
  };
37
37
 
38
38
  const t0 = performance.now();
39
- const { wasm, exports } = await compile(source, raw ? [ 'module' ] : [ 'module', 'info' ], {}, print);
39
+ const { wasm, exports, pages } = await compile(source, raw ? [ 'module' ] : [ 'module', 'info' ], {}, print);
40
40
 
41
41
  if (!raw && typeof Deno === 'undefined') fs.writeFileSync('out.wasm', Buffer.from(wasm));
42
42
 
@@ -51,4 +51,39 @@ if (!process.argv.includes('-no-run')) {
51
51
  }
52
52
 
53
53
  if (!raw) console.log(bold(`wasm binary is ${wasm.byteLength} bytes`));
54
- if (!raw) console.log(`total: ${(performance.now() - t0).toFixed(2)}ms`);
54
+ if (!raw) console.log(`total: ${(performance.now() - t0).toFixed(2)}ms`);
55
+
56
+ if (!raw && process.argv.includes('-mem') && exports.$) {
57
+ console.log();
58
+
59
+ let lastMemory, lastPages;
60
+ const PageSize = 65536;
61
+ const memoryToString = mem => {
62
+ let out = '';
63
+ const pages = lastPages.length;
64
+ const wasmPages = mem.buffer.byteLength / PageSize;
65
+
66
+ out += `\x1B[1mallocated ${mem.buffer.byteLength / 1024}KiB\x1B[0m for ${pages} things using ${wasmPages} Wasm page${wasmPages === 1 ? '' : 's'}\n`;
67
+
68
+ const buf = new Uint8Array(mem.buffer);
69
+
70
+ for (let i = 0; i < pages; i++) {
71
+ out += `\x1B[36m${lastPages[i]}\x1B[2m | \x1B[0m`;
72
+
73
+ for (let j = 0; j < 50; j++) {
74
+ const val = buf[i * pageSize + j];
75
+ if (val === 0) out += '\x1B[2m';
76
+ out += val.toString(16).padStart(2, '0');
77
+ if (val === 0) out += '\x1B[0m';
78
+ out += ' ';
79
+ }
80
+ out += '\n';
81
+ }
82
+
83
+ return out;
84
+ };
85
+
86
+ lastPages = [...pages.keys()];
87
+ lastMemory = exports.$;
88
+ console.log(memoryToString(lastMemory));
89
+ }