porffor 0.0.0-bddcdc3 → 0.0.0-ebc0491
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 +24 -4
- package/compiler/codeGen.js +104 -55
- package/compiler/decompile.js +2 -2
- package/compiler/encoding.js +4 -2
- package/compiler/index.js +10 -1
- package/compiler/opt.js +34 -4
- package/compiler/prototype.js +2 -4
- package/compiler/rhemyn/compile.js +173 -0
- package/compiler/rhemyn/parse.js +125 -0
- package/compiler/rhemyn/test/parse.js +20 -0
- package/compiler/sections.js +27 -2
- package/compiler/wrap.js +9 -0
- package/package.json +1 -1
- package/r.js +1 -0
- package/runner/index.js +18 -0
- package/runner/repl.js +3 -10
- package/runner/transform.js +2 -1
- package/runner/version.js +10 -0
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
|
-
##
|
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
|
-
-
|
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
|
+

|
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
|
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).
|
package/compiler/codeGen.js
CHANGED
@@ -35,7 +35,14 @@ const debug = str => {
|
|
35
35
|
};
|
36
36
|
|
37
37
|
const todo = msg => {
|
38
|
-
|
38
|
+
class TodoError extends Error {
|
39
|
+
constructor(message) {
|
40
|
+
super(message);
|
41
|
+
this.name = 'TodoError';
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
throw new TodoError(`todo: ${msg}`);
|
39
46
|
|
40
47
|
const code = [];
|
41
48
|
|
@@ -167,7 +174,6 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
167
174
|
}
|
168
175
|
|
169
176
|
if (asm[0] === 'memory') {
|
170
|
-
scope.memory = true;
|
171
177
|
allocPage('asm instrinsic');
|
172
178
|
// todo: add to store/load offset insts
|
173
179
|
continue;
|
@@ -281,7 +287,7 @@ const generateReturn = (scope, decl) => {
|
|
281
287
|
];
|
282
288
|
}
|
283
289
|
|
284
|
-
|
290
|
+
scope.returnType = getNodeType(scope, decl.argument);
|
285
291
|
|
286
292
|
return [
|
287
293
|
...generate(scope, decl.argument),
|
@@ -298,11 +304,11 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
298
304
|
return idx;
|
299
305
|
};
|
300
306
|
|
301
|
-
const performLogicOp = (scope, op, left, right) => {
|
307
|
+
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
302
308
|
const checks = {
|
303
|
-
'||':
|
304
|
-
'&&':
|
305
|
-
|
309
|
+
'||': falsy,
|
310
|
+
'&&': truthy,
|
311
|
+
'??': nullish
|
306
312
|
};
|
307
313
|
|
308
314
|
if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
|
@@ -313,7 +319,8 @@ const performLogicOp = (scope, op, left, right) => {
|
|
313
319
|
return [
|
314
320
|
...left,
|
315
321
|
[ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
|
316
|
-
...checks[op],
|
322
|
+
...checks[op](scope, [], leftType),
|
323
|
+
Opcodes.i32_to,
|
317
324
|
[ Opcodes.if, valtypeBinary ],
|
318
325
|
...right,
|
319
326
|
[ Opcodes.else ],
|
@@ -328,8 +335,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
328
335
|
// todo: optimize by looking up names in arrays and using that if exists?
|
329
336
|
// todo: optimize this if using literals/known lengths?
|
330
337
|
|
331
|
-
scope.memory = true;
|
332
|
-
|
333
338
|
const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
|
334
339
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
335
340
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
@@ -464,8 +469,6 @@ const compareStrings = (scope, left, right) => {
|
|
464
469
|
// todo: optimize by looking up names in arrays and using that if exists?
|
465
470
|
// todo: optimize this if using literals/known lengths?
|
466
471
|
|
467
|
-
scope.memory = true;
|
468
|
-
|
469
472
|
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
470
473
|
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
471
474
|
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
@@ -563,75 +566,100 @@ const compareStrings = (scope, left, right) => {
|
|
563
566
|
];
|
564
567
|
};
|
565
568
|
|
566
|
-
const
|
569
|
+
const truthy = (scope, wasm, type) => {
|
567
570
|
// arrays are always truthy
|
568
571
|
if (type === TYPES._array) return [
|
569
572
|
...wasm,
|
570
573
|
[ Opcodes.drop ],
|
571
|
-
number(
|
574
|
+
...number(1)
|
572
575
|
];
|
573
576
|
|
574
577
|
if (type === TYPES.string) {
|
575
|
-
// if "" (length = 0)
|
578
|
+
// if not "" (length = 0)
|
576
579
|
return [
|
577
580
|
// pointer
|
578
581
|
...wasm,
|
582
|
+
Opcodes.i32_to_u,
|
579
583
|
|
580
584
|
// get length
|
581
585
|
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
582
586
|
|
583
|
-
// if length
|
584
|
-
[ Opcodes.i32_eqz ],
|
587
|
+
// if length != 0
|
588
|
+
/* [ Opcodes.i32_eqz ],
|
589
|
+
[ Opcodes.i32_eqz ], */
|
585
590
|
Opcodes.i32_from_u
|
586
591
|
]
|
587
592
|
}
|
588
593
|
|
589
|
-
// if
|
594
|
+
// if != 0
|
590
595
|
return [
|
591
596
|
...wasm,
|
592
597
|
|
593
|
-
|
594
|
-
Opcodes.
|
598
|
+
/* Opcodes.eqz,
|
599
|
+
[ Opcodes.i32_eqz ],
|
600
|
+
Opcodes.i32_from */
|
595
601
|
];
|
596
602
|
};
|
597
603
|
|
598
|
-
const
|
604
|
+
const falsy = (scope, wasm, type) => {
|
599
605
|
// arrays are always truthy
|
600
606
|
if (type === TYPES._array) return [
|
601
607
|
...wasm,
|
602
608
|
[ Opcodes.drop ],
|
603
|
-
number(
|
609
|
+
...number(0)
|
604
610
|
];
|
605
611
|
|
606
612
|
if (type === TYPES.string) {
|
607
|
-
// if
|
613
|
+
// if "" (length = 0)
|
608
614
|
return [
|
609
615
|
// pointer
|
610
616
|
...wasm,
|
617
|
+
Opcodes.i32_to_u,
|
611
618
|
|
612
619
|
// get length
|
613
620
|
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
614
621
|
|
615
|
-
// if length
|
616
|
-
|
617
|
-
[ Opcodes.i32_eqz ], */
|
622
|
+
// if length == 0
|
623
|
+
[ Opcodes.i32_eqz ],
|
618
624
|
Opcodes.i32_from_u
|
619
625
|
]
|
620
626
|
}
|
621
627
|
|
622
|
-
// if
|
628
|
+
// if = 0
|
623
629
|
return [
|
624
630
|
...wasm,
|
625
631
|
|
626
|
-
|
627
|
-
|
628
|
-
|
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)
|
629
657
|
];
|
630
658
|
};
|
631
659
|
|
632
660
|
const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
|
633
661
|
if (op === '||' || op === '&&' || op === '??') {
|
634
|
-
return performLogicOp(scope, op, left, right);
|
662
|
+
return performLogicOp(scope, op, left, right, leftType, rightType);
|
635
663
|
}
|
636
664
|
|
637
665
|
if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
|
@@ -772,7 +800,7 @@ const includeBuiltin = (scope, builtin) => {
|
|
772
800
|
};
|
773
801
|
|
774
802
|
const generateLogicExp = (scope, decl) => {
|
775
|
-
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));
|
776
804
|
};
|
777
805
|
|
778
806
|
const TYPES = {
|
@@ -936,7 +964,8 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
936
964
|
const countLeftover = wasm => {
|
937
965
|
let count = 0, depth = 0;
|
938
966
|
|
939
|
-
for (
|
967
|
+
for (let i = 0; i < wasm.length; i++) {
|
968
|
+
const inst = wasm[i];
|
940
969
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
941
970
|
if (inst[0] === Opcodes.if) count--;
|
942
971
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1040,7 +1069,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1040
1069
|
}
|
1041
1070
|
|
1042
1071
|
let out = [];
|
1043
|
-
let protoFunc, protoName, baseType, baseName
|
1072
|
+
let protoFunc, protoName, baseType, baseName;
|
1044
1073
|
// ident.func()
|
1045
1074
|
if (name && name.startsWith('__')) {
|
1046
1075
|
const spl = name.slice(2).split('_');
|
@@ -1063,11 +1092,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1063
1092
|
|
1064
1093
|
out = generate(scope, decl.callee.object);
|
1065
1094
|
out.push([ Opcodes.drop ]);
|
1095
|
+
|
1096
|
+
baseName = [...arrays.keys()].pop();
|
1066
1097
|
}
|
1067
1098
|
|
1068
1099
|
if (protoFunc) {
|
1069
|
-
scope.memory = true;
|
1070
|
-
|
1071
1100
|
let pointer = arrays.get(baseName);
|
1072
1101
|
|
1073
1102
|
if (pointer == null) {
|
@@ -1075,7 +1104,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1075
1104
|
if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
|
1076
1105
|
|
1077
1106
|
// register array
|
1078
|
-
|
1107
|
+
0, [ , pointer ] = makeArray(scope, {
|
1079
1108
|
rawElements: new Array(0)
|
1080
1109
|
}, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
|
1081
1110
|
|
@@ -1173,7 +1202,6 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1173
1202
|
args = args.slice(0, func.params.length);
|
1174
1203
|
}
|
1175
1204
|
|
1176
|
-
if (func && func.memory) scope.memory = true;
|
1177
1205
|
if (func && func.throws) scope.throws = true;
|
1178
1206
|
|
1179
1207
|
for (const arg of args) {
|
@@ -1189,7 +1217,7 @@ const generateNew = (scope, decl, _global, _name) => {
|
|
1189
1217
|
// hack: basically treat this as a normal call for builtins for now
|
1190
1218
|
const name = mapName(decl.callee.name);
|
1191
1219
|
if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1192
|
-
if (!builtinFuncs[name]) return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1220
|
+
if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1193
1221
|
|
1194
1222
|
return generateCall(scope, decl, _global, _name);
|
1195
1223
|
};
|
@@ -1297,8 +1325,6 @@ const generateAssign = (scope, decl) => {
|
|
1297
1325
|
const name = decl.left.object.name;
|
1298
1326
|
const pointer = arrays.get(name);
|
1299
1327
|
|
1300
|
-
scope.memory = true;
|
1301
|
-
|
1302
1328
|
const aotPointer = pointer != null;
|
1303
1329
|
|
1304
1330
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
@@ -1349,8 +1375,27 @@ const generateAssign = (scope, decl) => {
|
|
1349
1375
|
];
|
1350
1376
|
}
|
1351
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
|
+
|
1352
1397
|
return [
|
1353
|
-
...performOp(scope,
|
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),
|
1354
1399
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1355
1400
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1356
1401
|
];
|
@@ -1377,13 +1422,14 @@ const generateUnary = (scope, decl) => {
|
|
1377
1422
|
|
1378
1423
|
case '!':
|
1379
1424
|
// !=
|
1380
|
-
return falsy(scope, generate(scope, decl.argument));
|
1425
|
+
return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
|
1381
1426
|
|
1382
1427
|
case '~':
|
1428
|
+
// todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
|
1383
1429
|
return [
|
1384
1430
|
...generate(scope, decl.argument),
|
1385
1431
|
Opcodes.i32_to,
|
1386
|
-
[ Opcodes.i32_const, signedLEB128(-1) ],
|
1432
|
+
[ Opcodes.i32_const, ...signedLEB128(-1) ],
|
1387
1433
|
[ Opcodes.i32_xor ],
|
1388
1434
|
Opcodes.i32_from
|
1389
1435
|
];
|
@@ -1665,7 +1711,16 @@ const allocPage = reason => {
|
|
1665
1711
|
let ind = pages.size;
|
1666
1712
|
pages.set(reason, ind);
|
1667
1713
|
|
1668
|
-
if (
|
1714
|
+
if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason}`);
|
1715
|
+
|
1716
|
+
return ind;
|
1717
|
+
};
|
1718
|
+
|
1719
|
+
const freePage = reason => {
|
1720
|
+
let ind = pages.get(reason);
|
1721
|
+
pages.delete(reason);
|
1722
|
+
|
1723
|
+
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
1669
1724
|
|
1670
1725
|
return ind;
|
1671
1726
|
};
|
@@ -1727,8 +1782,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1727
1782
|
// local value as pointer
|
1728
1783
|
out.push(...number(pointer));
|
1729
1784
|
|
1730
|
-
scope.memory = true;
|
1731
|
-
|
1732
1785
|
return [ out, pointer ];
|
1733
1786
|
};
|
1734
1787
|
|
@@ -1747,8 +1800,6 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1747
1800
|
const name = decl.object.name;
|
1748
1801
|
const pointer = arrays.get(name);
|
1749
1802
|
|
1750
|
-
scope.memory = true;
|
1751
|
-
|
1752
1803
|
const aotPointer = pointer != null;
|
1753
1804
|
|
1754
1805
|
return [
|
@@ -1768,8 +1819,6 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1768
1819
|
const name = decl.object.name;
|
1769
1820
|
const pointer = arrays.get(name);
|
1770
1821
|
|
1771
|
-
scope.memory = true;
|
1772
|
-
|
1773
1822
|
const aotPointer = pointer != null;
|
1774
1823
|
|
1775
1824
|
if (type === TYPES._array) {
|
@@ -1879,6 +1928,7 @@ const generateFunc = (scope, decl) => {
|
|
1879
1928
|
locals: {},
|
1880
1929
|
localInd: 0,
|
1881
1930
|
returns: [ valtypeBinary ],
|
1931
|
+
returnType: null,
|
1882
1932
|
memory: false,
|
1883
1933
|
throws: false,
|
1884
1934
|
name
|
@@ -1905,7 +1955,6 @@ const generateFunc = (scope, decl) => {
|
|
1905
1955
|
returns: innerScope.returns,
|
1906
1956
|
returnType: innerScope.returnType,
|
1907
1957
|
locals: innerScope.locals,
|
1908
|
-
memory: innerScope.memory,
|
1909
1958
|
throws: innerScope.throws,
|
1910
1959
|
index: currentFuncIndex++
|
1911
1960
|
};
|
@@ -1920,6 +1969,8 @@ const generateFunc = (scope, decl) => {
|
|
1920
1969
|
|
1921
1970
|
if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
|
1922
1971
|
wasm.push(...number(0), [ Opcodes.return ]);
|
1972
|
+
|
1973
|
+
if (func.returnType === null) func.returnType = TYPES.undefined;
|
1923
1974
|
}
|
1924
1975
|
|
1925
1976
|
// change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
|
@@ -1930,9 +1981,7 @@ const generateFunc = (scope, decl) => {
|
|
1930
1981
|
if (local.type === Valtype.v128) {
|
1931
1982
|
vecParams++;
|
1932
1983
|
|
1933
|
-
/*
|
1934
|
-
|
1935
|
-
wasm.unshift( // add v128 load for param
|
1984
|
+
/* wasm.unshift( // add v128 load for param
|
1936
1985
|
[ Opcodes.i32_const, 0 ],
|
1937
1986
|
[ ...Opcodes.v128_load, 0, i * 16 ],
|
1938
1987
|
[ Opcodes.local_set, local.idx ]
|
package/compiler/decompile.js
CHANGED
@@ -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;
|
package/compiler/encoding.js
CHANGED
@@ -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
|
-
|
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/index.js
CHANGED
@@ -14,7 +14,8 @@ const bold = x => `\u001b[1m${x}\u001b[0m`;
|
|
14
14
|
const areaColors = {
|
15
15
|
codegen: [ 20, 80, 250 ],
|
16
16
|
opt: [ 250, 20, 80 ],
|
17
|
-
sections: [ 20, 250, 80 ]
|
17
|
+
sections: [ 20, 250, 80 ],
|
18
|
+
alloc: [ 250, 250, 20 ]
|
18
19
|
};
|
19
20
|
|
20
21
|
globalThis.log = (area, ...args) => console.log(`\u001b[90m[\u001b[0m${rgb(...areaColors[area], area)}\u001b[90m]\u001b[0m`, ...args);
|
@@ -38,6 +39,7 @@ const logFuncs = (funcs, globals, exceptions) => {
|
|
38
39
|
export default (code, flags) => {
|
39
40
|
globalThis.optLog = process.argv.includes('-opt-log');
|
40
41
|
globalThis.codeLog = process.argv.includes('-code-log');
|
42
|
+
globalThis.allocLog = process.argv.includes('-alloc-log');
|
41
43
|
|
42
44
|
for (const x in BuiltinPreludes) {
|
43
45
|
if (code.indexOf(x + '(') !== -1) code = BuiltinPreludes[x] + code;
|
@@ -63,5 +65,12 @@ export default (code, flags) => {
|
|
63
65
|
const sections = produceSections(funcs, globals, tags, pages, flags);
|
64
66
|
if (flags.includes('info')) console.log(`4. produced sections in ${(performance.now() - t3).toFixed(2)}ms`);
|
65
67
|
|
68
|
+
if (allocLog) {
|
69
|
+
const wasmPages = Math.ceil((pages.size * pageSize) / 65536);
|
70
|
+
const bytes = wasmPages * 65536;
|
71
|
+
log('alloc', `\x1B[1mallocated ${bytes / 1024}KiB\x1B[0m for ${pages.size} things using ${wasmPages} Wasm page${wasmPages === 1 ? '' : 's'}`);
|
72
|
+
// console.log([...pages.keys()].map(x => `\x1B[36m - ${x}\x1B[0m`).join('\n'));
|
73
|
+
}
|
74
|
+
|
66
75
|
return { wasm: sections, funcs, globals, tags, exceptions, pages };
|
67
76
|
};
|
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') {
|
@@ -20,7 +21,7 @@ export default (funcs, globals) => {
|
|
20
21
|
if (optLevel === 0) return;
|
21
22
|
|
22
23
|
const tailCall = process.argv.includes('-tail-call');
|
23
|
-
if (tailCall) log('opt', 'tail call proposal is not widely implemented! (you used -tail-call)');
|
24
|
+
if (tailCall) log('opt', 'warning: tail call proposal is not widely implemented! (you used -tail-call)');
|
24
25
|
|
25
26
|
if (optLevel >= 2 && !process.argv.includes('-opt-no-inline')) {
|
26
27
|
// inline pass (very WIP)
|
@@ -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
|
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
|
package/compiler/prototype.js
CHANGED
@@ -25,7 +25,6 @@ export const PrototypeFuncs = function() {
|
|
25
25
|
|
26
26
|
this[TYPES._array] = {
|
27
27
|
// lX = local accessor of X ({ get, set }), iX = local index of X, wX = wasm ops of X
|
28
|
-
// todo: out of bounds (>) properly
|
29
28
|
at: (pointer, length, wIndex, iTmp) => [
|
30
29
|
...wIndex,
|
31
30
|
Opcodes.i32_to,
|
@@ -147,7 +146,6 @@ export const PrototypeFuncs = function() {
|
|
147
146
|
this[TYPES._array].push.noArgRetLength = true;
|
148
147
|
|
149
148
|
this[TYPES.string] = {
|
150
|
-
// todo: out of bounds properly
|
151
149
|
at: (pointer, length, wIndex, iTmp, arrayShell) => {
|
152
150
|
const [ newOut, newPointer ] = arrayShell(1, 'i16');
|
153
151
|
|
@@ -157,9 +155,9 @@ export const PrototypeFuncs = function() {
|
|
157
155
|
[ Opcodes.drop ],
|
158
156
|
|
159
157
|
...number(0, Valtype.i32), // base 0 for store later
|
160
|
-
Opcodes.i32_to_u,
|
161
158
|
|
162
159
|
...wIndex,
|
160
|
+
Opcodes.i32_to_u,
|
163
161
|
[ Opcodes.local_tee, iTmp ],
|
164
162
|
|
165
163
|
// if index < 0: access index + array length
|
@@ -265,7 +263,7 @@ export const PrototypeFuncs = function() {
|
|
265
263
|
},
|
266
264
|
};
|
267
265
|
|
268
|
-
this[TYPES.string].at.local =
|
266
|
+
this[TYPES.string].at.local = Valtype.i32;
|
269
267
|
this[TYPES.string].at.returnType = TYPES.string;
|
270
268
|
this[TYPES.string].charAt.returnType = TYPES.string;
|
271
269
|
this[TYPES.string].charCodeAt.local = Valtype.i32;
|
@@ -0,0 +1,173 @@
|
|
1
|
+
import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from '../wasmSpec.js';
|
2
|
+
import { signedLEB128, unsignedLEB128 } from '../encoding.js';
|
3
|
+
import parse from './parse.js';
|
4
|
+
|
5
|
+
// local indexes
|
6
|
+
const BasePointer = 0; // base string pointer
|
7
|
+
const IterPointer = 1; // this iteration base pointer
|
8
|
+
const Counter = 2; // what char we are running on
|
9
|
+
const Pointer = 3; // next char BYTE pointer
|
10
|
+
const Length = 4;
|
11
|
+
const Tmp = 5;
|
12
|
+
|
13
|
+
const generate = (node, get = true) => {
|
14
|
+
let out = [];
|
15
|
+
switch (node.type) {
|
16
|
+
case 'Expression':
|
17
|
+
out = [
|
18
|
+
// set length local
|
19
|
+
[ Opcodes.local_get, BasePointer ],
|
20
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(0) ],
|
21
|
+
[ Opcodes.local_set, Length ],
|
22
|
+
|
23
|
+
// set iter pointer local as base + sizeof i32 initially
|
24
|
+
[ Opcodes.local_get, BasePointer ],
|
25
|
+
...number(ValtypeSize.i32, Valtype.i32),
|
26
|
+
[ Opcodes.i32_add ],
|
27
|
+
[ Opcodes.local_set, IterPointer ],
|
28
|
+
|
29
|
+
[ Opcodes.loop, Blocktype.void ],
|
30
|
+
|
31
|
+
// reset pointer as iter pointer
|
32
|
+
[ Opcodes.local_get, IterPointer ],
|
33
|
+
[ Opcodes.local_set, Pointer ],
|
34
|
+
|
35
|
+
[ Opcodes.block, Blocktype.void ],
|
36
|
+
// generate checks
|
37
|
+
...generate(x),
|
38
|
+
|
39
|
+
// reached end without branching out, successful match
|
40
|
+
...number(1, Valtype.i32),
|
41
|
+
[ Opcodes.return ],
|
42
|
+
|
43
|
+
[ Opcodes.end ],
|
44
|
+
|
45
|
+
// increment iter pointer by sizeof i16
|
46
|
+
[ Opcodes.local_get, IterPointer ],
|
47
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
48
|
+
[ Opcodes.i32_add ],
|
49
|
+
[ Opcodes.local_set, IterPointer ],
|
50
|
+
|
51
|
+
// increment counter by 1, check if eq length, if not loop
|
52
|
+
[ Opcodes.local_get, Counter ],
|
53
|
+
...number(1, Valtype.i32),
|
54
|
+
[ Opcodes.i32_add ],
|
55
|
+
[ Opcodes.local_tee, Counter ],
|
56
|
+
|
57
|
+
[ Opcodes.local_get, Length ],
|
58
|
+
[ Opcodes.i32_ne ],
|
59
|
+
[ Opcodes.br_if, 1 ],
|
60
|
+
|
61
|
+
[ Opcodes.end ],
|
62
|
+
|
63
|
+
// no match, return 0
|
64
|
+
...number(0, Valtype.i32)
|
65
|
+
];
|
66
|
+
|
67
|
+
break;
|
68
|
+
|
69
|
+
case 'Character':
|
70
|
+
out = generateChar(node, get);
|
71
|
+
break;
|
72
|
+
|
73
|
+
case 'Set':
|
74
|
+
out = generateSet(node, get);
|
75
|
+
break;
|
76
|
+
|
77
|
+
case 'Group':
|
78
|
+
out = generateGroup(node, get);
|
79
|
+
break;
|
80
|
+
|
81
|
+
case 'Range':
|
82
|
+
out = generateRange(node, get);
|
83
|
+
break;
|
84
|
+
}
|
85
|
+
|
86
|
+
return out;
|
87
|
+
};
|
88
|
+
|
89
|
+
const getNextChar = () => [
|
90
|
+
// get char from pointer
|
91
|
+
[ Opcodes.local_get, Pointer ],
|
92
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(0) ],
|
93
|
+
|
94
|
+
// pointer += sizeof i16
|
95
|
+
[ Opcodes.local_get, Pointer ],
|
96
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
97
|
+
[ Opcodes.i32_add ],
|
98
|
+
[ Opcodes.local_set, Pointer ]
|
99
|
+
];
|
100
|
+
|
101
|
+
const checkFailure = () => [
|
102
|
+
// surely we do not need to do this for every single mismatch, right?
|
103
|
+
/* [ Opcodes.if, Blocktype.void ],
|
104
|
+
...number(0, Valtype.i32),
|
105
|
+
[ Opcodes.return ],
|
106
|
+
[ Opcodes.end ], */
|
107
|
+
|
108
|
+
[ Opcodes.br, 1 ]
|
109
|
+
];
|
110
|
+
|
111
|
+
const generateChar = (node, get) => {
|
112
|
+
return [
|
113
|
+
...(get ? getNextChar() : []),
|
114
|
+
...number(String.fromCharCode(node.char), Valtype.i32),
|
115
|
+
[ Opcodes.i32_ne ],
|
116
|
+
...checkFailure()
|
117
|
+
];
|
118
|
+
};
|
119
|
+
|
120
|
+
const generateSet = (node, get) => {
|
121
|
+
const out = [
|
122
|
+
...(get ? getNextChar() : []),
|
123
|
+
[ Opcodes.local_set, Tmp ],
|
124
|
+
];
|
125
|
+
|
126
|
+
for (const x of node.body) {
|
127
|
+
out = [
|
128
|
+
...out,
|
129
|
+
[ Opcodes.local_get, Tmp ],
|
130
|
+
...generate(x, false)
|
131
|
+
];
|
132
|
+
}
|
133
|
+
|
134
|
+
out = out.concat(new Array(node.body.length - 1).fill([ Opcodes.i32_or ]));
|
135
|
+
|
136
|
+
return [
|
137
|
+
...out,
|
138
|
+
...checkFailure()
|
139
|
+
];
|
140
|
+
};
|
141
|
+
|
142
|
+
const generateGroup = (node, get) => {
|
143
|
+
|
144
|
+
};
|
145
|
+
|
146
|
+
const generateRange = (node, get) => {
|
147
|
+
|
148
|
+
};
|
149
|
+
|
150
|
+
|
151
|
+
export const match = regex => {
|
152
|
+
|
153
|
+
};
|
154
|
+
|
155
|
+
export const test = regex => {
|
156
|
+
const code = generate(parse(regex));
|
157
|
+
};
|
158
|
+
|
159
|
+
const wasmify = code => {
|
160
|
+
const funcs = [{
|
161
|
+
name: 'exp',
|
162
|
+
export: true,
|
163
|
+
params: [ Valtype.i32 ],
|
164
|
+
returns: [ Valtype.i32 ],
|
165
|
+
locals: {
|
166
|
+
iterPointer: { idx: 1, type: Valtype.i32 },
|
167
|
+
counter: { idx: 2, type: Valtype.i32 },
|
168
|
+
pointer: { idx: 3, type: Valtype.i32 },
|
169
|
+
length: { idx: 4, type: Valtype.i32 },
|
170
|
+
tmp: { idx: 5, type: Valtype.i32 },
|
171
|
+
}
|
172
|
+
}]
|
173
|
+
};
|
@@ -0,0 +1,125 @@
|
|
1
|
+
const State = {
|
2
|
+
none: 0,
|
3
|
+
insideSet: 1
|
4
|
+
};
|
5
|
+
|
6
|
+
const Quantifiers = {
|
7
|
+
'*': [ 0 ], // 0 -
|
8
|
+
'+': [ 1 ], // 1 -
|
9
|
+
'?': [ 0, 1 ], // 0 - 1
|
10
|
+
};
|
11
|
+
const QuantifierKeys = Object.keys(Quantifiers);
|
12
|
+
|
13
|
+
export default str => {
|
14
|
+
const out = {
|
15
|
+
type: 'Expression',
|
16
|
+
body: []
|
17
|
+
};
|
18
|
+
let node = out, parents = [];
|
19
|
+
|
20
|
+
let state = State.none, escape = false;
|
21
|
+
for (let i = 0; i < str.length; i++) {
|
22
|
+
const c = str[i];
|
23
|
+
|
24
|
+
const addChar = () => {
|
25
|
+
node.body.push({
|
26
|
+
type: 'Character',
|
27
|
+
char: c
|
28
|
+
});
|
29
|
+
};
|
30
|
+
|
31
|
+
const seek = () => {
|
32
|
+
const cNext = str[++i];
|
33
|
+
|
34
|
+
if (cNext === '\\') return str[++i];
|
35
|
+
return cNext;
|
36
|
+
};
|
37
|
+
|
38
|
+
if (escape) {
|
39
|
+
addChar();
|
40
|
+
|
41
|
+
escape = false;
|
42
|
+
continue;
|
43
|
+
}
|
44
|
+
|
45
|
+
if (c === '\\') {
|
46
|
+
escape = true;
|
47
|
+
continue;
|
48
|
+
}
|
49
|
+
|
50
|
+
switch (state) {
|
51
|
+
case State.none:
|
52
|
+
if (c === '[') {
|
53
|
+
parents.push(node);
|
54
|
+
node = {
|
55
|
+
type: 'Set',
|
56
|
+
body: []
|
57
|
+
};
|
58
|
+
|
59
|
+
parents.at(-1).body.push(node);
|
60
|
+
|
61
|
+
state = State.insideSet;
|
62
|
+
continue;
|
63
|
+
}
|
64
|
+
|
65
|
+
if (c === '(') {
|
66
|
+
parents.push(node);
|
67
|
+
node = {
|
68
|
+
type: 'Group',
|
69
|
+
body: []
|
70
|
+
};
|
71
|
+
|
72
|
+
parents.at(-1).body.push(node);
|
73
|
+
continue;
|
74
|
+
}
|
75
|
+
|
76
|
+
if (c === ')') {
|
77
|
+
if (node.type !== 'Group') throw new SyntaxError('Unmatched closing parenthesis');
|
78
|
+
|
79
|
+
node = parents.pop();
|
80
|
+
continue;
|
81
|
+
}
|
82
|
+
|
83
|
+
if (QuantifierKeys.includes(c)) {
|
84
|
+
const amount = Quantifiers[c];
|
85
|
+
node.body.at(-1).quantifier = amount;
|
86
|
+
continue;
|
87
|
+
}
|
88
|
+
|
89
|
+
addChar();
|
90
|
+
break;
|
91
|
+
|
92
|
+
case State.insideSet:
|
93
|
+
if (c === ']') {
|
94
|
+
state = State.none;
|
95
|
+
node = parents.pop();
|
96
|
+
|
97
|
+
continue;
|
98
|
+
}
|
99
|
+
|
100
|
+
// range
|
101
|
+
if (c === '-') {
|
102
|
+
const from = node.body.pop().char;
|
103
|
+
const to = seek();
|
104
|
+
|
105
|
+
node.body.push({
|
106
|
+
type: 'Range',
|
107
|
+
from,
|
108
|
+
to
|
109
|
+
});
|
110
|
+
continue;
|
111
|
+
}
|
112
|
+
|
113
|
+
addChar();
|
114
|
+
break;
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
118
|
+
// still in a group by the end
|
119
|
+
if (node.type !== 'Expression') throw new SyntaxError('Unmatched opening parenthesis');
|
120
|
+
|
121
|
+
// still in a set by the end
|
122
|
+
if (state === State.insideSet) throw new SyntaxError('Unmatched opening square bracket');
|
123
|
+
|
124
|
+
return out;
|
125
|
+
};
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import util from 'node:util';
|
2
|
+
|
3
|
+
import parse from '../parse.js';
|
4
|
+
|
5
|
+
const tests = {
|
6
|
+
'a': {},
|
7
|
+
'a(b)': {},
|
8
|
+
'a(b(c))': {},
|
9
|
+
'ab': {},
|
10
|
+
'[ab]': {},
|
11
|
+
'[a-z]': {},
|
12
|
+
'a*': {},
|
13
|
+
'a+': {},
|
14
|
+
'a?': {},
|
15
|
+
'a(b)+': {}
|
16
|
+
}
|
17
|
+
|
18
|
+
for (const str in tests) {
|
19
|
+
console.log(str, util.inspect(parse(str), false, null, true));
|
20
|
+
}
|
package/compiler/sections.js
CHANGED
@@ -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]}`);
|
@@ -36,7 +51,7 @@ export default (funcs, globals, tags, pages, flags) => {
|
|
36
51
|
// tree shake imports
|
37
52
|
for (const f of funcs) {
|
38
53
|
for (const inst of f.wasm) {
|
39
|
-
if (inst[0] === Opcodes.call && inst[1] < importedFuncs.length) {
|
54
|
+
if ((inst[0] === Opcodes.call || inst[0] === Opcodes.return_call) && inst[1] < importedFuncs.length) {
|
40
55
|
const idx = inst[1];
|
41
56
|
const func = importedFuncs[idx];
|
42
57
|
|
@@ -51,10 +66,11 @@ export default (funcs, globals, tags, pages, flags) => {
|
|
51
66
|
// fix call indexes for non-imports
|
52
67
|
const delta = importedFuncs.length - importFuncs.length;
|
53
68
|
for (const f of funcs) {
|
69
|
+
f.originalIndex = f.index;
|
54
70
|
f.index -= delta;
|
55
71
|
|
56
72
|
for (const inst of f.wasm) {
|
57
|
-
if (inst[0] === Opcodes.call && inst[1] >= importedFuncs.length) {
|
73
|
+
if ((inst[0] === Opcodes.call || inst[0] === Opcodes.return_call) && inst[1] >= importedFuncs.length) {
|
58
74
|
inst[1] -= delta;
|
59
75
|
}
|
60
76
|
}
|
@@ -73,6 +89,14 @@ export default (funcs, globals, tags, pages, flags) => {
|
|
73
89
|
encodeVector(funcs.map(x => getType(x.params, x.returns))) // type indexes
|
74
90
|
);
|
75
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
|
+
|
76
100
|
const globalSection = Object.keys(globals).length === 0 ? [] : createSection(
|
77
101
|
Section.global,
|
78
102
|
encodeVector(Object.keys(globals).map(x => [ globals[x].type, 0x01, ...number(globals[x].init ?? 0, globals[x].type).flat(), Opcodes.end ]))
|
@@ -145,6 +169,7 @@ export default (funcs, globals, tags, pages, flags) => {
|
|
145
169
|
...typeSection,
|
146
170
|
...importSection,
|
147
171
|
...funcSection,
|
172
|
+
...chSection,
|
148
173
|
...memorySection,
|
149
174
|
...tagSection,
|
150
175
|
...globalSection,
|
package/compiler/wrap.js
CHANGED
@@ -90,6 +90,15 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
|
|
90
90
|
return Array.from(new Uint16Array(memory.buffer, pointer + 4, length)).map(x => String.fromCharCode(x)).join('');
|
91
91
|
}
|
92
92
|
|
93
|
+
case 'function': {
|
94
|
+
// wasm func index, including all imports
|
95
|
+
const func = funcs.find(x => (x.originalIndex ?? x.index) === ret);
|
96
|
+
if (!func) return ret;
|
97
|
+
|
98
|
+
// make fake empty func for repl/etc
|
99
|
+
return {[func.name]() {}}[func.name];
|
100
|
+
}
|
101
|
+
|
93
102
|
default: return ret;
|
94
103
|
}
|
95
104
|
} catch (e) {
|
package/package.json
CHANGED
package/r.js
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
/a(b)/.test('hi');
|
package/runner/index.js
CHANGED
@@ -3,8 +3,26 @@
|
|
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) {
|
20
|
+
if (process.argv.includes('-v')) {
|
21
|
+
// just print version
|
22
|
+
console.log((await import('./version.js')).default);
|
23
|
+
process.exit(0);
|
24
|
+
}
|
25
|
+
|
8
26
|
// run repl if no file given
|
9
27
|
await import('./repl.js');
|
10
28
|
|
package/runner/repl.js
CHANGED
@@ -1,14 +1,7 @@
|
|
1
1
|
import compile from '../compiler/wrap.js';
|
2
|
+
import rev from './version.js';
|
2
3
|
|
3
4
|
import repl from 'node:repl';
|
4
|
-
import fs from 'node:fs';
|
5
|
-
|
6
|
-
let rev = 'unknown';
|
7
|
-
try {
|
8
|
-
rev = fs.readFileSync(new URL('../.git/refs/heads/main', import.meta.url), 'utf8').trim().slice(0, 7);
|
9
|
-
} catch {
|
10
|
-
rev = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version.split('-')[1];
|
11
|
-
}
|
12
5
|
|
13
6
|
// process.argv.push('-O0'); // disable opts
|
14
7
|
|
@@ -48,7 +41,7 @@ const memoryToString = mem => {
|
|
48
41
|
return out;
|
49
42
|
};
|
50
43
|
|
51
|
-
const alwaysPrev = process.argv.includes('-
|
44
|
+
const alwaysPrev = process.argv.includes('-prev');
|
52
45
|
|
53
46
|
let prev = '';
|
54
47
|
const run = async (source, _context, _filename, callback, run = true) => {
|
@@ -56,7 +49,7 @@ const run = async (source, _context, _filename, callback, run = true) => {
|
|
56
49
|
if (alwaysPrev) prev = toRun + ';\n';
|
57
50
|
|
58
51
|
const { exports, wasm, pages } = await compile(toRun, []);
|
59
|
-
fs.writeFileSync('out.wasm', Buffer.from(wasm));
|
52
|
+
// fs.writeFileSync('out.wasm', Buffer.from(wasm));
|
60
53
|
|
61
54
|
if (run && exports.$) {
|
62
55
|
lastMemory = exports.$;
|
package/runner/transform.js
CHANGED
@@ -8,7 +8,8 @@ const source = fs.readFileSync(file, 'utf8');
|
|
8
8
|
const { wasm } = await compile(source);
|
9
9
|
|
10
10
|
// const out = `(async () => { const print = str => process.stdout.write(str); (await WebAssembly.instantiate(Uint8Array.from([${wasm.toString()}]), {'': { p: i => print(i.toString()), c: i => print(String.fromCharCode(i))}})).instance.exports.m()})()`;
|
11
|
-
const out = `new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array([${wasm.toString()}])),{'':{p:i=>process.stdout.write(i.toString())}}).exports.m()`;
|
11
|
+
// const out = `new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array([${wasm.toString()}])),{'':{p:i=>process.stdout.write(i.toString())}}).exports.m()`;
|
12
|
+
const out = `const a=new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array([${wasm.toString()}])));const b=a.exports.m();console.log(Array.from(new Uint16Array(a.exports.$.buffer,b+4,new Int32Array(a.exports.$.buffer,b,1))).map(x=>String.fromCharCode(x)).join(''))`;
|
12
13
|
|
13
14
|
console.log(out);
|
14
15
|
eval(out);
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import fs from 'node:fs';
|
2
|
+
|
3
|
+
let rev = 'unknown';
|
4
|
+
try {
|
5
|
+
rev = fs.readFileSync(new URL('../.git/refs/heads/main', import.meta.url), 'utf8').trim().slice(0, 7);
|
6
|
+
} catch {
|
7
|
+
rev = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version.split('-')[1].slice(0, 7);
|
8
|
+
}
|
9
|
+
|
10
|
+
export default rev;
|