porffor 0.0.0-70c2792 → 0.0.0-758fed5

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
@@ -77,6 +77,7 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
77
77
  - string concat (`+`) (eg `'a' + 'b'`)
78
78
  - truthy/falsy (eg `!'' == true`)
79
79
  - string comparison (eg `'a' == 'a'`, `'a' != 'b'`)
80
+ - nullish coalescing operator (`??`)
80
81
 
81
82
  ### built-ins
82
83
 
@@ -100,17 +101,29 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
100
101
  - intrinsic functions (see below)
101
102
  - inlining wasm via ``asm`...``\` "macro"
102
103
 
103
- ## soon todo
104
+ ## todo
105
+ no particular order and no guarentees, just what could happen soon™
106
+
104
107
  - arrays
105
108
  - member setting (`arr[0] = 2`)
106
109
  - more of `Array` prototype
107
110
  - arrays/strings inside arrays
111
+ - destructuring
112
+ - for .. of
108
113
  - strings
109
114
  - member setting
115
+ - objects
116
+ - basic object expressions (eg `{}`, `{ a: 0 }`)
117
+ - wasm
118
+ - *basic* wasm engine (interpreter) in js
119
+ - regex
120
+ - *basic* regex engine (in wasm compiled aot or js interpreter?)
110
121
  - more math operators (`**`, etc)
111
122
  - `do { ... } while (...)`
123
+ - rewrite `console.log` to work with strings/arrays
112
124
  - exceptions
113
- - `try { } finally {}`
125
+ - rewrite to use actual strings (optional?)
126
+ - `try { } finally { }`
114
127
  - rethrowing inside catch
115
128
  - optimizations
116
129
  - rewrite local indexes per func for smallest local header and remove unused idxs
@@ -118,6 +131,11 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
118
131
  - remove const ifs (`if (true)`, etc)
119
132
  - use data segments for initing arrays
120
133
 
134
+ ## porfformance
135
+ *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.
136
+
137
+ ![Screenshot of comparison chart](https://github.com/CanadaHonk/porffor/assets/19228318/76c75264-cc68-4be1-8891-c06dc389d97a)
138
+
121
139
  ## test262
122
140
  porffor can run test262 via some hacks/transforms which remove unsupported features whilst still doing the same asserts (eg simpler error messages using literals only). it currently passes >10% (see latest commit desc for latest and details). use `node test262` to test, it will also show a difference of overall results between the last commit and current results.
123
141
 
@@ -135,6 +153,7 @@ mostly for reducing size. do not really care about compiler perf/time as long as
135
153
  - `i64.extend_i32_s`, `i32.wrap_i64` -> ``
136
154
  - `f64.convert_i32_u`, `i32.trunc_sat_f64_s` -> ``
137
155
  - `return`, `end` -> `end`
156
+ - change const, convert to const of converted valtype (eg `f64.const`, `i32.trunc_sat_f64_s -> `i32.const`)
138
157
  - remove some redundant sets/gets
139
158
  - remove unneeded single just used vars
140
159
  - remove unneeded blocks (no `br`s inside)
