porffor 0.0.0-1989c22 → 0.0.0-425ea20
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 +17 -3
- package/compiler/codeGen.js +133 -63
- 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/sections.js +3 -2
- package/compiler/wrap.js +9 -0
- package/package.json +1 -1
- package/runner/index.js +6 -0
- package/runner/repl.js +6 -11
- package/runner/version.js +10 -0
package/README.md
CHANGED
@@ -76,6 +76,8 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
76
76
|
- string member (char) access via `str[ind]` (eg `str[0]`)
|
77
77
|
- string concat (`+`) (eg `'a' + 'b'`)
|
78
78
|
- truthy/falsy (eg `!'' == true`)
|
79
|
+
- string comparison (eg `'a' == 'a'`, `'a' != 'b'`)
|
80
|
+
- nullish coalescing operator (`??`)
|
79
81
|
|
80
82
|
### built-ins
|
81
83
|
|
@@ -99,18 +101,29 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
99
101
|
- intrinsic functions (see below)
|
100
102
|
- inlining wasm via ``asm`...``\` "macro"
|
101
103
|
|
102
|
-
##
|
104
|
+
## todo
|
105
|
+
no particular order and no guarentees, just what could happen soon™
|
106
|
+
|
103
107
|
- arrays
|
104
108
|
- member setting (`arr[0] = 2`)
|
105
109
|
- more of `Array` prototype
|
106
110
|
- arrays/strings inside arrays
|
111
|
+
- destructuring
|
112
|
+
- for .. of
|
107
113
|
- strings
|
108
114
|
- member setting
|
109
|
-
|
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
|
@@ -135,6 +148,7 @@ mostly for reducing size. do not really care about compiler perf/time as long as
|
|
135
148
|
- `i64.extend_i32_s`, `i32.wrap_i64` -> ``
|
136
149
|
- `f64.convert_i32_u`, `i32.trunc_sat_f64_s` -> ``
|
137
150
|
- `return`, `end` -> `end`
|
151
|
+
- change const, convert to const of converted valtype (eg `f64.const`, `i32.trunc_sat_f64_s -> `i32.const`)
|
138
152
|
- remove some redundant sets/gets
|
139
153
|
- remove unneeded single just used vars
|
140
154
|
- remove unneeded blocks (no `br`s inside)
|
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
|
|
@@ -101,6 +108,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
101
108
|
case 'WhileStatement':
|
102
109
|
return generateWhile(scope, decl);
|
103
110
|
|
111
|
+
/* case 'ForOfStatement':
|
112
|
+
return generateForOf(scope, decl); */
|
113
|
+
|
104
114
|
case 'BreakStatement':
|
105
115
|
return generateBreak(scope, decl);
|
106
116
|
|
@@ -164,7 +174,6 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
164
174
|
}
|
165
175
|
|
166
176
|
if (asm[0] === 'memory') {
|
167
|
-
scope.memory = true;
|
168
177
|
allocPage('asm instrinsic');
|
169
178
|
// todo: add to store/load offset insts
|
170
179
|
continue;
|
@@ -278,7 +287,7 @@ const generateReturn = (scope, decl) => {
|
|
278
287
|
];
|
279
288
|
}
|
280
289
|
|
281
|
-
|
290
|
+
scope.returnType = getNodeType(scope, decl.argument);
|
282
291
|
|
283
292
|
return [
|
284
293
|
...generate(scope, decl.argument),
|
@@ -295,11 +304,11 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
295
304
|
return idx;
|
296
305
|
};
|
297
306
|
|
298
|
-
const performLogicOp = (scope, op, left, right) => {
|
307
|
+
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
299
308
|
const checks = {
|
300
|
-
'||':
|
301
|
-
'&&':
|
302
|
-
|
309
|
+
'||': falsy,
|
310
|
+
'&&': truthy,
|
311
|
+
'??': nullish
|
303
312
|
};
|
304
313
|
|
305
314
|
if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
|
@@ -310,7 +319,8 @@ const performLogicOp = (scope, op, left, right) => {
|
|
310
319
|
return [
|
311
320
|
...left,
|
312
321
|
[ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
|
313
|
-
...checks[op],
|
322
|
+
...checks[op](scope, [], leftType),
|
323
|
+
Opcodes.i32_to,
|
314
324
|
[ Opcodes.if, valtypeBinary ],
|
315
325
|
...right,
|
316
326
|
[ Opcodes.else ],
|
@@ -325,8 +335,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
325
335
|
// todo: optimize by looking up names in arrays and using that if exists?
|
326
336
|
// todo: optimize this if using literals/known lengths?
|
327
337
|
|
328
|
-
scope.memory = true;
|
329
|
-
|
330
338
|
const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
|
331
339
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
332
340
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
@@ -461,8 +469,6 @@ const compareStrings = (scope, left, right) => {
|
|
461
469
|
// todo: optimize by looking up names in arrays and using that if exists?
|
462
470
|
// todo: optimize this if using literals/known lengths?
|
463
471
|
|
464
|
-
scope.memory = true;
|
465
|
-
|
466
472
|
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
467
473
|
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
468
474
|
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
@@ -472,9 +478,6 @@ const compareStrings = (scope, left, right) => {
|
|
472
478
|
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
473
479
|
|
474
480
|
return [
|
475
|
-
// use block to "return" a value early
|
476
|
-
[ Opcodes.block, Valtype.i32 ],
|
477
|
-
|
478
481
|
// setup left
|
479
482
|
...left,
|
480
483
|
Opcodes.i32_to_u,
|
@@ -486,11 +489,9 @@ const compareStrings = (scope, left, right) => {
|
|
486
489
|
[ Opcodes.local_tee, rightPointer ],
|
487
490
|
|
488
491
|
// fast path: check leftPointer == rightPointer
|
489
|
-
|
490
|
-
[ Opcodes.
|
491
|
-
|
492
|
-
[ Opcodes.br, 1 ],
|
493
|
-
[ Opcodes.end ],
|
492
|
+
// use if (block) for everything after to "return" a value early
|
493
|
+
[ Opcodes.i32_ne ],
|
494
|
+
[ Opcodes.if, Valtype.i32 ],
|
494
495
|
|
495
496
|
// get lengths
|
496
497
|
[ Opcodes.local_get, leftPointer ],
|
@@ -553,6 +554,10 @@ const compareStrings = (scope, left, right) => {
|
|
553
554
|
|
554
555
|
// no failed checks, so true!
|
555
556
|
...number(1, Valtype.i32),
|
557
|
+
|
558
|
+
// pointers match, so true
|
559
|
+
[ Opcodes.else ],
|
560
|
+
...number(1, Valtype.i32),
|
556
561
|
[ Opcodes.end ],
|
557
562
|
|
558
563
|
// convert i32 result to valtype
|
@@ -561,75 +566,100 @@ const compareStrings = (scope, left, right) => {
|
|
561
566
|
];
|
562
567
|
};
|
563
568
|
|
564
|
-
const
|
569
|
+
const truthy = (scope, wasm, type) => {
|
565
570
|
// arrays are always truthy
|
566
571
|
if (type === TYPES._array) return [
|
567
572
|
...wasm,
|
568
573
|
[ Opcodes.drop ],
|
569
|
-
number(
|
574
|
+
...number(1)
|
570
575
|
];
|
571
576
|
|
572
577
|
if (type === TYPES.string) {
|
573
|
-
// if "" (length = 0)
|
578
|
+
// if not "" (length = 0)
|
574
579
|
return [
|
575
580
|
// pointer
|
576
581
|
...wasm,
|
582
|
+
Opcodes.i32_to_u,
|
577
583
|
|
578
584
|
// get length
|
579
585
|
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
580
586
|
|
581
|
-
// if length
|
582
|
-
[ Opcodes.i32_eqz ],
|
587
|
+
// if length != 0
|
588
|
+
/* [ Opcodes.i32_eqz ],
|
589
|
+
[ Opcodes.i32_eqz ], */
|
583
590
|
Opcodes.i32_from_u
|
584
591
|
]
|
585
592
|
}
|
586
593
|
|
587
|
-
// if
|
594
|
+
// if != 0
|
588
595
|
return [
|
589
596
|
...wasm,
|
590
597
|
|
591
|
-
|
592
|
-
Opcodes.
|
598
|
+
/* Opcodes.eqz,
|
599
|
+
[ Opcodes.i32_eqz ],
|
600
|
+
Opcodes.i32_from */
|
593
601
|
];
|
594
602
|
};
|
595
603
|
|
596
|
-
const
|
604
|
+
const falsy = (scope, wasm, type) => {
|
597
605
|
// arrays are always truthy
|
598
606
|
if (type === TYPES._array) return [
|
599
607
|
...wasm,
|
600
608
|
[ Opcodes.drop ],
|
601
|
-
number(
|
609
|
+
...number(0)
|
602
610
|
];
|
603
611
|
|
604
612
|
if (type === TYPES.string) {
|
605
|
-
// if
|
613
|
+
// if "" (length = 0)
|
606
614
|
return [
|
607
615
|
// pointer
|
608
616
|
...wasm,
|
617
|
+
Opcodes.i32_to_u,
|
609
618
|
|
610
619
|
// get length
|
611
620
|
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
612
621
|
|
613
|
-
// if length
|
614
|
-
|
615
|
-
[ Opcodes.i32_eqz ], */
|
622
|
+
// if length == 0
|
623
|
+
[ Opcodes.i32_eqz ],
|
616
624
|
Opcodes.i32_from_u
|
617
625
|
]
|
618
626
|
}
|
619
627
|
|
620
|
-
// if
|
628
|
+
// if = 0
|
621
629
|
return [
|
622
630
|
...wasm,
|
623
631
|
|
624
|
-
|
625
|
-
|
626
|
-
|
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)
|
627
657
|
];
|
628
658
|
};
|
629
659
|
|
630
660
|
const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
|
631
661
|
if (op === '||' || op === '&&' || op === '??') {
|
632
|
-
return performLogicOp(scope, op, left, right);
|
662
|
+
return performLogicOp(scope, op, left, right, leftType, rightType);
|
633
663
|
}
|
634
664
|
|
635
665
|
if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
|
@@ -770,7 +800,7 @@ const includeBuiltin = (scope, builtin) => {
|
|
770
800
|
};
|
771
801
|
|
772
802
|
const generateLogicExp = (scope, decl) => {
|
773
|
-
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));
|
774
804
|
};
|
775
805
|
|
776
806
|
const TYPES = {
|
@@ -934,7 +964,8 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
934
964
|
const countLeftover = wasm => {
|
935
965
|
let count = 0, depth = 0;
|
936
966
|
|
937
|
-
for (
|
967
|
+
for (let i = 0; i < wasm.length; i++) {
|
968
|
+
const inst = wasm[i];
|
938
969
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
939
970
|
if (inst[0] === Opcodes.if) count--;
|
940
971
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1038,7 +1069,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1038
1069
|
}
|
1039
1070
|
|
1040
1071
|
let out = [];
|
1041
|
-
let protoFunc, protoName, baseType, baseName
|
1072
|
+
let protoFunc, protoName, baseType, baseName;
|
1042
1073
|
// ident.func()
|
1043
1074
|
if (name && name.startsWith('__')) {
|
1044
1075
|
const spl = name.slice(2).split('_');
|
@@ -1061,11 +1092,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1061
1092
|
|
1062
1093
|
out = generate(scope, decl.callee.object);
|
1063
1094
|
out.push([ Opcodes.drop ]);
|
1095
|
+
|
1096
|
+
baseName = [...arrays.keys()].pop();
|
1064
1097
|
}
|
1065
1098
|
|
1066
1099
|
if (protoFunc) {
|
1067
|
-
scope.memory = true;
|
1068
|
-
|
1069
1100
|
let pointer = arrays.get(baseName);
|
1070
1101
|
|
1071
1102
|
if (pointer == null) {
|
@@ -1073,7 +1104,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1073
1104
|
if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
|
1074
1105
|
|
1075
1106
|
// register array
|
1076
|
-
|
1107
|
+
0, [ , pointer ] = makeArray(scope, {
|
1077
1108
|
rawElements: new Array(0)
|
1078
1109
|
}, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
|
1079
1110
|
|
@@ -1171,7 +1202,6 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1171
1202
|
args = args.slice(0, func.params.length);
|
1172
1203
|
}
|
1173
1204
|
|
1174
|
-
if (func && func.memory) scope.memory = true;
|
1175
1205
|
if (func && func.throws) scope.throws = true;
|
1176
1206
|
|
1177
1207
|
for (const arg of args) {
|
@@ -1187,7 +1217,7 @@ const generateNew = (scope, decl, _global, _name) => {
|
|
1187
1217
|
// hack: basically treat this as a normal call for builtins for now
|
1188
1218
|
const name = mapName(decl.callee.name);
|
1189
1219
|
if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1190
|
-
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)})`);
|
1191
1221
|
|
1192
1222
|
return generateCall(scope, decl, _global, _name);
|
1193
1223
|
};
|
@@ -1295,8 +1325,6 @@ const generateAssign = (scope, decl) => {
|
|
1295
1325
|
const name = decl.left.object.name;
|
1296
1326
|
const pointer = arrays.get(name);
|
1297
1327
|
|
1298
|
-
scope.memory = true;
|
1299
|
-
|
1300
1328
|
const aotPointer = pointer != null;
|
1301
1329
|
|
1302
1330
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
@@ -1347,8 +1375,27 @@ const generateAssign = (scope, decl) => {
|
|
1347
1375
|
];
|
1348
1376
|
}
|
1349
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
|
+
|
1350
1397
|
return [
|
1351
|
-
...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),
|
1352
1399
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1353
1400
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1354
1401
|
];
|
@@ -1375,13 +1422,14 @@ const generateUnary = (scope, decl) => {
|
|
1375
1422
|
|
1376
1423
|
case '!':
|
1377
1424
|
// !=
|
1378
|
-
return falsy(scope, generate(scope, decl.argument));
|
1425
|
+
return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
|
1379
1426
|
|
1380
1427
|
case '~':
|
1428
|
+
// todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
|
1381
1429
|
return [
|
1382
1430
|
...generate(scope, decl.argument),
|
1383
1431
|
Opcodes.i32_to,
|
1384
|
-
[ Opcodes.i32_const, signedLEB128(-1) ],
|
1432
|
+
[ Opcodes.i32_const, ...signedLEB128(-1) ],
|
1385
1433
|
[ Opcodes.i32_xor ],
|
1386
1434
|
Opcodes.i32_from
|
1387
1435
|
];
|
@@ -1552,6 +1600,25 @@ const generateWhile = (scope, decl) => {
|
|
1552
1600
|
return out;
|
1553
1601
|
};
|
1554
1602
|
|
1603
|
+
const generateForOf = (scope, decl) => {
|
1604
|
+
const out = [];
|
1605
|
+
|
1606
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
1607
|
+
depth.push('while');
|
1608
|
+
|
1609
|
+
out.push(...generate(scope, decl.test));
|
1610
|
+
out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
|
1611
|
+
depth.push('if');
|
1612
|
+
|
1613
|
+
out.push(...generate(scope, decl.body));
|
1614
|
+
|
1615
|
+
out.push([ Opcodes.br, 1 ]);
|
1616
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
1617
|
+
depth.pop(); depth.pop();
|
1618
|
+
|
1619
|
+
return out;
|
1620
|
+
};
|
1621
|
+
|
1555
1622
|
const getNearestLoop = () => {
|
1556
1623
|
for (let i = depth.length - 1; i >= 0; i--) {
|
1557
1624
|
if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
|
@@ -1644,7 +1711,16 @@ const allocPage = reason => {
|
|
1644
1711
|
let ind = pages.size;
|
1645
1712
|
pages.set(reason, ind);
|
1646
1713
|
|
1647
|
-
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}`);
|
1648
1724
|
|
1649
1725
|
return ind;
|
1650
1726
|
};
|
@@ -1706,8 +1782,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1706
1782
|
// local value as pointer
|
1707
1783
|
out.push(...number(pointer));
|
1708
1784
|
|
1709
|
-
scope.memory = true;
|
1710
|
-
|
1711
1785
|
return [ out, pointer ];
|
1712
1786
|
};
|
1713
1787
|
|
@@ -1726,8 +1800,6 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1726
1800
|
const name = decl.object.name;
|
1727
1801
|
const pointer = arrays.get(name);
|
1728
1802
|
|
1729
|
-
scope.memory = true;
|
1730
|
-
|
1731
1803
|
const aotPointer = pointer != null;
|
1732
1804
|
|
1733
1805
|
return [
|
@@ -1747,8 +1819,6 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1747
1819
|
const name = decl.object.name;
|
1748
1820
|
const pointer = arrays.get(name);
|
1749
1821
|
|
1750
|
-
scope.memory = true;
|
1751
|
-
|
1752
1822
|
const aotPointer = pointer != null;
|
1753
1823
|
|
1754
1824
|
if (type === TYPES._array) {
|
@@ -1858,6 +1928,7 @@ const generateFunc = (scope, decl) => {
|
|
1858
1928
|
locals: {},
|
1859
1929
|
localInd: 0,
|
1860
1930
|
returns: [ valtypeBinary ],
|
1931
|
+
returnType: null,
|
1861
1932
|
memory: false,
|
1862
1933
|
throws: false,
|
1863
1934
|
name
|
@@ -1884,7 +1955,6 @@ const generateFunc = (scope, decl) => {
|
|
1884
1955
|
returns: innerScope.returns,
|
1885
1956
|
returnType: innerScope.returnType,
|
1886
1957
|
locals: innerScope.locals,
|
1887
|
-
memory: innerScope.memory,
|
1888
1958
|
throws: innerScope.throws,
|
1889
1959
|
index: currentFuncIndex++
|
1890
1960
|
};
|
@@ -1899,6 +1969,8 @@ const generateFunc = (scope, decl) => {
|
|
1899
1969
|
|
1900
1970
|
if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
|
1901
1971
|
wasm.push(...number(0), [ Opcodes.return ]);
|
1972
|
+
|
1973
|
+
if (func.returnType === null) func.returnType = TYPES.undefined;
|
1902
1974
|
}
|
1903
1975
|
|
1904
1976
|
// change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
|
@@ -1909,9 +1981,7 @@ const generateFunc = (scope, decl) => {
|
|
1909
1981
|
if (local.type === Valtype.v128) {
|
1910
1982
|
vecParams++;
|
1911
1983
|
|
1912
|
-
/*
|
1913
|
-
|
1914
|
-
wasm.unshift( // add v128 load for param
|
1984
|
+
/* wasm.unshift( // add v128 load for param
|
1915
1985
|
[ Opcodes.i32_const, 0 ],
|
1916
1986
|
[ ...Opcodes.v128_load, 0, i * 16 ],
|
1917
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;
|
package/compiler/sections.js
CHANGED
@@ -36,7 +36,7 @@ export default (funcs, globals, tags, pages, flags) => {
|
|
36
36
|
// tree shake imports
|
37
37
|
for (const f of funcs) {
|
38
38
|
for (const inst of f.wasm) {
|
39
|
-
if (inst[0] === Opcodes.call && inst[1] < importedFuncs.length) {
|
39
|
+
if ((inst[0] === Opcodes.call || inst[0] === Opcodes.return_call) && inst[1] < importedFuncs.length) {
|
40
40
|
const idx = inst[1];
|
41
41
|
const func = importedFuncs[idx];
|
42
42
|
|
@@ -51,10 +51,11 @@ export default (funcs, globals, tags, pages, flags) => {
|
|
51
51
|
// fix call indexes for non-imports
|
52
52
|
const delta = importedFuncs.length - importFuncs.length;
|
53
53
|
for (const f of funcs) {
|
54
|
+
f.originalIndex = f.index;
|
54
55
|
f.index -= delta;
|
55
56
|
|
56
57
|
for (const inst of f.wasm) {
|
57
|
-
if (inst[0] === Opcodes.call && inst[1] >= importedFuncs.length) {
|
58
|
+
if ((inst[0] === Opcodes.call || inst[0] === Opcodes.return_call) && inst[1] >= importedFuncs.length) {
|
58
59
|
inst[1] -= delta;
|
59
60
|
}
|
60
61
|
}
|
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/runner/index.js
CHANGED
@@ -5,6 +5,12 @@ import fs from 'node:fs';
|
|
5
5
|
|
6
6
|
const file = process.argv.slice(2).find(x => x[0] !== '-');
|
7
7
|
if (!file) {
|
8
|
+
if (process.argv.includes('-v')) {
|
9
|
+
// just print version
|
10
|
+
console.log((await import('./version.js')).default);
|
11
|
+
process.exit(0);
|
12
|
+
}
|
13
|
+
|
8
14
|
// run repl if no file given
|
9
15
|
await import('./repl.js');
|
10
16
|
|
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,13 +41,15 @@ const memoryToString = mem => {
|
|
48
41
|
return out;
|
49
42
|
};
|
50
43
|
|
44
|
+
const alwaysPrev = process.argv.includes('-prev');
|
45
|
+
|
51
46
|
let prev = '';
|
52
47
|
const run = async (source, _context, _filename, callback, run = true) => {
|
53
48
|
let toRun = prev + source.trim();
|
54
|
-
|
49
|
+
if (alwaysPrev) prev = toRun + ';\n';
|
55
50
|
|
56
51
|
const { exports, wasm, pages } = await compile(toRun, []);
|
57
|
-
fs.writeFileSync('out.wasm', Buffer.from(wasm));
|
52
|
+
// fs.writeFileSync('out.wasm', Buffer.from(wasm));
|
58
53
|
|
59
54
|
if (run && exports.$) {
|
60
55
|
lastMemory = exports.$;
|
@@ -64,7 +59,7 @@ const run = async (source, _context, _filename, callback, run = true) => {
|
|
64
59
|
const ret = run ? exports.main() : undefined;
|
65
60
|
callback(null, ret);
|
66
61
|
|
67
|
-
if (source.includes(' = ') || source.includes('let ') || source.includes('var ') || source.includes('const ') || source.includes('function ')) prev = toRun + ';\n';
|
62
|
+
if (!alwaysPrev && (source.includes(' = ') || source.includes('let ') || source.includes('var ') || source.includes('const ') || source.includes('function '))) prev = toRun + ';\n';
|
68
63
|
// prev = toRun + ';\n';
|
69
64
|
};
|
70
65
|
|
@@ -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;
|