porffor 0.2.0-e562242 → 0.2.0-eeb45f8

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
@@ -143,8 +143,8 @@ No particular order and no guarentees, just what could happen soon™
143
143
  - [`do` expressions](https://github.com/tc39/proposal-do-expressions)
144
144
  - [String Trim Characters](https://github.com/Kingwl/proposal-string-trim-characters)
145
145
  - Posts
146
- - Type annotations for performance
147
146
  - Inlining investigation
147
+ - Self hosted testing?
148
148
 
149
149
  ## Performance
150
150
  *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.
@@ -209,10 +209,13 @@ Porffor can run Test262 via some hacks/transforms which remove unsupported featu
209
209
  - `test262`: test262 runner and utils
210
210
 
211
211
  ## Usecases
212
- Basically none (other than giving people headaches). Potential ideas to come?
212
+ Basically none right now (other than giving people headaches). Potential ideas:
213
+ - Safety. As Porffor is written in JS, a memory-safe language\*, and compiles JS to Wasm, a fully sandboxed environment\*, it is quite safe. (\* These rely on the underlying implementations being secure. You could also run Wasm, or even Porffor itself, with an interpreter instead of a JIT for bonus security points too.)
214
+ - Compiling JS to native binaries. This is still very early, [`2c`](#2c) is not that good yet :(
215
+ - More in future probably?
213
216
 
214
217
  ## Usage
215
- Basically nothing will work :). See files in `test` for examples.
218
+ Basically nothing will work :). See files in `test` and `bench` for examples.
216
219
 
217
220
  1. Clone repo
218
221
  2. `npm install`
@@ -1,4 +1,4 @@
1
- import { Blocktype, Opcodes, Valtype } from "./wasmSpec.js";
1
+ import { Blocktype, Opcodes, Valtype, ValtypeSize } from "./wasmSpec.js";
2
2
  import { number, i32x4 } from "./embedding.js";
3
3
 
4
4
  export const importedFuncs = [
@@ -29,6 +29,21 @@ for (let i = 0; i < importedFuncs.length; i++) {
29
29
 
30
30
  const char = c => number(c.charCodeAt(0));
31
31
 
32
+ const printStaticStr = str => {
33
+ const out = [];
34
+
35
+ for (let i = 0; i < str.length; i++) {
36
+ out.push(
37
+ // ...number(str.charCodeAt(i)),
38
+ ...number(str.charCodeAt(i), Valtype.i32),
39
+ Opcodes.i32_from_u,
40
+ [ Opcodes.call, importedFuncs.printChar ]
41
+ );
42
+ }
43
+
44
+ return out;
45
+ };
46
+
32
47
  // todo: somehow diff between these (undefined != null) while remaining falsey in wasm as a number value
33
48
  export const UNDEFINED = 0;
34
49
  export const NULL = 0;
@@ -187,12 +202,125 @@ export const BuiltinFuncs = function() {
187
202
 
188
203
 
189
204
  this.__console_log = {
190
- params: [ valtypeBinary ],
191
- locals: [],
205
+ params: [ valtypeBinary, Valtype.i32 ],
206
+ typedParams: true,
207
+ locals: [ Valtype.i32, Valtype.i32 ],
192
208
  returns: [],
193
- wasm: [
194
- [ Opcodes.local_get, 0 ],
195
- [ Opcodes.call, importedFuncs.print ],
209
+ wasm: (scope, { TYPES, typeSwitch }) => [
210
+ ...typeSwitch(scope, [ [ Opcodes.local_get, 1 ] ], {
211
+ [TYPES.number]: [
212
+ [ Opcodes.local_get, 0 ],
213
+ [ Opcodes.call, importedFuncs.print ],
214
+ ],
215
+ [TYPES.boolean]: [
216
+ [ Opcodes.local_get, 0 ],
217
+ Opcodes.i32_to_u,
218
+ [ Opcodes.if, Blocktype.void ],
219
+ ...printStaticStr('true'),
220
+ [ Opcodes.else ],
221
+ ...printStaticStr('false'),
222
+ [ Opcodes.end ]
223
+ ],
224
+ [TYPES.string]: [
225
+ // simply print a string :))
226
+ // cache input pointer as i32
227
+ [ Opcodes.local_get, 0 ],
228
+ Opcodes.i32_to_u,
229
+ [ Opcodes.local_tee, 2 ],
230
+
231
+ // make end pointer
232
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
233
+ ...number(ValtypeSize.i16, Valtype.i32),
234
+ [ Opcodes.i32_mul ],
235
+
236
+ [ Opcodes.local_get, 2 ],
237
+ [ Opcodes.i32_add ],
238
+ [ Opcodes.local_set, 3 ],
239
+
240
+ [ Opcodes.loop, Blocktype.void ],
241
+
242
+ // print current char
243
+ [ Opcodes.local_get, 2 ],
244
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
245
+ Opcodes.i32_from_u,
246
+ [ Opcodes.call, importedFuncs.printChar ],
247
+
248
+ // increment pointer by sizeof i16
249
+ [ Opcodes.local_get, 2 ],
250
+ ...number(ValtypeSize.i16, Valtype.i32),
251
+ [ Opcodes.i32_add ],
252
+ [ Opcodes.local_tee, 2 ],
253
+
254
+ // if pointer != end pointer, loop
255
+ [ Opcodes.local_get, 3 ],
256
+ [ Opcodes.i32_ne ],
257
+ [ Opcodes.br_if, 0 ],
258
+
259
+ [ Opcodes.end ]
260
+ ],
261
+ [TYPES._array]: [
262
+ ...printStaticStr('[ '),
263
+
264
+ // cache input pointer as i32
265
+ [ Opcodes.local_get, 0 ],
266
+ Opcodes.i32_to_u,
267
+ [ Opcodes.local_tee, 2 ],
268
+
269
+ // make end pointer
270
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
271
+ ...number(ValtypeSize[valtype], Valtype.i32),
272
+ [ Opcodes.i32_mul ],
273
+
274
+ [ Opcodes.local_get, 2 ],
275
+ [ Opcodes.i32_add ],
276
+ [ Opcodes.local_set, 3 ],
277
+
278
+ [ Opcodes.loop, Blocktype.void ],
279
+
280
+ // print current char
281
+ [ Opcodes.local_get, 2 ],
282
+ [ Opcodes.load, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
283
+ [ Opcodes.call, importedFuncs.print ],
284
+
285
+ // increment pointer by sizeof valtype
286
+ [ Opcodes.local_get, 2 ],
287
+ ...number(ValtypeSize[valtype], Valtype.i32),
288
+ [ Opcodes.i32_add ],
289
+ [ Opcodes.local_tee, 2 ],
290
+
291
+ // if pointer != end pointer, print separator and loop
292
+ [ Opcodes.local_get, 3 ],
293
+ [ Opcodes.i32_ne ],
294
+ [ Opcodes.if, Blocktype.void ],
295
+ ...printStaticStr(', '),
296
+ [ Opcodes.br, 1 ],
297
+ [ Opcodes.end ],
298
+
299
+ [ Opcodes.end ],
300
+
301
+ ...printStaticStr(' ]'),
302
+ ],
303
+ [TYPES.undefined]: [
304
+ ...printStaticStr('undefined')
305
+ ],
306
+ [TYPES.function]: [
307
+ ...printStaticStr('function () {}')
308
+ ],
309
+ [TYPES.object]: [
310
+ [ Opcodes.local_get, 0 ],
311
+ Opcodes.i32_to_u,
312
+ [ Opcodes.if, Blocktype.void ],
313
+ ...printStaticStr('{}'),
314
+ [ Opcodes.else ],
315
+ ...printStaticStr('null'),
316
+ [ Opcodes.end ]
317
+ ],
318
+ default: [
319
+ [ Opcodes.local_get, 0 ],
320
+ [ Opcodes.call, importedFuncs.print ],
321
+ ]
322
+ }, Blocktype.void),
323
+
196
324
  ...char('\n'),
197
325
  [ Opcodes.call, importedFuncs.printChar ]
198
326
  ]
@@ -55,7 +55,7 @@ const todo = msg => {
55
55
  };
56
56
 
57
57
  const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
58
- const generate = (scope, decl, global = false, name = undefined) => {
58
+ const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
59
59
  switch (decl.type) {
60
60
  case 'BinaryExpression':
61
61
  return generateBinaryExp(scope, decl, global, name);
@@ -68,7 +68,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
68
68
 
69
69
  case 'ArrowFunctionExpression':
70
70
  case 'FunctionDeclaration':
71
- generateFunc(scope, decl);
71
+ const func = generateFunc(scope, decl);
72
+
73
+ if (decl.type.endsWith('Expression')) {
74
+ return number(func.index);
75
+ }
76
+
72
77
  return [];
73
78
 
74
79
  case 'BlockStatement':
@@ -81,7 +86,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
81
86
  return generateExp(scope, decl);
82
87
 
83
88
  case 'CallExpression':
84
- return generateCall(scope, decl, global, name);
89
+ return generateCall(scope, decl, global, name, valueUnused);
85
90
 
86
91
  case 'NewExpression':
87
92
  return generateNew(scope, decl, global, name);
@@ -680,6 +685,15 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
680
685
  [ Opcodes.i32_eqz ], */
681
686
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
682
687
  ],
688
+ [TYPES._bytestring]: [ // duplicate of string
689
+ [ Opcodes.local_get, tmp ],
690
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
691
+
692
+ // get length
693
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
694
+
695
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
696
+ ],
683
697
  default: def
684
698
  }, intOut ? Valtype.i32 : valtypeBinary)
685
699
  ];
@@ -707,6 +721,17 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
707
721
  [ Opcodes.i32_eqz ],
708
722
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
709
723
  ],
724
+ [TYPES._bytestring]: [ // duplicate of string
725
+ [ Opcodes.local_get, tmp ],
726
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
727
+
728
+ // get length
729
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
730
+
731
+ // if length == 0
732
+ [ Opcodes.i32_eqz ],
733
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
734
+ ],
710
735
  default: [
711
736
  // if value == 0
712
737
  [ Opcodes.local_get, tmp ],
@@ -947,6 +972,18 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
947
972
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
948
973
  }
949
974
 
975
+ if (typeof wasm === 'function') {
976
+ const scope = {
977
+ name,
978
+ params,
979
+ locals,
980
+ returns,
981
+ localInd: allLocals.length,
982
+ };
983
+
984
+ wasm = wasm(scope, { TYPES, typeSwitch, makeArray });
985
+ }
986
+
950
987
  let baseGlobalIdx, i = 0;
951
988
  for (const type of globalTypes) {
952
989
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -1027,7 +1064,8 @@ const TYPES = {
1027
1064
 
1028
1065
  // these are not "typeof" types but tracked internally
1029
1066
  _array: 0x10,
1030
- _regexp: 0x11
1067
+ _regexp: 0x11,
1068
+ _bytestring: 0x12
1031
1069
  };
1032
1070
 
1033
1071
  const TYPE_NAMES = {
@@ -1041,7 +1079,8 @@ const TYPE_NAMES = {
1041
1079
  [TYPES.bigint]: 'BigInt',
1042
1080
 
1043
1081
  [TYPES._array]: 'Array',
1044
- [TYPES._regexp]: 'RegExp'
1082
+ [TYPES._regexp]: 'RegExp',
1083
+ [TYPES._bytestring]: 'ByteString'
1045
1084
  };
1046
1085
 
1047
1086
  const getType = (scope, _name) => {
@@ -1094,6 +1133,8 @@ const getNodeType = (scope, node) => {
1094
1133
  if (node.type === 'Literal') {
1095
1134
  if (node.regex) return TYPES._regexp;
1096
1135
 
1136
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1137
+
1097
1138
  return TYPES[typeof node.value];
1098
1139
  }
1099
1140
 
@@ -1107,6 +1148,15 @@ const getNodeType = (scope, node) => {
1107
1148
 
1108
1149
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1109
1150
  const name = node.callee.name;
1151
+ if (!name) {
1152
+ // iife
1153
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1154
+
1155
+ // presume
1156
+ // todo: warn here?
1157
+ return TYPES.number;
1158
+ }
1159
+
1110
1160
  const func = funcs.find(x => x.name === name);
1111
1161
 
1112
1162
  if (func) {
@@ -1201,7 +1251,7 @@ const getNodeType = (scope, node) => {
1201
1251
  if (node.operator === '!') return TYPES.boolean;
1202
1252
  if (node.operator === 'void') return TYPES.undefined;
1203
1253
  if (node.operator === 'delete') return TYPES.boolean;
1204
- if (node.operator === 'typeof') return TYPES.string;
1254
+ if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
1205
1255
 
1206
1256
  return TYPES.number;
1207
1257
  }
@@ -1210,7 +1260,9 @@ const getNodeType = (scope, node) => {
1210
1260
  // hack: if something.length, number type
1211
1261
  if (node.property.name === 'length') return TYPES.number;
1212
1262
 
1213
- // we cannot guess
1263
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1264
+
1265
+ // presume
1214
1266
  return TYPES.number;
1215
1267
  }
1216
1268
 
@@ -1261,16 +1313,7 @@ const generateLiteral = (scope, decl, global, name) => {
1261
1313
  return number(decl.value ? 1 : 0);
1262
1314
 
1263
1315
  case 'string':
1264
- const str = decl.value;
1265
- const rawElements = new Array(str.length);
1266
- let j = 0;
1267
- for (let i = 0; i < str.length; i++) {
1268
- rawElements[i] = str.charCodeAt(i);
1269
- }
1270
-
1271
- return makeArray(scope, {
1272
- rawElements
1273
- }, global, name, false, 'i16')[0];
1316
+ return makeString(scope, decl.value, global, name);
1274
1317
 
1275
1318
  default:
1276
1319
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -1291,9 +1334,9 @@ const countLeftover = wasm => {
1291
1334
 
1292
1335
  if (depth === 0)
1293
1336
  if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1294
- else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
1337
+ else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
1295
1338
  else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1296
- else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
1339
+ else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1297
1340
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1298
1341
  else if (inst[0] === Opcodes.return) count = 0;
1299
1342
  else if (inst[0] === Opcodes.call) {
@@ -1319,7 +1362,7 @@ const disposeLeftover = wasm => {
1319
1362
  const generateExp = (scope, decl) => {
1320
1363
  const expression = decl.expression;
1321
1364
 
1322
- const out = generate(scope, expression);
1365
+ const out = generate(scope, expression, undefined, undefined, true);
1323
1366
  disposeLeftover(out);
1324
1367
 
1325
1368
  return out;
@@ -1377,7 +1420,7 @@ const RTArrayUtil = {
1377
1420
  ]
1378
1421
  };
1379
1422
 
1380
- const generateCall = (scope, decl, _global, _name) => {
1423
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1381
1424
  /* const callee = decl.callee;
1382
1425
  const args = decl.arguments;
1383
1426
 
@@ -1496,10 +1539,18 @@ const generateCall = (scope, decl, _global, _name) => {
1496
1539
  // use local for cached i32 length as commonly used
1497
1540
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1498
1541
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1499
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1500
1542
 
1501
1543
  // TODO: long-term, prototypes should be their individual separate funcs
1502
1544
 
1545
+ const rawPointer = [
1546
+ ...generate(scope, target),
1547
+ Opcodes.i32_to_u
1548
+ ];
1549
+
1550
+ const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
1551
+ const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
1552
+
1553
+ let allOptUnused = true;
1503
1554
  let lengthI32CacheUsed = false;
1504
1555
  const protoBC = {};
1505
1556
  for (const x in protoCands) {
@@ -1519,6 +1570,7 @@ const generateCall = (scope, decl, _global, _name) => {
1519
1570
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1520
1571
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1521
1572
 
1573
+ let optUnused = false;
1522
1574
  const protoOut = protoFunc(getPointer, {
1523
1575
  getCachedI32: () => {
1524
1576
  lengthI32CacheUsed = true;
@@ -1533,10 +1585,15 @@ const generateCall = (scope, decl, _global, _name) => {
1533
1585
  return makeArray(scope, {
1534
1586
  rawElements: new Array(length)
1535
1587
  }, _global, _name, true, itemType);
1588
+ }, () => {
1589
+ optUnused = true;
1590
+ return unusedValue;
1536
1591
  });
1537
1592
 
1593
+ if (!optUnused) allOptUnused = false;
1594
+
1538
1595
  protoBC[x] = [
1539
- [ Opcodes.block, valtypeBinary ],
1596
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1540
1597
  ...protoOut,
1541
1598
 
1542
1599
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
@@ -1545,11 +1602,13 @@ const generateCall = (scope, decl, _global, _name) => {
1545
1602
  ];
1546
1603
  }
1547
1604
 
1548
- return [
1549
- ...generate(scope, target),
1605
+ // todo: if some cands use optUnused and some don't, we will probably crash
1550
1606
 
1551
- Opcodes.i32_to_u,
1552
- [ Opcodes.local_set, pointerLocal ],
1607
+ return [
1608
+ ...(usePointerCache ? [
1609
+ ...rawPointer,
1610
+ [ Opcodes.local_set, pointerLocal ],
1611
+ ] : []),
1553
1612
 
1554
1613
  ...(!lengthI32CacheUsed ? [] : [
1555
1614
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1561,7 +1620,7 @@ const generateCall = (scope, decl, _global, _name) => {
1561
1620
 
1562
1621
  // TODO: error better
1563
1622
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1564
- }, valtypeBinary),
1623
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1565
1624
  ];
1566
1625
  }
1567
1626
  }
@@ -1608,7 +1667,9 @@ const generateCall = (scope, decl, _global, _name) => {
1608
1667
  const func = funcs.find(x => x.index === idx);
1609
1668
 
1610
1669
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1611
- const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
1670
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1671
+ const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
1672
+ const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1612
1673
 
1613
1674
  let args = decl.arguments;
1614
1675
  if (func && args.length < paramCount) {
@@ -1626,12 +1687,12 @@ const generateCall = (scope, decl, _global, _name) => {
1626
1687
  let out = [];
1627
1688
  for (const arg of args) {
1628
1689
  out = out.concat(generate(scope, arg));
1629
- if (userFunc) out = out.concat(getNodeType(scope, arg));
1690
+ if (typedParams) out = out.concat(getNodeType(scope, arg));
1630
1691
  }
1631
1692
 
1632
1693
  out.push([ Opcodes.call, idx ]);
1633
1694
 
1634
- if (!userFunc) {
1695
+ if (!typedReturn) {
1635
1696
  // let type;
1636
1697
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1637
1698
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1767,6 +1828,8 @@ const brTable = (input, bc, returns) => {
1767
1828
  };
1768
1829
 
1769
1830
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1831
+ if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
1832
+
1770
1833
  const known = knownType(scope, type);
1771
1834
  if (known != null) {
1772
1835
  return bc[known] ?? bc.default;
@@ -2157,6 +2220,8 @@ const generateUnary = (scope, decl) => {
2157
2220
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2158
2221
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2159
2222
 
2223
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2224
+
2160
2225
  // object and internal types
2161
2226
  default: makeString(scope, 'object', false, '#typeof_result'),
2162
2227
  });
@@ -2338,13 +2403,14 @@ const generateForOf = (scope, decl) => {
2338
2403
  // // todo: we should only do this for strings but we don't know at compile-time :(
2339
2404
  // hack: this is naughty and will break things!
2340
2405
  let newOut = number(0, Valtype.f64), newPointer = -1;
2341
- if (pages.hasString) {
2406
+ if (pages.hasAnyString) {
2342
2407
  0, [ newOut, newPointer ] = makeArray(scope, {
2343
2408
  rawElements: new Array(1)
2344
2409
  }, isGlobal, leftName, true, 'i16');
2345
2410
  }
2346
2411
 
2347
2412
  // set type for local
2413
+ // todo: optimize away counter and use end pointer
2348
2414
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2349
2415
  [TYPES._array]: [
2350
2416
  ...setType(scope, leftName, TYPES.number),
@@ -2530,6 +2596,8 @@ const allocPage = (reason, type) => {
2530
2596
 
2531
2597
  if (reason.startsWith('array:')) pages.hasArray = true;
2532
2598
  if (reason.startsWith('string:')) pages.hasString = true;
2599
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
2600
+ if (reason.includes('string:')) pages.hasAnyString = true;
2533
2601
 
2534
2602
  const ind = pages.size;
2535
2603
  pages.set(reason, { ind, type });
@@ -2563,7 +2631,8 @@ const StoreOps = {
2563
2631
  f64: Opcodes.f64_store,
2564
2632
 
2565
2633
  // expects i32 input!
2566
- i16: Opcodes.i32_store16
2634
+ i8: Opcodes.i32_store8,
2635
+ i16: Opcodes.i32_store16,
2567
2636
  };
2568
2637
 
2569
2638
  let data = [];
@@ -2582,6 +2651,15 @@ const compileBytes = (val, itemType, signed = true) => {
2582
2651
  }
2583
2652
  };
2584
2653
 
2654
+ const getAllocType = itemType => {
2655
+ switch (itemType) {
2656
+ case 'i8': return 'bytestring';
2657
+ case 'i16': return 'string';
2658
+
2659
+ default: return 'array';
2660
+ }
2661
+ };
2662
+
2585
2663
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2586
2664
  const out = [];
2587
2665
 
@@ -2591,7 +2669,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2591
2669
 
2592
2670
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2593
2671
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2594
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
2672
+ arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2595
2673
  }
2596
2674
 
2597
2675
  const pointer = arrays.get(name);
@@ -2637,7 +2715,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2637
2715
  out.push(
2638
2716
  ...number(0, Valtype.i32),
2639
2717
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2640
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2718
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2641
2719
  );
2642
2720
  }
2643
2721
 
@@ -2647,15 +2725,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2647
2725
  return [ out, pointer ];
2648
2726
  };
2649
2727
 
2728
+ const byteStringable = str => {
2729
+ if (!process.argv.includes('-bytestring')) return false;
2730
+
2731
+ for (let i = 0; i < str.length; i++) {
2732
+ if (str.charCodeAt(i) > 0xFF) return false;
2733
+ }
2734
+
2735
+ return true;
2736
+ };
2737
+
2650
2738
  const makeString = (scope, str, global = false, name = '$undeclared') => {
2651
2739
  const rawElements = new Array(str.length);
2740
+ let byteStringable = process.argv.includes('-bytestring');
2652
2741
  for (let i = 0; i < str.length; i++) {
2653
- rawElements[i] = str.charCodeAt(i);
2742
+ const c = str.charCodeAt(i);
2743
+ rawElements[i] = c;
2744
+
2745
+ if (byteStringable && c > 0xFF) byteStringable = false;
2654
2746
  }
2655
2747
 
2656
2748
  return makeArray(scope, {
2657
2749
  rawElements
2658
- }, global, name, false, 'i16')[0];
2750
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2659
2751
  };
2660
2752
 
2661
2753
  let arrays = new Map();
@@ -2683,10 +2775,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2683
2775
  ];
2684
2776
  }
2685
2777
 
2778
+ const object = generate(scope, decl.object);
2779
+ const property = generate(scope, decl.property);
2780
+
2686
2781
  // // todo: we should only do this for strings but we don't know at compile-time :(
2687
2782
  // hack: this is naughty and will break things!
2688
2783
  let newOut = number(0, valtypeBinary), newPointer = -1;
2689
- if (pages.hasString) {
2784
+ if (pages.hasAnyString) {
2690
2785
  0, [ newOut, newPointer ] = makeArray(scope, {
2691
2786
  rawElements: new Array(1)
2692
2787
  }, _global, _name, true, 'i16');
@@ -2695,7 +2790,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2695
2790
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2696
2791
  [TYPES._array]: [
2697
2792
  // get index as valtype
2698
- ...generate(scope, decl.property),
2793
+ ...property,
2699
2794
 
2700
2795
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2701
2796
  Opcodes.i32_to_u,
@@ -2703,7 +2798,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2703
2798
  [ Opcodes.i32_mul ],
2704
2799
 
2705
2800
  ...(aotPointer ? [] : [
2706
- ...generate(scope, decl.object),
2801
+ ...object,
2707
2802
  Opcodes.i32_to_u,
2708
2803
  [ Opcodes.i32_add ]
2709
2804
  ]),
@@ -2722,14 +2817,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2722
2817
 
2723
2818
  ...number(0, Valtype.i32), // base 0 for store later
2724
2819
 
2725
- ...generate(scope, decl.property),
2726
-
2820
+ ...property,
2727
2821
  Opcodes.i32_to_u,
2822
+
2728
2823
  ...number(ValtypeSize.i16, Valtype.i32),
2729
2824
  [ Opcodes.i32_mul ],
2730
2825
 
2731
2826
  ...(aotPointer ? [] : [
2732
- ...generate(scope, decl.object),
2827
+ ...object,
2733
2828
  Opcodes.i32_to_u,
2734
2829
  [ Opcodes.i32_add ]
2735
2830
  ]),
@@ -2746,6 +2841,34 @@ export const generateMember = (scope, decl, _global, _name) => {
2746
2841
  ...number(TYPES.string, Valtype.i32),
2747
2842
  setLastType(scope)
2748
2843
  ],
2844
+ [TYPES._bytestring]: [
2845
+ // setup new/out array
2846
+ ...newOut,
2847
+ [ Opcodes.drop ],
2848
+
2849
+ ...number(0, Valtype.i32), // base 0 for store later
2850
+
2851
+ ...property,
2852
+ Opcodes.i32_to_u,
2853
+
2854
+ ...(aotPointer ? [] : [
2855
+ ...object,
2856
+ Opcodes.i32_to_u,
2857
+ [ Opcodes.i32_add ]
2858
+ ]),
2859
+
2860
+ // load current string ind {arg}
2861
+ [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2862
+
2863
+ // store to new string ind 0
2864
+ [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2865
+
2866
+ // return new string (page)
2867
+ ...number(newPointer),
2868
+
2869
+ ...number(TYPES._bytestring, Valtype.i32),
2870
+ setLastType(scope)
2871
+ ],
2749
2872
 
2750
2873
  default: [ [ Opcodes.unreachable ] ]
2751
2874
  });
@@ -2827,10 +2950,8 @@ const generateFunc = (scope, decl) => {
2827
2950
  const func = {
2828
2951
  name,
2829
2952
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2830
- returns: innerScope.returns,
2831
- locals: innerScope.locals,
2832
- throws: innerScope.throws,
2833
- index: currentFuncIndex++
2953
+ index: currentFuncIndex++,
2954
+ ...innerScope
2834
2955
  };
2835
2956
  funcIndex[name] = func.index;
2836
2957
 
@@ -15,7 +15,7 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
15
15
  if (name) out += `${makeSignature(params, returns)} ;; $${name} (${ind})\n`;
16
16
 
17
17
  const justLocals = Object.values(locals).sort((a, b) => a.idx - b.idx).slice(params.length);
18
- if (justLocals.length > 0) out += ` local ${justLocals.map(x => invValtype[x.type]).join(' ')}\n`;
18
+ if (name && justLocals.length > 0) out += ` local ${justLocals.map(x => invValtype[x.type]).join(' ')}\n`;
19
19
 
20
20
  let i = -1, lastInst;
21
21
  let byte = 0;
@@ -32,7 +32,7 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
32
32
  inst = [ [ inst[0], inst[1] ], ...inst.slice(2) ];
33
33
  }
34
34
 
35
- if (inst[0] === Opcodes.end || inst[0] === Opcodes.else || inst[0] === Opcodes.catch_all) depth--;
35
+ if (depth > 0 && (inst[0] === Opcodes.end || inst[0] === Opcodes.else || inst[0] === Opcodes.catch_all)) depth--;
36
36
 
37
37
  out += ' '.repeat(Math.max(0, depth * 2));
38
38
 
@@ -119,7 +119,7 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
119
119
  export const highlightAsm = asm => asm
120
120
  .replace(/(local|global|memory)\.[^\s]*/g, _ => `\x1B[31m${_}\x1B[0m`)
121
121
  .replace(/(i(8|16|32|64)x[0-9]+|v128)(\.[^\s]*)?/g, _ => `\x1B[34m${_}\x1B[0m`)
122
- .replace(/[^m](i32|i64|f32|f64|drop)(\.[^\s]*)?/g, _ => `${_[0]}\x1B[36m${_.slice(1)}\x1B[0m`)
122
+ .replace(/(i32|i64|f32|f64|drop)(\.[^\s]*)?/g, _ => `\x1B[36m${_}\x1B[0m`)
123
123
  .replace(/(return_call|call|br_if|br|return|rethrow|throw)/g, _ => `\x1B[35m${_}\x1B[0m`)
124
124
  .replace(/(block|loop|if|end|else|try|catch_all|catch|delegate)/g, _ => `\x1B[95m${_}\x1B[0m`)
125
125
  .replace(/unreachable/g, _ => `\x1B[91m${_}\x1B[0m`)
package/compiler/opt.js CHANGED
@@ -192,6 +192,7 @@ export default (funcs, globals, pages, tags) => {
192
192
  let missing = false;
193
193
  if (type === 'Array') missing = !pages.hasArray;
194
194
  if (type === 'String') missing = !pages.hasString;
195
+ if (type === 'ByteString') missing = !pages.hasByteString;
195
196
 
196
197
  if (missing) {
197
198
  let j = i, depth = 0;
@@ -16,7 +16,8 @@ const TYPES = {
16
16
 
17
17
  // these are not "typeof" types but tracked internally
18
18
  _array: 0x10,
19
- _regexp: 0x11
19
+ _regexp: 0x11,
20
+ _bytestring: 0x12
20
21
  };
21
22
 
22
23
  // todo: turn these into built-ins once arrays and these become less hacky
@@ -71,7 +72,7 @@ export const PrototypeFuncs = function() {
71
72
  ],
72
73
 
73
74
  // todo: only for 1 argument
74
- push: (pointer, length, wNewMember) => [
75
+ push: (pointer, length, wNewMember, _1, _2, _3, unusedValue) => [
75
76
  // get memory offset of array at last index (length)
76
77
  ...length.getCachedI32(),
77
78
  ...number(ValtypeSize[valtype], Valtype.i32),
@@ -92,22 +93,28 @@ export const PrototypeFuncs = function() {
92
93
  ...number(1, Valtype.i32),
93
94
  [ Opcodes.i32_add ],
94
95
 
95
- ...length.setCachedI32(),
96
- ...length.getCachedI32(),
96
+ ...(unusedValue() ? [] : [
97
+ ...length.setCachedI32(),
98
+ ...length.getCachedI32(),
99
+ ])
97
100
  ]),
98
101
 
99
- ...length.getCachedI32(),
100
- Opcodes.i32_from_u
102
+ ...(unusedValue() ? [] : [
103
+ ...length.getCachedI32(),
104
+ Opcodes.i32_from_u
105
+ ])
101
106
 
102
107
  // ...length.get()
103
108
  ],
104
109
 
105
- pop: (pointer, length) => [
110
+ pop: (pointer, length, _1, _2, _3, _4, unusedValue) => [
106
111
  // if length == 0, noop
107
112
  ...length.getCachedI32(),
108
113
  [ Opcodes.i32_eqz ],
109
114
  [ Opcodes.if, Blocktype.void ],
110
- ...number(UNDEFINED),
115
+ ...(unusedValue() ? [] : [
116
+ ...number(UNDEFINED),
117
+ ]),
111
118
  [ Opcodes.br, 1 ],
112
119
  [ Opcodes.end ],
113
120
 
@@ -119,19 +126,23 @@ export const PrototypeFuncs = function() {
119
126
  ...number(1, Valtype.i32),
120
127
  [ Opcodes.i32_sub ],
121
128
 
122
- ...length.setCachedI32(),
123
- ...length.getCachedI32(),
129
+ ...(unusedValue() ? [] : [
130
+ ...length.setCachedI32(),
131
+ ...length.getCachedI32(),
132
+ ])
124
133
  ]),
125
134
 
126
135
  // load last element
127
- ...length.getCachedI32(),
128
- ...number(ValtypeSize[valtype], Valtype.i32),
129
- [ Opcodes.i32_mul ],
136
+ ...(unusedValue() ? [] : [
137
+ ...length.getCachedI32(),
138
+ ...number(ValtypeSize[valtype], Valtype.i32),
139
+ [ Opcodes.i32_mul ],
130
140
 
131
- ...pointer,
132
- [ Opcodes.i32_add ],
141
+ ...pointer,
142
+ [ Opcodes.i32_add ],
133
143
 
134
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ]
144
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ]
145
+ ])
135
146
  ],
136
147
 
137
148
  shift: (pointer, length) => [
@@ -472,8 +483,152 @@ export const PrototypeFuncs = function() {
472
483
  this[TYPES.string].at.returnType = TYPES.string;
473
484
  this[TYPES.string].charAt.returnType = TYPES.string;
474
485
  this[TYPES.string].charCodeAt.local = Valtype.i32;
486
+ this[TYPES.string].charCodeAt.noPointerCache = zeroChecks.charcodeat;
475
487
 
476
488
  this[TYPES.string].isWellFormed.local = Valtype.i32;
477
489
  this[TYPES.string].isWellFormed.local2 = Valtype.i32;
478
490
  this[TYPES.string].isWellFormed.returnType = TYPES.boolean;
491
+
492
+ if (process.argv.includes('-bytestring')) {
493
+ this[TYPES._bytestring] = {
494
+ at: (pointer, length, wIndex, iTmp, _, arrayShell) => {
495
+ const [ newOut, newPointer ] = arrayShell(1, 'i16');
496
+
497
+ return [
498
+ // setup new/out array
499
+ ...newOut,
500
+ [ Opcodes.drop ],
501
+
502
+ ...number(0, Valtype.i32), // base 0 for store later
503
+
504
+ ...wIndex,
505
+ Opcodes.i32_to_u,
506
+ [ Opcodes.local_tee, iTmp ],
507
+
508
+ // if index < 0: access index + array length
509
+ ...number(0, Valtype.i32),
510
+ [ Opcodes.i32_lt_s ],
511
+ [ Opcodes.if, Blocktype.void ],
512
+ [ Opcodes.local_get, iTmp ],
513
+ ...length.getCachedI32(),
514
+ [ Opcodes.i32_add ],
515
+ [ Opcodes.local_set, iTmp ],
516
+ [ Opcodes.end ],
517
+
518
+ // if still < 0 or >= length: return undefined
519
+ [ Opcodes.local_get, iTmp ],
520
+ ...number(0, Valtype.i32),
521
+ [ Opcodes.i32_lt_s ],
522
+
523
+ [ Opcodes.local_get, iTmp ],
524
+ ...length.getCachedI32(),
525
+ [ Opcodes.i32_ge_s ],
526
+ [ Opcodes.i32_or ],
527
+
528
+ [ Opcodes.if, Blocktype.void ],
529
+ ...number(UNDEFINED),
530
+ [ Opcodes.br, 1 ],
531
+ [ Opcodes.end ],
532
+
533
+ [ Opcodes.local_get, iTmp ],
534
+
535
+ ...pointer,
536
+ [ Opcodes.i32_add ],
537
+
538
+ // load current string ind {arg}
539
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
540
+
541
+ // store to new string ind 0
542
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
543
+
544
+ // return new string (pointer)
545
+ ...number(newPointer)
546
+ ];
547
+ },
548
+
549
+ // todo: out of bounds properly
550
+ charAt: (pointer, length, wIndex, _1, _2, arrayShell) => {
551
+ const [ newOut, newPointer ] = arrayShell(1, 'i16');
552
+
553
+ return [
554
+ // setup new/out array
555
+ ...newOut,
556
+ [ Opcodes.drop ],
557
+
558
+ ...number(0, Valtype.i32), // base 0 for store later
559
+
560
+ ...wIndex,
561
+
562
+ Opcodes.i32_to,
563
+
564
+ ...pointer,
565
+ [ Opcodes.i32_add ],
566
+
567
+ // load current string ind {arg}
568
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
569
+
570
+ // store to new string ind 0
571
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
572
+
573
+ // return new string (page)
574
+ ...number(newPointer)
575
+ ];
576
+ },
577
+
578
+ charCodeAt: (pointer, length, wIndex, iTmp) => {
579
+ return [
580
+ ...wIndex,
581
+ Opcodes.i32_to,
582
+
583
+ ...(zeroChecks.charcodeat ? [] : [
584
+ [ Opcodes.local_set, iTmp ],
585
+
586
+ // index < 0
587
+ ...(noUnlikelyChecks ? [] : [
588
+ [ Opcodes.local_get, iTmp ],
589
+ ...number(0, Valtype.i32),
590
+ [ Opcodes.i32_lt_s ],
591
+ ]),
592
+
593
+ // index >= length
594
+ [ Opcodes.local_get, iTmp ],
595
+ ...length.getCachedI32(),
596
+ [ Opcodes.i32_ge_s ],
597
+
598
+ ...(noUnlikelyChecks ? [] : [ [ Opcodes.i32_or ] ]),
599
+ [ Opcodes.if, Blocktype.void ],
600
+ ...number(NaN),
601
+ [ Opcodes.br, 1 ],
602
+ [ Opcodes.end ],
603
+
604
+ [ Opcodes.local_get, iTmp ],
605
+ ]),
606
+
607
+ ...pointer,
608
+ [ Opcodes.i32_add ],
609
+
610
+ // load current string ind {arg}
611
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
612
+ Opcodes.i32_from_u
613
+ ];
614
+ },
615
+
616
+ isWellFormed: () => {
617
+ return [
618
+ // we know it must be true as it is a bytestring lol
619
+ ...number(1)
620
+ ]
621
+ }
622
+ };
623
+
624
+ this[TYPES._bytestring].at.local = Valtype.i32;
625
+ this[TYPES._bytestring].at.returnType = TYPES._bytestring;
626
+ this[TYPES._bytestring].charAt.returnType = TYPES._bytestring;
627
+ this[TYPES._bytestring].charCodeAt.local = Valtype.i32;
628
+ this[TYPES._bytestring].charCodeAt.noPointerCache = zeroChecks.charcodeat;
629
+
630
+ this[TYPES._bytestring].isWellFormed.local = Valtype.i32;
631
+ this[TYPES._bytestring].isWellFormed.local2 = Valtype.i32;
632
+ this[TYPES._bytestring].isWellFormed.returnType = TYPES.boolean;
633
+ }
479
634
  };
@@ -57,9 +57,12 @@ export const Opcodes = {
57
57
  i64_load: 0x29,
58
58
  f64_load: 0x2b,
59
59
 
60
+ i32_load8_s: 0x2c,
61
+ i32_load8_u: 0x2d,
60
62
  i32_load16_s: 0x2e,
61
63
  i32_load16_u: 0x2f,
62
64
 
65
+ i32_store8: 0x3a,
63
66
  i32_store16: 0x3b,
64
67
 
65
68
  i32_store: 0x36,
package/compiler/wrap.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import compile from './index.js';
2
2
  import decompile from './decompile.js';
3
- import fs from 'node:fs';
3
+ import { encodeVector, encodeLocal } from './encoding.js';
4
+ // import fs from 'node:fs';
4
5
 
5
6
  const bold = x => `\u001b[1m${x}\u001b[0m`;
6
7
 
@@ -18,7 +19,8 @@ const TYPES = {
18
19
 
19
20
  // internal
20
21
  [internalTypeBase]: '_array',
21
- [internalTypeBase + 1]: '_regexp'
22
+ [internalTypeBase + 1]: '_regexp',
23
+ [internalTypeBase + 2]: '_bytestring'
22
24
  };
23
25
 
24
26
  export default async (source, flags = [ 'module' ], customImports = {}, print = str => process.stdout.write(str)) => {
@@ -29,20 +31,104 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
29
31
 
30
32
  if (source.includes('export function')) flags.push('module');
31
33
 
32
- fs.writeFileSync('out.wasm', Buffer.from(wasm));
34
+ // fs.writeFileSync('out.wasm', Buffer.from(wasm));
33
35
 
34
36
  times.push(performance.now() - t1);
35
37
  if (flags.includes('info')) console.log(bold(`compiled in ${times[0].toFixed(2)}ms`));
36
38
 
37
39
  const t2 = performance.now();
38
- const { instance } = await WebAssembly.instantiate(wasm, {
39
- '': {
40
- p: valtype === 'i64' ? i => print(Number(i).toString()) : i => print(i.toString()),
41
- c: valtype === 'i64' ? i => print(String.fromCharCode(Number(i))) : i => print(String.fromCharCode(i)),
42
- t: _ => performance.now(),
43
- ...customImports
40
+
41
+ let instance;
42
+ try {
43
+ 0, { instance } = await WebAssembly.instantiate(wasm, {
44
+ '': {
45
+ p: valtype === 'i64' ? i => print(Number(i).toString()) : i => print(i.toString()),
46
+ c: valtype === 'i64' ? i => print(String.fromCharCode(Number(i))) : i => print(String.fromCharCode(i)),
47
+ t: _ => performance.now(),
48
+ ...customImports
49
+ }
50
+ });
51
+ } catch (e) {
52
+ // only backtrace for runner, not test262/etc
53
+ if (!process.argv[1].includes('/runner')) throw e;
54
+
55
+ const funcInd = parseInt(e.message.match(/function #([0-9]+) /)[1]);
56
+ const blobOffset = parseInt(e.message.split('@')[1]);
57
+
58
+ // convert blob offset -> function wasm offset.
59
+ // this is not good code and is somewhat duplicated
60
+ // I just want it to work for debugging, I don't care about perf/yes
61
+
62
+ const func = funcs.find(x => x.index === funcInd);
63
+ const locals = Object.values(func.locals).sort((a, b) => a.idx - b.idx).slice(func.params.length).sort((a, b) => a.idx - b.idx);
64
+
65
+ let localDecl = [], typeCount = 0, lastType;
66
+ for (let i = 0; i < locals.length; i++) {
67
+ const local = locals[i];
68
+ if (i !== 0 && local.type !== lastType) {
69
+ localDecl.push(encodeLocal(typeCount, lastType));
70
+ typeCount = 0;
71
+ }
72
+
73
+ typeCount++;
74
+ lastType = local.type;
75
+ }
76
+
77
+ if (typeCount !== 0) localDecl.push(encodeLocal(typeCount, lastType));
78
+
79
+ const toFind = encodeVector(localDecl).concat(func.wasm.flat().filter(x => x != null && x <= 0xff).slice(0, 40));
80
+
81
+ let i = 0;
82
+ for (; i < wasm.length; i++) {
83
+ let mismatch = false;
84
+ for (let j = 0; j < toFind.length; j++) {
85
+ if (wasm[i + j] !== toFind[j]) {
86
+ mismatch = true;
87
+ break;
88
+ }
89
+ }
90
+
91
+ if (!mismatch) break;
92
+ }
93
+
94
+ if (i === wasm.length) throw e;
95
+
96
+ const offset = (blobOffset - i) + encodeVector(localDecl).length;
97
+
98
+ let cumLen = 0;
99
+ i = 0;
100
+ for (; i < func.wasm.length; i++) {
101
+ cumLen += func.wasm[i].filter(x => x != null && x <= 0xff).length;
102
+ if (cumLen === offset) break;
103
+ }
104
+
105
+ if (cumLen !== offset) throw e;
106
+
107
+ i -= 1;
108
+
109
+ console.log(`\x1B[35m\x1B[1mporffor backtrace\u001b[0m`);
110
+
111
+ console.log('\x1B[4m' + func.name + '\x1B[0m');
112
+
113
+ const surrounding = 6;
114
+
115
+ const decomp = decompile(func.wasm.slice(i - surrounding, i + surrounding + 1), '', 0, func.locals, func.params, func.returns, funcs, globals, exceptions).slice(0, -1).split('\n');
116
+
117
+ const noAnsi = s => s.replace(/\u001b\[[0-9]+m/g, '');
118
+ let longest = 0;
119
+ for (let j = 0; j < decomp.length; j++) {
120
+ longest = Math.max(longest, noAnsi(decomp[j]).length);
44
121
  }
45
- });
122
+
123
+ const middle = Math.floor(decomp.length / 2);
124
+ decomp[middle] = `\x1B[47m\x1B[30m${noAnsi(decomp[middle])}${'\u00a0'.repeat(longest - noAnsi(decomp[middle]).length)}\x1B[0m`;
125
+
126
+ console.log('\x1B[90m...\x1B[0m');
127
+ console.log(decomp.join('\n'));
128
+ console.log('\x1B[90m...\x1B[0m\n');
129
+
130
+ throw e;
131
+ }
46
132
 
47
133
  times.push(performance.now() - t2);
48
134
  if (flags.includes('info')) console.log(`instantiated in ${times[1].toFixed(2)}ms`);
@@ -95,6 +181,13 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
95
181
  return Array.from(new Uint16Array(memory.buffer, pointer + 4, length)).map(x => String.fromCharCode(x)).join('');
96
182
  }
97
183
 
184
+ case '_bytestring': {
185
+ const pointer = ret;
186
+ const length = new Int32Array(memory.buffer, pointer, 1);
187
+
188
+ return Array.from(new Uint8Array(memory.buffer, pointer + 4, length)).map(x => String.fromCharCode(x)).join('');
189
+ }
190
+
98
191
  case 'function': {
99
192
  // wasm func index, including all imports
100
193
  const func = funcs.find(x => (x.originalIndex ?? x.index) === ret);
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.2.0-e562242",
4
+ "version": "0.2.0-eeb45f8",
5
5
  "author": "CanadaHonk",
6
6
  "license": "MIT",
7
7
  "dependencies": {
package/node_trace.1.log DELETED
@@ -1 +0,0 @@
1
- {"traceEvents":[{"pid":13264,"tid":15096,"ts":95423117674,"tts":0,"ph":"X","cat":"v8","name":"V8.DeserializeIsolate","dur":4531,"tdur":0,"args":{}},{"pid":13264,"tid":15096,"ts":95423122578,"tts":0,"ph":"X","cat":"v8","name":"V8.DeserializeContext","dur":3271,"tdur":0,"args":{}},{"pid":13264,"tid":15096,"ts":95423130539,"tts":0,"ph":"X","cat":"v8","name":"V8.DeserializeContext","dur":346,"tdur":0,"args":{}},{"pid":13264,"tid":15096,"ts":95423152512,"tts":0,"ph":"B","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":4996680,"type":"allocation failure"}},{"pid":13264,"tid":15096,"ts":95423152519,"tts":0,"ph":"X","cat":"v8","name":"V8.GCScavenger","dur":431,"tdur":0,"args":{}},{"pid":13264,"tid":15096,"ts":95423152969,"tts":0,"ph":"E","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":4389296}},{"pid":13264,"tid":15096,"ts":95423160029,"tts":0,"ph":"B","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":4924936,"type":"task"}},{"pid":13264,"tid":15096,"ts":95423160032,"tts":0,"ph":"X","cat":"v8","name":"V8.GCScavenger","dur":324,"tdur":0,"args":{}},{"pid":13264,"tid":15096,"ts":95423160367,"tts":0,"ph":"E","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":4440256}},{"pid":13264,"tid":15096,"ts":95423175256,"tts":67346,"ph":"B","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":6472768,"type":"allocation failure"}},{"pid":13264,"tid":15096,"ts":95423175267,"tts":67356,"ph":"X","cat":"v8","name":"V8.GCScavenger","dur":299,"tdur":300,"args":{}},{"pid":13264,"tid":15096,"ts":95423175578,"tts":67667,"ph":"E","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":5881080}},{"pid":13264,"tid":15096,"ts":95423188220,"tts":80241,"ph":"B","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":6928320,"type":"allocation failure"}},{"pid":13264,"tid":15096,"ts":95423188222,"tts":80243,"ph":"X","cat":"v8","name":"V8.GCScavenger","dur":363,"tdur":362,"args":{}},{"pid":13264,"tid":15096,"ts":95423188596,"tts":80616,"ph":"E","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":6372328}},{"pid":13264,"tid":15096,"ts":95423191843,"tts":83862,"ph":"B","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":8430312,"type":"allocation failure"}},{"pid":13264,"tid":15096,"ts":95423191845,"tts":83864,"ph":"X","cat":"v8","name":"V8.GCScavenger","dur":258,"tdur":239,"args":{}},{"pid":13264,"tid":15096,"ts":95423192113,"tts":84113,"ph":"E","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":6741768}},{"pid":13264,"tid":15096,"ts":95423199261,"tts":91198,"ph":"B","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":8882104,"type":"allocation failure"}},{"pid":13264,"tid":15096,"ts":95423199264,"tts":91200,"ph":"X","cat":"v8","name":"V8.GCScavenger","dur":256,"tdur":220,"args":{}},{"pid":13264,"tid":15096,"ts":95423199530,"tts":91430,"ph":"E","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":6991296}},{"pid":13264,"tid":15096,"ts":95423203033,"tts":94931,"ph":"B","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":9582528,"type":"allocation failure"}},{"pid":13264,"tid":15096,"ts":95423203035,"tts":94933,"ph":"X","cat":"v8","name":"V8.GCScavenger","dur":147,"tdur":128,"args":{}},{"pid":13264,"tid":15096,"ts":95423203191,"tts":95069,"ph":"E","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":7206184}},{"pid":13264,"tid":15096,"ts":95423207062,"tts":98940,"ph":"B","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":10032360,"type":"allocation failure"}},{"pid":13264,"tid":15096,"ts":95423207063,"tts":98941,"ph":"X","cat":"v8","name":"V8.GCScavenger","dur":98,"tdur":98,"args":{}},{"pid":13264,"tid":15096,"ts":95423207169,"tts":99046,"ph":"E","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":7617064}},{"pid":13264,"tid":15096,"ts":95423210148,"tts":102008,"ph":"B","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":10442184,"type":"allocation failure"}},{"pid":13264,"tid":15096,"ts":95423210149,"tts":102009,"ph":"X","cat":"v8","name":"V8.GCScavenger","dur":83,"tdur":82,"args":{}},{"pid":13264,"tid":15096,"ts":95423210241,"tts":102101,"ph":"E","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":7743728}},{"pid":13264,"tid":15096,"ts":95598120854,"tts":172740055,"ph":"B","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":10650144,"type":"allocation failure"}},{"pid":13264,"tid":15096,"ts":95598120861,"tts":172740060,"ph":"X","cat":"v8","name":"V8.GCScavenger","dur":188,"tdur":189,"args":{}},{"pid":13264,"tid":15096,"ts":95598121065,"tts":172740264,"ph":"E","cat":"devtools.timeline,v8","name":"MinorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":8016624}},{"pid":13264,"tid":15096,"ts":95707243189,"tts":280108308,"ph":"X","cat":"v8","name":"V8.DeoptimizeCode","dur":20,"tdur":19,"args":{}},{"pid":13264,"tid":15096,"ts":95707243201,"tts":280108319,"ph":"X","cat":"v8","name":"V8.DeoptimizeCode","dur":8,"tdur":8,"args":{}},{"pid":13264,"tid":15096,"ts":95707244259,"tts":280109369,"ph":"X","cat":"v8","name":"V8.DeoptimizeCode","dur":10,"tdur":8,"args":{}},{"pid":13264,"tid":15096,"ts":95707244265,"tts":280109374,"ph":"X","cat":"v8","name":"V8.DeoptimizeCode","dur":3,"tdur":3,"args":{}},{"pid":13264,"tid":15096,"ts":95707244376,"tts":280109452,"ph":"X","cat":"v8","name":"V8.DeoptimizeCode","dur":9,"tdur":8,"args":{}},{"pid":13264,"tid":15096,"ts":95707244381,"tts":280109456,"ph":"X","cat":"v8","name":"V8.DeoptimizeCode","dur":3,"tdur":4,"args":{}},{"pid":13264,"tid":15096,"ts":95707245192,"tts":280110267,"ph":"X","cat":"v8","name":"V8.GCIncrementalMarkingStart","dur":214,"tdur":214,"args":{"epoch":11,"reason":"memory reducer"}},{"pid":13264,"tid":15096,"ts":95707245464,"tts":280110540,"ph":"X","cat":"v8","name":"V8.GCIncrementalMarking","dur":7,"tdur":6,"args":{"epoch":11}},{"pid":13264,"tid":15096,"ts":95707245559,"tts":280110634,"ph":"B","cat":"devtools.timeline,v8","name":"MajorGC","dur":0,"tdur":0,"args":{"usedHeapSizeBefore":8366408,"type":"external finalize"}},{"pid":13264,"tid":15096,"ts":95707245561,"tts":280110635,"ph":"X","cat":"v8","name":"V8.GCFinalizeMCReduceMemory","dur":1914,"tdur":1617,"args":{}},{"pid":13264,"tid":15096,"ts":95707247497,"tts":280112273,"ph":"E","cat":"devtools.timeline,v8","name":"MajorGC","dur":0,"tdur":0,"args":{"usedHeapSizeAfter":6173808}},{"pid":13264,"tid":15096,"ts":95423116966,"tts":0,"ph":"M","cat":"__metadata","name":"process_name","dur":0,"tdur":0,"args":{"name":"Command Prompt - node --trace-event-categories v8 runner/index.js bench/bf.js"}},{"pid":13264,"tid":15096,"ts":95423116967,"tts":0,"ph":"M","cat":"__metadata","name":"version","dur":0,"tdur":0,"args":{"node":"21.6.0"}},{"pid":13264,"tid":15096,"ts":95423116968,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"JavaScriptMainThread"}},{"pid":13264,"tid":15096,"ts":95423116978,"tts":0,"ph":"M","cat":"__metadata","name":"node","dur":0,"tdur":0,"args":{"process":{"versions":{"node":"21.6.0","v8":"11.8.172.17-node.19","uv":"1.47.0","zlib":"1.3.0.1-motley-40e35a7","brotli":"1.1.0","ares":"1.20.1","modules":"120","nghttp2":"1.58.0","napi":"9","llhttp":"9.1.3","uvwasi":"0.0.19","acorn":"8.11.3","simdjson":"3.6.3","simdutf":"4.0.8","ada":"2.7.4","undici":"5.28.2","cjs_module_lexer":"1.2.2","base64":"0.5.1","openssl":"3.0.12+quic","cldr":"44.0","icu":"74.1","tz":"2023c","unicode":"15.1","ngtcp2":"0.8.1","nghttp3":"0.7.0"},"arch":"x64","platform":"win32","release":{"name":"node"}}}},{"pid":13264,"tid":9152,"ts":95423117058,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"WorkerThreadsTaskRunner::DelayedTaskScheduler"}},{"pid":13264,"tid":12304,"ts":95423117145,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":13264,"tid":11816,"ts":95423117184,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":13264,"tid":5044,"ts":95423117206,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":13264,"tid":4032,"ts":95423117236,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":13264,"tid":15096,"ts":95423116966,"tts":0,"ph":"M","cat":"__metadata","name":"process_name","dur":0,"tdur":0,"args":{"name":"Command Prompt - node --trace-event-categories v8 runner/index.js bench/bf.js"}},{"pid":13264,"tid":15096,"ts":95423116967,"tts":0,"ph":"M","cat":"__metadata","name":"version","dur":0,"tdur":0,"args":{"node":"21.6.0"}},{"pid":13264,"tid":15096,"ts":95423116968,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"JavaScriptMainThread"}},{"pid":13264,"tid":15096,"ts":95423116978,"tts":0,"ph":"M","cat":"__metadata","name":"node","dur":0,"tdur":0,"args":{"process":{"versions":{"node":"21.6.0","v8":"11.8.172.17-node.19","uv":"1.47.0","zlib":"1.3.0.1-motley-40e35a7","brotli":"1.1.0","ares":"1.20.1","modules":"120","nghttp2":"1.58.0","napi":"9","llhttp":"9.1.3","uvwasi":"0.0.19","acorn":"8.11.3","simdjson":"3.6.3","simdutf":"4.0.8","ada":"2.7.4","undici":"5.28.2","cjs_module_lexer":"1.2.2","base64":"0.5.1","openssl":"3.0.12+quic","cldr":"44.0","icu":"74.1","tz":"2023c","unicode":"15.1","ngtcp2":"0.8.1","nghttp3":"0.7.0"},"arch":"x64","platform":"win32","release":{"name":"node"}}}},{"pid":13264,"tid":9152,"ts":95423117058,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"WorkerThreadsTaskRunner::DelayedTaskScheduler"}},{"pid":13264,"tid":12304,"ts":95423117145,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":13264,"tid":11816,"ts":95423117184,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":13264,"tid":5044,"ts":95423117206,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":13264,"tid":4032,"ts":95423117236,"tts":0,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}}]}