@@ -190,11 +209,12 @@ you can also use deno (`deno run -A ...` instead of `node ...`), or bun (`bun ..
190
209
  - `-no-run` to not run wasm output, just compile
191
210
  - `-opt-log` to log some opts
192
211
  - `-code-log` to log some codegen (you probably want `-funcs`)
193
- - `-funcs` to log funcs (internal representations)
212
+ - `-funcs` to log funcs
194
213
  - `-opt-funcs` to log funcs after opt
195
214
  - `-sections` to log sections as hex
196
215
  - `-opt-no-inline` to not inline any funcs
197
- - `-tail-call` to enable tail calls (not widely implemented)
216
+ - `-tail-call` to enable tail calls (experimental + not widely implemented)
217
+ - `-compile-hints` to enable V8 compilation hints (experimental + doesn't seem to do much?)
198
218
 
199
219
  ## vscode extension
200
220
  there is a vscode extension in `porffor-for-vscode` which tweaks js syntax highlighting to be nicer with porffor features (eg highlighting wasm inside of inline asm).
@@ -174,7 +174,6 @@ const generate = (scope, decl, global = false, name = undefined) => {
174
174
  }
175
175
 
176
176
  if (asm[0] === 'memory') {
177
- scope.memory = true;
178
177
  allocPage('asm instrinsic');
179
178
  // todo: add to store/load offset insts
180
179
  continue;
@@ -288,7 +287,7 @@ const generateReturn = (scope, decl) => {
288
287
  ];
289
288
  }
290
289
 
291
- if (!scope.returnType) scope.returnType = getNodeType(scope, decl.argument);
290
+ scope.returnType = getNodeType(scope, decl.argument);
292
291
 
293
292
  return [
294
293
  ...generate(scope, decl.argument),
@@ -305,11 +304,11 @@ const localTmp = (scope, name, type = valtypeBinary) => {
305
304
  return idx;
306
305
  };
307
306
 
308
- const performLogicOp = (scope, op, left, right) => {
307
+ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
309
308
  const checks = {
310
- '||': Opcodes.eqz,
311
- '&&': [ Opcodes.i32_to ]
312
- // todo: ??
309
+ '||': falsy,
310
+ '&&': truthy,
311
+ '??': nullish
313
312
  };
314
313
 
315
314
  if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
@@ -320,7 +319,8 @@ const performLogicOp = (scope, op, left, right) => {
320
319
  return [
321
320
  ...left,
322
321
  [ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
323
- ...checks[op],
322
+ ...checks[op](scope, [], leftType),
323
+ Opcodes.i32_to,
324
324
  [ Opcodes.if, valtypeBinary ],
325
325
  ...right,
326
326
  [ Opcodes.else ],
@@ -335,8 +335,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
335
335
  // todo: optimize by looking up names in arrays and using that if exists?
336
336
  // todo: optimize this if using literals/known lengths?
337
337
 
338
- scope.memory = true;
339
-
340
338
  const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
341
339
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
342
340
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
@@ -471,8 +469,6 @@ const compareStrings = (scope, left, right) => {
471
469
  // todo: optimize by looking up names in arrays and using that if exists?
472
470
  // todo: optimize this if using literals/known lengths?
473
471
 
474
- scope.memory = true;
475
-
476
472
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
477
473
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
478
474
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
@@ -570,75 +566,100 @@ const compareStrings = (scope, left, right) => {
570
566
  ];
571
567
  };
572
568
 
573
- const falsy = (scope, wasm, type) => {
569
+ const truthy = (scope, wasm, type) => {
574
570
  // arrays are always truthy
575
571
  if (type === TYPES._array) return [
576
572
  ...wasm,
577
573
  [ Opcodes.drop ],
578
- number(0)
574
+ ...number(1)
579
575
  ];
580
576
 
581
577
  if (type === TYPES.string) {
582
- // if "" (length = 0)
578
+ // if not "" (length = 0)
583
579
  return [
584
580
  // pointer
585
581
  ...wasm,
582
+ Opcodes.i32_to_u,
586
583
 
587
584
  // get length
588
585
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
589
586
 
590
- // if length == 0
591
- [ Opcodes.i32_eqz ],
587
+ // if length != 0
588
+ /* [ Opcodes.i32_eqz ],
589
+ [ Opcodes.i32_eqz ], */
592
590
  Opcodes.i32_from_u
593
591
  ]
594
592
  }
595
593
 
596
- // if = 0
594
+ // if != 0
597
595
  return [
598
596
  ...wasm,
599
597
 
600
- ...Opcodes.eqz,
601
- Opcodes.i32_from_u
598
+ /* Opcodes.eqz,
599
+ [ Opcodes.i32_eqz ],
600
+ Opcodes.i32_from */
602
601
  ];
603
602
  };
604
603
 
605
- const truthy = (scope, wasm, type) => {
604
+ const falsy = (scope, wasm, type) => {
606
605
  // arrays are always truthy
607
606
  if (type === TYPES._array) return [
608
607
  ...wasm,
609
608
  [ Opcodes.drop ],
610
- number(1)
609
+ ...number(0)
611
610
  ];
612
611
 
613
612
  if (type === TYPES.string) {
614
- // if not "" (length = 0)
613
+ // if "" (length = 0)
615
614
  return [
616
615
  // pointer
617
616
  ...wasm,
617
+ Opcodes.i32_to_u,
618
618
 
619
619
  // get length
620
620
  [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
621
621
 
622
- // if length != 0
623
- /* [ Opcodes.i32_eqz ],
624
- [ Opcodes.i32_eqz ], */
622
+ // if length == 0
623
+ [ Opcodes.i32_eqz ],
625
624
  Opcodes.i32_from_u
626
625
  ]
627
626
  }
628
627
 
629
- // if != 0
628
+ // if = 0
630
629
  return [
631
630
  ...wasm,
632
631
 
633
- /* Opcodes.eqz,
634
- [ Opcodes.i32_eqz ],
635
- Opcodes.i32_from */
632
+ ...Opcodes.eqz,
633
+ Opcodes.i32_from_u
634
+ ];
635
+ };
636
+
637
+ const nullish = (scope, wasm, type) => {
638
+ // undefined
639
+ if (type === TYPES.undefined) return [
640
+ ...wasm,
641
+ [ Opcodes.drop ],
642
+ ...number(1)
643
+ ];
644
+
645
+ // null (if object and = "0")
646
+ if (type === TYPES.object) return [
647
+ ...wasm,
648
+ ...Opcodes.eqz,
649
+ Opcodes.i32_from_u
650
+ ];
651
+
652
+ // not
653
+ return [
654
+ ...wasm,
655
+ [ Opcodes.drop ],
656
+ ...number(0)
636
657
  ];
637
658
  };
638
659
 
639
660
  const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
640
661
  if (op === '||' || op === '&&' || op === '??') {
641
- return performLogicOp(scope, op, left, right);
662
+ return performLogicOp(scope, op, left, right, leftType, rightType);
642
663
  }
643
664
 
644
665
  if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
@@ -779,7 +800,7 @@ const includeBuiltin = (scope, builtin) => {
779
800
  };
780
801
 
781
802
  const generateLogicExp = (scope, decl) => {
782
- return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
803
+ return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
783
804
  };
784
805
 
785
806
  const TYPES = {
@@ -943,7 +964,8 @@ const generateLiteral = (scope, decl, global, name) => {
943
964
  const countLeftover = wasm => {
944
965
  let count = 0, depth = 0;
945
966
 
946
- for (const inst of wasm) {
967
+ for (let i = 0; i < wasm.length; i++) {
968
+ const inst = wasm[i];
947
969
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
948
970
  if (inst[0] === Opcodes.if) count--;
949
971
  if (inst[1] !== Blocktype.void) count++;
@@ -1047,7 +1069,7 @@ const generateCall = (scope, decl, _global, _name) => {
1047
1069
  }
1048
1070
 
1049
1071
  let out = [];
1050
- let protoFunc, protoName, baseType, baseName = '$undeclared';
1072
+ let protoFunc, protoName, baseType, baseName;
1051
1073
  // ident.func()
1052
1074
  if (name && name.startsWith('__')) {
1053
1075
  const spl = name.slice(2).split('_');
@@ -1070,11 +1092,11 @@ const generateCall = (scope, decl, _global, _name) => {
1070
1092
 
1071
1093
  out = generate(scope, decl.callee.object);
1072
1094
  out.push([ Opcodes.drop ]);
1095
+
1096
+ baseName = [...arrays.keys()].pop();
1073
1097
  }
1074
1098
 
1075
1099
  if (protoFunc) {
1076
- scope.memory = true;
1077
-
1078
1100
  let pointer = arrays.get(baseName);
1079
1101
 
1080
1102
  if (pointer == null) {
@@ -1082,7 +1104,7 @@ const generateCall = (scope, decl, _global, _name) => {
1082
1104
  if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
1083
1105
 
1084
1106
  // register array
1085
- const [ , pointer ] = makeArray(scope, {
1107
+ 0, [ , pointer ] = makeArray(scope, {
1086
1108
  rawElements: new Array(0)
1087
1109
  }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
1088
1110
 
@@ -1180,7 +1202,6 @@ const generateCall = (scope, decl, _global, _name) => {
1180
1202
  args = args.slice(0, func.params.length);
1181
1203
  }
1182
1204
 
1183
- if (func && func.memory) scope.memory = true;
1184
1205
  if (func && func.throws) scope.throws = true;
1185
1206
 
1186
1207
  for (const arg of args) {
@@ -1304,8 +1325,6 @@ const generateAssign = (scope, decl) => {
1304
1325
  const name = decl.left.object.name;
1305
1326
  const pointer = arrays.get(name);
1306
1327
 
1307
- scope.memory = true;
1308
-
1309
1328
  const aotPointer = pointer != null;
1310
1329
 
1311
1330
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
@@ -1356,8 +1375,27 @@ const generateAssign = (scope, decl) => {
1356
1375
  ];
1357
1376
  }
1358
1377
 
1378
+ const op = decl.operator.slice(0, -1);
1379
+ if (op === '||' || op === '&&' || op === '??') {
1380
+ // todo: is this needed?
1381
+ // for logical assignment ops, it is not left @= right ~= left = left @ right
1382
+ // instead, left @ (left = right)
1383
+ // eg, x &&= y ~= x && (x = y)
1384
+
1385
+ return [
1386
+ ...performOp(scope, op, [
1387
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1388
+ ], [
1389
+ ...generate(scope, decl.right),
1390
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1391
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1392
+ ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1393
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1394
+ ];
1395
+ }
1396
+
1359
1397
  return [
1360
- ...performOp(scope, decl.operator.slice(0, -1), [ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ] ], generate(scope, decl.right), getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1398
+ ...performOp(scope, op, [ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ] ], generate(scope, decl.right), getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1361
1399
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1362
1400
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1363
1401
  ];
@@ -1384,13 +1422,14 @@ const generateUnary = (scope, decl) => {
1384
1422
 
1385
1423
  case '!':
1386
1424
  // !=
1387
- return falsy(scope, generate(scope, decl.argument));
1425
+ return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
1388
1426
 
1389
1427
  case '~':
1428
+ // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
1390
1429
  return [
1391
1430
  ...generate(scope, decl.argument),
1392
1431
  Opcodes.i32_to,
1393
- [ Opcodes.i32_const, signedLEB128(-1) ],
1432
+ [ Opcodes.i32_const, ...signedLEB128(-1) ],
1394
1433
  [ Opcodes.i32_xor ],
1395
1434
  Opcodes.i32_from
1396
1435
  ];
@@ -1743,8 +1782,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1743
1782
  // local value as pointer
1744
1783
  out.push(...number(pointer));
1745
1784
 
1746
- scope.memory = true;
1747
-
1748
1785
  return [ out, pointer ];
1749
1786
  };
1750
1787
 
@@ -1763,8 +1800,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1763
1800
  const name = decl.object.name;
1764
1801
  const pointer = arrays.get(name);
1765
1802
 
1766
- scope.memory = true;
1767
-
1768
1803
  const aotPointer = pointer != null;
1769
1804
 
1770
1805
  return [
@@ -1784,8 +1819,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1784
1819
  const name = decl.object.name;
1785
1820
  const pointer = arrays.get(name);
1786
1821
 
1787
- scope.memory = true;
1788
-
1789
1822
  const aotPointer = pointer != null;
1790
1823
 
1791
1824
  if (type === TYPES._array) {
@@ -1895,6 +1928,7 @@ const generateFunc = (scope, decl) => {
1895
1928
  locals: {},
1896
1929
  localInd: 0,
1897
1930
  returns: [ valtypeBinary ],
1931
+ returnType: null,
1898
1932
  memory: false,
1899
1933
  throws: false,
1900
1934
  name
@@ -1921,7 +1955,6 @@ const generateFunc = (scope, decl) => {
1921
1955
  returns: innerScope.returns,
1922
1956
  returnType: innerScope.returnType,
1923
1957
  locals: innerScope.locals,
1924
- memory: innerScope.memory,
1925
1958
  throws: innerScope.throws,
1926
1959
  index: currentFuncIndex++
1927
1960
  };
@@ -1936,6 +1969,8 @@ const generateFunc = (scope, decl) => {
1936
1969
 
1937
1970
  if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
1938
1971
  wasm.push(...number(0), [ Opcodes.return ]);
1972
+
1973
+ if (func.returnType === null) func.returnType = TYPES.undefined;
1939
1974
  }
1940
1975
 
1941
1976
  // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
@@ -1946,9 +1981,7 @@ const generateFunc = (scope, decl) => {
1946
1981
  if (local.type === Valtype.v128) {
1947
1982
  vecParams++;
1948
1983
 
1949
- /* func.memory = true; // mark func as using memory
1950
-
1951
- wasm.unshift( // add v128 load for param
1984
+ /* wasm.unshift( // add v128 load for param
1952
1985
  [ Opcodes.i32_const, 0 ],
1953
1986
  [ ...Opcodes.v128_load, 0, i * 16 ],
1954
1987
  [ Opcodes.local_set, local.idx ]
@@ -42,8 +42,8 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
42
42
  out += ` ${read_ieee754_binary64(inst.slice(1))}`;
43
43
  } else if (inst[0] === Opcodes.i32_const || inst[0] === Opcodes.i64_const) {
44
44
  out += ` ${read_signedLEB128(inst.slice(1))}`;
45
- } else if (inst[0] === Opcodes.i32_load || inst[0] === Opcodes.i64_load || inst[0] === Opcodes.f64_load || inst[0] === Opcodes.i32_store || inst[0] === Opcodes.i64_store || inst[0] === Opcodes.f64_store) {
46
- out += ` ${inst[1]} ${read_unsignedLEB128(inst.slice(2))}`
45
+ } else if (inst[0] === Opcodes.i32_load || inst[0] === Opcodes.i64_load || inst[0] === Opcodes.f64_load || inst[0] === Opcodes.i32_store || inst[0] === Opcodes.i64_store || inst[0] === Opcodes.f64_store || inst[0] === Opcodes.i32_store16 || inst[0] === Opcodes.i32_load16_u) {
46
+ out += ` ${inst[1]} ${read_unsignedLEB128(inst.slice(2))}`;
47
47
  } else for (const operand of inst.slice(1)) {
48
48
  if (inst[0] === Opcodes.if || inst[0] === Opcodes.loop || inst[0] === Opcodes.block) {
49
49
  if (operand === Blocktype.void) continue;
@@ -22,15 +22,15 @@ export const encodeLocal = (count, type) => [
22
22
  type
23
23
  ];
24
24
 
25
+ // todo: this only works with integers within 32 bit range
25
26
  export const signedLEB128 = n => {
26
- // todo: this only works with integers within 32 bit range
27
+ n |= 0;
27
28
 
28
29
  // just input for small numbers (for perf as common)
29
30
  if (n >= 0 && n <= 63) return [ n ];
30
31
  if (n >= -64 && n <= 0) return [ 128 + n ];
31
32
 
32
33
  const buffer = [];
33
- n |= 0;
34
34
 
35
35
  while (true) {
36
36
  let byte = n & 0x7f;
@@ -50,6 +50,8 @@ export const signedLEB128 = n => {
50
50
  };
51
51
 
52
52
  export const unsignedLEB128 = n => {
53
+ n |= 0;
54
+
53
55
  // just input for small numbers (for perf as common)
54
56
  if (n >= 0 && n <= 127) return [ n ];
55
57
 
package/compiler/opt.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Opcodes, Valtype } from "./wasmSpec.js";
2
2
  import { number } from "./embedding.js";
3
+ import { read_signedLEB128, read_ieee754_binary64 } from "./encoding.js";
3
4
 
4
5
  // deno compat
5
6
  if (typeof process === 'undefined' && typeof Deno !== 'undefined') {
@@ -95,7 +96,6 @@ export default (funcs, globals) => {
95
96
  }
96
97
 
97
98
  if (t.index > c.index) t.index--; // adjust index if after removed func
98
- if (c.memory) t.memory = true;
99
99
  }
100
100
 
101
101
  funcs.splice(funcs.indexOf(c), 1); // remove func from funcs
@@ -213,9 +213,9 @@ export default (funcs, globals) => {
213
213
  // i32.const 0
214
214
  // drop
215
215
  // -->
216
- // <nothing>>
216
+ // <nothing>
217
217
 
218
- wasm.splice(i - 1, 2); // remove this inst
218
+ wasm.splice(i - 1, 2); // remove these inst
219
219
  i -= 2;
220
220
  continue;
221
221
  }
@@ -259,6 +259,36 @@ export default (funcs, globals) => {
259
259
  continue;
260
260
  }
261
261
 
262
+ if (lastInst[0] === Opcodes.const && (inst === Opcodes.i32_to || inst === Opcodes.i32_to_u)) {
263
+ // change const and immediate i32 convert to i32 const
264
+ // f64.const 0
265
+ // i32.trunc_sat_f64_s || i32.trunc_sat_f64_u
266
+ // -->
267
+ // i32.const 0
268
+
269
+ wasm[i - 1] = number((valtype === 'f64' ? read_ieee754_binary64 : read_signedLEB128)(lastInst.slice(1)), Valtype.i32)[0]; // f64.const -> i32.const
270
+
271
+ wasm.splice(i, 1); // remove this inst
272
+ i--;
273
+ if (optLog) log('opt', `converted const -> i32 convert into i32 const`);
274
+ continue;
275
+ }
276
+
277
+ if (lastInst[0] === Opcodes.i32_const && (inst === Opcodes.i32_from || inst === Opcodes.i32_from_u)) {
278
+ // change i32 const and immediate convert to const (opposite way of previous)
279
+ // i32.const 0
280
+ // f64.convert_i32_s || f64.convert_i32_u
281
+ // -->
282
+ // f64.const 0
283
+
284
+ wasm[i - 1] = number(read_signedLEB128(lastInst.slice(1)))[0]; // i32.const -> f64.const
285
+
286
+ wasm.splice(i, 1); // remove this inst
287
+ i--;
288
+ if (optLog) log('opt', `converted i32 const -> convert into const`);
289
+ continue;
290
+ }
291
+
262
292
  if (tailCall && lastInst[0] === Opcodes.call && inst[0] === Opcodes.return) {
263
293
  // replace call, return with tail calls (return_call)
264
294
  // call X
@@ -8,11 +8,26 @@ const createSection = (type, data) => [
8
8
  ...encodeVector(data)
9
9
  ];
10
10
 
11
+ const customSection = (name, data) => [
12
+ Section.custom,
13
+ ...encodeVector([...encodeString(name), ...data])
14
+ ];
15
+
16
+ const chHint = (topTier, baselineTier, strategy) => {
17
+ // 1 byte of 4 2 bit components: spare, top tier, baseline tier, compilation strategy
18
+ // tiers: 0x00 = default, 0x01 = baseline (liftoff), 0x02 = optimized (turbofan)
19
+ // strategy: 0x00 = default, 0x01 = lazy, 0x02 = eager, 0x03 = lazy baseline, eager top tier
20
+ return (strategy | (baselineTier << 2) | (topTier << 4));
21
+ };
22
+
11
23
  export default (funcs, globals, tags, pages, flags) => {
12
24
  const types = [], typeCache = {};
13
25
 
14
26
  const optLevel = parseInt(process.argv.find(x => x.startsWith('-O'))?.[2] ?? 1);
15
27
 
28
+ const compileHints = process.argv.includes('-compile-hints');
29
+ if (compileHints) log('sections', 'warning: compile hints is V8 only w/ experimental arg! (you used -compile-hints)');
30
+
16
31
  const getType = (params, returns) => {
17
32
  const hash = `${params.join(',')}_${returns.join(',')}`;
18
33
  if (optLog) log('sections', `getType(${JSON.stringify(params)}, ${JSON.stringify(returns)}) -> ${hash} | cache: ${typeCache[hash]}`);
@@ -74,6 +89,14 @@ export default (funcs, globals, tags, pages, flags) => {
74
89
  encodeVector(funcs.map(x => getType(x.params, x.returns))) // type indexes
75
90
  );
76
91
 
92
+ // compilation hints section - unspec v8 only
93
+ // https://github.com/WebAssembly/design/issues/1473#issuecomment-1431274746
94
+ const chSection = !compileHints ? [] : customSection(
95
+ 'compilationHints',
96
+ // for now just do everything as optimise eager
97
+ encodeVector(funcs.map(_ => chHint(0x02, 0x02, 0x02)))
98
+ );
99
+
77
100
  const globalSection = Object.keys(globals).length === 0 ? [] : createSection(
78
101
  Section.global,
79
102
  encodeVector(Object.keys(globals).map(x => [ globals[x].type, 0x01, ...number(globals[x].init ?? 0, globals[x].type).flat(), Opcodes.end ]))
@@ -146,6 +169,7 @@ export default (funcs, globals, tags, pages, flags) => {
146
169
  ...typeSection,
147
170
  ...importSection,
148
171
  ...funcSection,
172
+ ...chSection,
149
173
  ...memorySection,
150
174
  ...tagSection,
151
175
  ...globalSection,
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-70c2792",
4
+ "version": "0.0.0-758fed5",
5
5
  "author": "CanadaHonk",
6
6
  "license": "MIT",
7
7
  "dependencies": {
package/runner/index.js CHANGED
@@ -3,6 +3,18 @@
3
3
  import compile from '../compiler/wrap.js';
4
4
  import fs from 'node:fs';
5
5
 
6
+ if (process.argv.includes('-compile-hints')) {
7
+ const v8 = await import('node:v8');
8
+ v8.setFlagsFromString(`--experimental-wasm-compilation-hints`);
9
+
10
+ // see also these flags:
11
+ // --experimental-wasm-branch-hinting
12
+ // --experimental-wasm-extended-const
13
+ // --experimental-wasm-inlining (?)
14
+ // --experimental-wasm-js-inlining (?)
15
+ // --experimental-wasm-return-call (on by default)
16
+ }
17
+
6
18
  const file = process.argv.slice(2).find(x => x[0] !== '-');
7
19
  if (!file) {
8
20
  if (process.argv.includes('-v')) {