porffor 0.0.0-05f898f → 0.0.0-1b0a5c6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/compiler/builtins.js +6 -0
- package/compiler/codeGen.js +255 -77
- package/compiler/index.js +1 -1
- package/compiler/opt.js +14 -18
- package/compiler/prototype.js +90 -28
- package/compiler/wrap.js +7 -0
- package/package.json +1 -1
- package/r.js +39 -1
- package/rhemyn/compile.js +1 -1
- package/runner/index.js +5 -3
- package/runner/info.js +37 -2
package/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# porffor
|
2
2
|
a basic experimental wip *aot* optimizing js -> wasm/c engine/compiler/runtime in js. not serious/intended for (real) use. (this is a straight forward, honest readme)<br>
|
3
|
-
age: ~
|
3
|
+
age: ~2 months
|
4
4
|
|
5
5
|
## design
|
6
6
|
porffor is a very unique js engine, due a very different approach. it is seriously limited, but what it can do, it does pretty well. key differences:
|
@@ -83,6 +83,8 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
83
83
|
- truthy/falsy (eg `!'' == true`)
|
84
84
|
- string comparison (eg `'a' == 'a'`, `'a' != 'b'`)
|
85
85
|
- nullish coalescing operator (`??`)
|
86
|
+
- `for...of` (arrays and strings)
|
87
|
+
- array member setting (`arr[0] = 2`, `arr[0] += 2`, etc)
|
86
88
|
|
87
89
|
### built-ins
|
88
90
|
|
@@ -110,11 +112,9 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
110
112
|
no particular order and no guarentees, just what could happen soon™
|
111
113
|
|
112
114
|
- arrays
|
113
|
-
- member setting (`arr[0] = 2`)
|
114
115
|
- more of `Array` prototype
|
115
116
|
- arrays/strings inside arrays
|
116
117
|
- destructuring
|
117
|
-
- for .. of
|
118
118
|
- strings
|
119
119
|
- member setting
|
120
120
|
- objects
|
@@ -135,7 +135,7 @@ no particular order and no guarentees, just what could happen soon™
|
|
135
135
|
- use data segments for initing arrays
|
136
136
|
|
137
137
|
## porfformance
|
138
|
-
*for the things it supports*, porffor is blazingly
|
138
|
+
*for the things it supports most of the time*, porffor is blazingly fast compared to most interpreters, and common engines running without JIT. for those with JIT, it is not that much slower like a traditional interpreter would be; mostly the same or a bit faster/slower depending on what.
|
139
139
|
|
140
140
|

|
141
141
|
|
package/compiler/builtins.js
CHANGED
package/compiler/codeGen.js
CHANGED
@@ -109,8 +109,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
109
109
|
case 'WhileStatement':
|
110
110
|
return generateWhile(scope, decl);
|
111
111
|
|
112
|
-
|
113
|
-
return generateForOf(scope, decl);
|
112
|
+
case 'ForOfStatement':
|
113
|
+
return generateForOf(scope, decl);
|
114
114
|
|
115
115
|
case 'BreakStatement':
|
116
116
|
return generateBreak(scope, decl);
|
@@ -152,44 +152,65 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
152
152
|
|
153
153
|
return [];
|
154
154
|
|
155
|
-
case 'TaggedTemplateExpression':
|
156
|
-
|
157
|
-
|
155
|
+
case 'TaggedTemplateExpression': {
|
156
|
+
const funcs = {
|
157
|
+
asm: str => {
|
158
|
+
let out = [];
|
158
159
|
|
159
|
-
|
160
|
-
|
160
|
+
for (const line of str.split('\n')) {
|
161
|
+
const asm = line.trim().split(';;')[0].split(' ');
|
162
|
+
if (asm[0] === '') continue; // blank
|
161
163
|
|
162
|
-
|
163
|
-
|
164
|
-
|
164
|
+
if (asm[0] === 'local') {
|
165
|
+
const [ name, idx, type ] = asm.slice(1);
|
166
|
+
scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
|
167
|
+
continue;
|
168
|
+
}
|
165
169
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
}
|
170
|
+
if (asm[0] === 'returns') {
|
171
|
+
scope.returns = asm.slice(1).map(x => Valtype[x]);
|
172
|
+
continue;
|
173
|
+
}
|
171
174
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
175
|
+
if (asm[0] === 'memory') {
|
176
|
+
allocPage('asm instrinsic');
|
177
|
+
// todo: add to store/load offset insts
|
178
|
+
continue;
|
179
|
+
}
|
176
180
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
181
|
+
let inst = Opcodes[asm[0].replace('.', '_')];
|
182
|
+
if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
183
|
+
|
184
|
+
if (!Array.isArray(inst)) inst = [ inst ];
|
185
|
+
const immediates = asm.slice(1).map(x => parseInt(x));
|
186
|
+
|
187
|
+
out.push([ ...inst, ...immediates ]);
|
188
|
+
}
|
182
189
|
|
183
|
-
|
184
|
-
|
190
|
+
return out;
|
191
|
+
},
|
185
192
|
|
186
|
-
|
187
|
-
|
193
|
+
__internal_print_type: str => {
|
194
|
+
const type = getType(scope, str) - TYPES.number;
|
188
195
|
|
189
|
-
|
196
|
+
return [
|
197
|
+
...number(type),
|
198
|
+
[ Opcodes.call, importedFuncs.print ],
|
199
|
+
|
200
|
+
// newline
|
201
|
+
...number(10),
|
202
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
203
|
+
];
|
204
|
+
}
|
190
205
|
}
|
191
206
|
|
192
|
-
|
207
|
+
const name = decl.tag.name;
|
208
|
+
// hack for inline asm
|
209
|
+
if (!funcs[name]) return todo('tagged template expressions not implemented');
|
210
|
+
|
211
|
+
const str = decl.quasi.quasis[0].value.raw;
|
212
|
+
return funcs[name](str);
|
213
|
+
}
|
193
214
|
|
194
215
|
default:
|
195
216
|
return todo(`no generation for ${decl.type}!`);
|
@@ -665,29 +686,44 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
665
686
|
|
666
687
|
if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
|
667
688
|
|
668
|
-
|
669
|
-
|
689
|
+
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
690
|
+
|
691
|
+
if (leftType && rightType && (
|
692
|
+
// if strict (in)equal and known types mismatch, return false (===)/true (!==)
|
693
|
+
((op === '===' || op === '!==') && leftType !== rightType) ||
|
694
|
+
|
695
|
+
// if equality op and an operand is undefined, return false
|
696
|
+
(eqOp && leftType === TYPES.undefined ^ rightType === TYPES.undefined)
|
697
|
+
)) {
|
698
|
+
// undefined == null
|
699
|
+
if (((leftType === TYPES.undefined && rightType === TYPES.object) || (leftType === TYPES.object && rightType === TYPES.undefined)) && (op === '==' || op === '!=')) return [
|
700
|
+
...(leftType === TYPES.object ? left : right),
|
701
|
+
...Opcodes.eqz,
|
702
|
+
...(op === '!=' ? [ [ Opcodes.i32_eqz ] ] : [])
|
703
|
+
];
|
704
|
+
|
670
705
|
return [
|
671
706
|
...left,
|
672
|
-
...right,
|
673
|
-
|
674
|
-
// drop values
|
675
707
|
[ Opcodes.drop ],
|
708
|
+
|
709
|
+
...right,
|
676
710
|
[ Opcodes.drop ],
|
677
711
|
|
678
|
-
// return
|
679
|
-
...number(op === '===' ?
|
712
|
+
// return true (!=/!==) or false (else)
|
713
|
+
...number(op === '!=' || op === '!==' ? 1 : 0, Valtype.i32)
|
680
714
|
];
|
681
715
|
}
|
682
716
|
|
717
|
+
// todo: niche null hell with 0
|
718
|
+
|
683
719
|
if (leftType === TYPES.string || rightType === TYPES.string) {
|
684
720
|
if (op === '+') {
|
685
721
|
// string concat (a + b)
|
686
722
|
return concatStrings(scope, left, right, _global, _name, assign);
|
687
723
|
}
|
688
724
|
|
689
|
-
//
|
690
|
-
if (!
|
725
|
+
// not an equality op, NaN
|
726
|
+
if (!eqOp) return number(NaN);
|
691
727
|
|
692
728
|
// else leave bool ops
|
693
729
|
// todo: convert string to number if string and number/bool
|
@@ -732,17 +768,11 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
732
768
|
];
|
733
769
|
};
|
734
770
|
|
735
|
-
let binaryExpDepth = 0;
|
736
771
|
const generateBinaryExp = (scope, decl, _global, _name) => {
|
737
|
-
|
738
|
-
|
739
|
-
const out = [
|
740
|
-
...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
|
741
|
-
];
|
772
|
+
const out = performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name);
|
742
773
|
|
743
774
|
if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
|
744
775
|
|
745
|
-
binaryExpDepth--;
|
746
776
|
return out;
|
747
777
|
};
|
748
778
|
|
@@ -999,6 +1029,8 @@ const countLeftover = wasm => {
|
|
999
1029
|
} else count--;
|
1000
1030
|
if (func) count += func.returns.length;
|
1001
1031
|
} else count--;
|
1032
|
+
|
1033
|
+
// console.log(count, decompile([ inst ]).slice(0, -1));
|
1002
1034
|
}
|
1003
1035
|
|
1004
1036
|
return count;
|
@@ -1185,24 +1217,34 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1185
1217
|
// use local for cached i32 length as commonly used
|
1186
1218
|
let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1187
1219
|
|
1220
|
+
let lengthI32CacheUsed = false;
|
1221
|
+
|
1222
|
+
const protoOut = protoFunc(pointer, {
|
1223
|
+
getCachedI32: () => {
|
1224
|
+
lengthI32CacheUsed = true;
|
1225
|
+
return [ [ Opcodes.local_get, lengthLocal ] ]
|
1226
|
+
},
|
1227
|
+
setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
|
1228
|
+
get: () => arrayUtil.getLength(pointer),
|
1229
|
+
getI32: () => arrayUtil.getLengthI32(pointer),
|
1230
|
+
set: value => arrayUtil.setLength(pointer, value),
|
1231
|
+
setI32: value => arrayUtil.setLengthI32(pointer, value)
|
1232
|
+
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
|
1233
|
+
return makeArray(scope, {
|
1234
|
+
rawElements: new Array(length)
|
1235
|
+
}, _global, _name, true, itemType);
|
1236
|
+
});
|
1237
|
+
|
1188
1238
|
return [
|
1189
1239
|
...out,
|
1190
1240
|
|
1191
|
-
...
|
1192
|
-
|
1241
|
+
...(!lengthI32CacheUsed ? [] : [
|
1242
|
+
...arrayUtil.getLengthI32(pointer),
|
1243
|
+
[ Opcodes.local_set, lengthLocal ],
|
1244
|
+
]),
|
1193
1245
|
|
1194
1246
|
[ Opcodes.block, valtypeBinary ],
|
1195
|
-
...
|
1196
|
-
cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
|
1197
|
-
get: arrayUtil.getLength(pointer),
|
1198
|
-
getI32: arrayUtil.getLengthI32(pointer),
|
1199
|
-
set: value => arrayUtil.setLength(pointer, value),
|
1200
|
-
setI32: value => arrayUtil.setLengthI32(pointer, value)
|
1201
|
-
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
|
1202
|
-
return makeArray(scope, {
|
1203
|
-
rawElements: new Array(length)
|
1204
|
-
}, _global, _name, true, itemType);
|
1205
|
-
}),
|
1247
|
+
...protoOut,
|
1206
1248
|
[ Opcodes.end ]
|
1207
1249
|
];
|
1208
1250
|
}
|
@@ -1262,7 +1304,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1262
1304
|
if (func && func.throws) scope.throws = true;
|
1263
1305
|
|
1264
1306
|
for (const arg of args) {
|
1265
|
-
out.
|
1307
|
+
out = out.concat(generate(scope, arg));
|
1266
1308
|
}
|
1267
1309
|
|
1268
1310
|
out.push([ Opcodes.call, idx ]);
|
@@ -1402,13 +1444,60 @@ const generateAssign = (scope, decl) => {
|
|
1402
1444
|
];
|
1403
1445
|
}
|
1404
1446
|
|
1447
|
+
const op = decl.operator.slice(0, -1) || '=';
|
1448
|
+
|
1449
|
+
// arr[i] | str[i]
|
1450
|
+
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
1451
|
+
const name = decl.left.object.name;
|
1452
|
+
const pointer = arrays.get(name);
|
1453
|
+
|
1454
|
+
const aotPointer = pointer != null;
|
1455
|
+
|
1456
|
+
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
1457
|
+
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1458
|
+
|
1459
|
+
const parentType = getNodeType(scope, decl.left.object);
|
1460
|
+
|
1461
|
+
return [
|
1462
|
+
...(aotPointer ? [] : [
|
1463
|
+
...generate(scope, decl.left.object),
|
1464
|
+
Opcodes.i32_to_u
|
1465
|
+
]),
|
1466
|
+
|
1467
|
+
// get index as valtype
|
1468
|
+
...generate(scope, decl.left.property),
|
1469
|
+
|
1470
|
+
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
1471
|
+
Opcodes.i32_to_u,
|
1472
|
+
...number(ValtypeSize[valtype], Valtype.i32),
|
1473
|
+
[ Opcodes.i32_mul ],
|
1474
|
+
...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
|
1475
|
+
...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
1476
|
+
|
1477
|
+
...(op === '=' ? generate(scope, decl.right, false, name) : performOp(scope, op, [
|
1478
|
+
[ Opcodes.local_get, pointerTmp ],
|
1479
|
+
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1480
|
+
], generate(scope, decl.right), parentType === TYPES._array ? TYPES.number : TYPES.string, getNodeType(scope, decl.right), false, name, true)),
|
1481
|
+
[ Opcodes.local_tee, newValueTmp ],
|
1482
|
+
|
1483
|
+
...(parentType === TYPES._array ? [
|
1484
|
+
[ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1485
|
+
] : [
|
1486
|
+
Opcodes.i32_to_u,
|
1487
|
+
[ StoreOps.i16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1488
|
+
]),
|
1489
|
+
|
1490
|
+
[ Opcodes.local_get, newValueTmp ]
|
1491
|
+
];
|
1492
|
+
}
|
1493
|
+
|
1405
1494
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1406
1495
|
|
1407
1496
|
if (local === undefined) {
|
1408
1497
|
// todo: this should be a devtools/repl/??? only thing
|
1409
1498
|
|
1410
1499
|
// only allow = for this
|
1411
|
-
if (
|
1500
|
+
if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
|
1412
1501
|
|
1413
1502
|
if (builtinVars[name]) {
|
1414
1503
|
// just return rhs (eg `NaN = 2`)
|
@@ -1422,8 +1511,10 @@ const generateAssign = (scope, decl) => {
|
|
1422
1511
|
];
|
1423
1512
|
}
|
1424
1513
|
|
1425
|
-
|
1426
|
-
|
1514
|
+
typeStates[name] = getNodeType(scope, decl.right);
|
1515
|
+
|
1516
|
+
if (op === '=') {
|
1517
|
+
// typeStates[name] = getNodeType(scope, decl.right);
|
1427
1518
|
|
1428
1519
|
return [
|
1429
1520
|
...generate(scope, decl.right, isGlobal, name),
|
@@ -1432,7 +1523,6 @@ const generateAssign = (scope, decl) => {
|
|
1432
1523
|
];
|
1433
1524
|
}
|
1434
1525
|
|
1435
|
-
const op = decl.operator.slice(0, -1);
|
1436
1526
|
if (op === '||' || op === '&&' || op === '??') {
|
1437
1527
|
// todo: is this needed?
|
1438
1528
|
// for logical assignment ops, it is not left @= right ~= left = left @ right
|
@@ -1567,7 +1657,7 @@ const generateUpdate = (scope, decl) => {
|
|
1567
1657
|
};
|
1568
1658
|
|
1569
1659
|
const generateIf = (scope, decl) => {
|
1570
|
-
const out = truthy(scope, generate(scope, decl.test), decl.test);
|
1660
|
+
const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test));
|
1571
1661
|
|
1572
1662
|
out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
|
1573
1663
|
depth.push('if');
|
@@ -1660,18 +1750,106 @@ const generateWhile = (scope, decl) => {
|
|
1660
1750
|
const generateForOf = (scope, decl) => {
|
1661
1751
|
const out = [];
|
1662
1752
|
|
1753
|
+
const rightType = getNodeType(scope, decl.right);
|
1754
|
+
const valtypeSize = rightType === TYPES._array ? ValtypeSize[valtype] : ValtypeSize.i16; // presume array (:()
|
1755
|
+
|
1756
|
+
// todo: for of inside for of might fuck up?
|
1757
|
+
const pointer = localTmp(scope, 'forof_base_pointer', Valtype.i32);
|
1758
|
+
const length = localTmp(scope, 'forof_length', Valtype.i32);
|
1759
|
+
const counter = localTmp(scope, 'forof_counter', Valtype.i32);
|
1760
|
+
|
1761
|
+
out.push(
|
1762
|
+
// set pointer as right
|
1763
|
+
...generate(scope, decl.right),
|
1764
|
+
Opcodes.i32_to_u,
|
1765
|
+
[ Opcodes.local_set, pointer ],
|
1766
|
+
|
1767
|
+
// get length
|
1768
|
+
[ Opcodes.local_get, pointer ],
|
1769
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
1770
|
+
[ Opcodes.local_set, length ]
|
1771
|
+
);
|
1772
|
+
|
1663
1773
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
1664
|
-
depth.push('
|
1774
|
+
depth.push('forof');
|
1665
1775
|
|
1666
|
-
|
1667
|
-
|
1668
|
-
depth.push('if');
|
1776
|
+
// setup local for left
|
1777
|
+
generate(scope, decl.left);
|
1669
1778
|
|
1670
|
-
|
1779
|
+
const leftName = decl.left.declarations[0].id.name;
|
1671
1780
|
|
1672
|
-
|
1673
|
-
|
1674
|
-
|
1781
|
+
// set type for local
|
1782
|
+
typeStates[leftName] = rightType === TYPES._array ? TYPES.number : TYPES.string;
|
1783
|
+
|
1784
|
+
const [ local, isGlobal ] = lookupName(scope, leftName);
|
1785
|
+
|
1786
|
+
if (rightType === TYPES._array) { // array
|
1787
|
+
out.push(
|
1788
|
+
[ Opcodes.local_get, pointer ],
|
1789
|
+
[ Opcodes.load, Math.log2(valtypeSize) - 1, ...unsignedLEB128(ValtypeSize.i32) ]
|
1790
|
+
);
|
1791
|
+
} else { // string
|
1792
|
+
const [ newOut, newPointer ] = makeArray(scope, {
|
1793
|
+
rawElements: new Array(1)
|
1794
|
+
}, isGlobal, leftName, true, 'i16');
|
1795
|
+
|
1796
|
+
out.push(
|
1797
|
+
// setup new/out array
|
1798
|
+
...newOut,
|
1799
|
+
[ Opcodes.drop ],
|
1800
|
+
|
1801
|
+
...number(0, Valtype.i32), // base 0 for store after
|
1802
|
+
|
1803
|
+
// load current string ind {arg}
|
1804
|
+
[ Opcodes.local_get, pointer ],
|
1805
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
1806
|
+
|
1807
|
+
// store to new string ind 0
|
1808
|
+
[ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
1809
|
+
|
1810
|
+
// return new string (page)
|
1811
|
+
...number(newPointer)
|
1812
|
+
);
|
1813
|
+
}
|
1814
|
+
|
1815
|
+
// set left value
|
1816
|
+
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ]);
|
1817
|
+
|
1818
|
+
out.push(
|
1819
|
+
[ Opcodes.block, Blocktype.void ],
|
1820
|
+
[ Opcodes.block, Blocktype.void ]
|
1821
|
+
);
|
1822
|
+
depth.push('block');
|
1823
|
+
depth.push('block');
|
1824
|
+
|
1825
|
+
out.push(
|
1826
|
+
...generate(scope, decl.body),
|
1827
|
+
[ Opcodes.end ]
|
1828
|
+
);
|
1829
|
+
depth.pop();
|
1830
|
+
|
1831
|
+
out.push(
|
1832
|
+
// increment iter pointer by valtype size
|
1833
|
+
[ Opcodes.local_get, pointer ],
|
1834
|
+
...number(valtypeSize, Valtype.i32),
|
1835
|
+
[ Opcodes.i32_add ],
|
1836
|
+
[ Opcodes.local_set, pointer ],
|
1837
|
+
|
1838
|
+
// increment counter by 1
|
1839
|
+
[ Opcodes.local_get, counter ],
|
1840
|
+
...number(1, Valtype.i32),
|
1841
|
+
[ Opcodes.i32_add ],
|
1842
|
+
[ Opcodes.local_tee, counter ],
|
1843
|
+
|
1844
|
+
// loop if counter != length
|
1845
|
+
[ Opcodes.local_get, length ],
|
1846
|
+
[ Opcodes.i32_ne ],
|
1847
|
+
[ Opcodes.br_if, 1 ],
|
1848
|
+
|
1849
|
+
[ Opcodes.end ], [ Opcodes.end ]
|
1850
|
+
);
|
1851
|
+
depth.pop();
|
1852
|
+
depth.pop();
|
1675
1853
|
|
1676
1854
|
return out;
|
1677
1855
|
};
|
@@ -1791,7 +1969,7 @@ const itemTypeToValtype = {
|
|
1791
1969
|
i16: 'i32'
|
1792
1970
|
};
|
1793
1971
|
|
1794
|
-
const
|
1972
|
+
const StoreOps = {
|
1795
1973
|
i32: Opcodes.i32_store,
|
1796
1974
|
i64: Opcodes.i64_store,
|
1797
1975
|
f64: Opcodes.f64_store,
|
@@ -1823,7 +2001,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1823
2001
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
|
1824
2002
|
);
|
1825
2003
|
|
1826
|
-
const storeOp =
|
2004
|
+
const storeOp = StoreOps[itemType];
|
1827
2005
|
const valtype = itemTypeToValtype[itemType];
|
1828
2006
|
|
1829
2007
|
if (!initEmpty) for (let i = 0; i < length; i++) {
|
@@ -2148,10 +2326,10 @@ const generateFunc = (scope, decl) => {
|
|
2148
2326
|
};
|
2149
2327
|
|
2150
2328
|
const generateCode = (scope, decl) => {
|
2151
|
-
|
2329
|
+
let out = [];
|
2152
2330
|
|
2153
2331
|
for (const x of decl.body) {
|
2154
|
-
out.
|
2332
|
+
out = out.concat(generate(scope, x));
|
2155
2333
|
}
|
2156
2334
|
|
2157
2335
|
return out;
|
package/compiler/index.js
CHANGED
@@ -78,7 +78,7 @@ export default (code, flags) => {
|
|
78
78
|
const wasmPages = Math.ceil((pages.size * pageSize) / 65536);
|
79
79
|
const bytes = wasmPages * 65536;
|
80
80
|
log('alloc', `\x1B[1mallocated ${bytes / 1024}KiB\x1B[0m for ${pages.size} things using ${wasmPages} Wasm page${wasmPages === 1 ? '' : 's'}`);
|
81
|
-
|
81
|
+
console.log([...pages.keys()].map(x => `\x1B[36m - ${x}\x1B[0m`).join('\n') + '\n');
|
82
82
|
}
|
83
83
|
|
84
84
|
const out = { wasm: sections, funcs, globals, tags, exceptions, pages };
|
package/compiler/opt.js
CHANGED
@@ -317,28 +317,24 @@ export default (funcs, globals) => {
|
|
317
317
|
continue;
|
318
318
|
}
|
319
319
|
|
320
|
-
|
321
|
-
const
|
320
|
+
// remove unneeded before get with update exprs (n++, etc) when value is unused
|
321
|
+
if (i < wasm.length - 4 && lastInst[1] === inst[1] && lastInst[0] === Opcodes.local_get && inst[0] === Opcodes.local_get && wasm[i + 1][0] === Opcodes.const && [Opcodes.add, Opcodes.sub].includes(wasm[i + 2][0]) && wasm[i + 3][0] === Opcodes.local_set && wasm[i + 3][1] === inst[1] && (wasm[i + 4][0] === Opcodes.drop || wasm[i + 4][0] === Opcodes.br)) {
|
322
|
+
// local.get 1
|
323
|
+
// local.get 1
|
324
|
+
// -->
|
325
|
+
// local.get 1
|
322
326
|
|
323
|
-
|
324
|
-
|
325
|
-
if (lastLastInst[0] === Opcodes.end && lastInst[1] === inst[1] && lastInst[0] === Opcodes.local_get && inst[0] === Opcodes.local_get) {
|
326
|
-
// local.get 1
|
327
|
-
// local.get 1
|
328
|
-
// -->
|
329
|
-
// local.get 1
|
330
|
-
|
331
|
-
// remove drop at the end as well
|
332
|
-
if (wasm[i + 4][0] === Opcodes.drop) {
|
333
|
-
wasm.splice(i + 4, 1);
|
334
|
-
}
|
327
|
+
// remove drop at the end as well
|
328
|
+
if (wasm[i + 4][0] === Opcodes.drop) wasm.splice(i + 4, 1);
|
335
329
|
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
}
|
330
|
+
wasm.splice(i, 1); // remove this inst (second get)
|
331
|
+
i--;
|
332
|
+
continue;
|
340
333
|
}
|
341
334
|
|
335
|
+
if (i < 2) continue;
|
336
|
+
const lastLastInst = wasm[i - 2];
|
337
|
+
|
342
338
|
if (lastLastInst[1] === inst[1] && inst[0] === Opcodes.local_get && lastInst[0] === Opcodes.local_tee && lastLastInst[0] === Opcodes.local_set) {
|
343
339
|
// local.set x
|
344
340
|
// local.tee y
|
package/compiler/prototype.js
CHANGED
@@ -23,6 +23,9 @@ const TYPES = {
|
|
23
23
|
|
24
24
|
export const PrototypeFuncs = function() {
|
25
25
|
const noUnlikelyChecks = process.argv.includes('-funsafe-no-unlikely-proto-checks');
|
26
|
+
let zeroChecks = process.argv.find(x => x.startsWith('-funsafe-zero-proto-checks='));
|
27
|
+
if (zeroChecks) zeroChecks = zeroChecks.split('=')[1].split(',').reduce((acc, x) => { acc[x.toLowerCase()] = true; return acc; }, {});
|
28
|
+
else zeroChecks = {};
|
26
29
|
|
27
30
|
this[TYPES._array] = {
|
28
31
|
// lX = local accessor of X ({ get, set }), iX = local index of X, wX = wasm ops of X
|
@@ -36,7 +39,7 @@ export const PrototypeFuncs = function() {
|
|
36
39
|
[ Opcodes.i32_lt_s ],
|
37
40
|
[ Opcodes.if, Blocktype.void ],
|
38
41
|
[ Opcodes.local_get, iTmp ],
|
39
|
-
...length.
|
42
|
+
...length.getCachedI32(),
|
40
43
|
[ Opcodes.i32_add ],
|
41
44
|
[ Opcodes.local_set, iTmp ],
|
42
45
|
[ Opcodes.end ],
|
@@ -47,7 +50,7 @@ export const PrototypeFuncs = function() {
|
|
47
50
|
[ Opcodes.i32_lt_s ],
|
48
51
|
|
49
52
|
[ Opcodes.local_get, iTmp ],
|
50
|
-
...length.
|
53
|
+
...length.getCachedI32(),
|
51
54
|
[ Opcodes.i32_ge_s ],
|
52
55
|
[ Opcodes.i32_or ],
|
53
56
|
|
@@ -67,7 +70,7 @@ export const PrototypeFuncs = function() {
|
|
67
70
|
// todo: only for 1 argument
|
68
71
|
push: (pointer, length, wNewMember) => [
|
69
72
|
// get memory offset of array at last index (length)
|
70
|
-
...length.
|
73
|
+
...length.getCachedI32(),
|
71
74
|
...number(ValtypeSize[valtype], Valtype.i32),
|
72
75
|
[ Opcodes.i32_mul ],
|
73
76
|
|
@@ -79,17 +82,17 @@ export const PrototypeFuncs = function() {
|
|
79
82
|
|
80
83
|
// bump array length by 1 and return it
|
81
84
|
...length.setI32([
|
82
|
-
...length.
|
85
|
+
...length.getCachedI32(),
|
83
86
|
...number(1, Valtype.i32),
|
84
87
|
[ Opcodes.i32_add ]
|
85
88
|
]),
|
86
89
|
|
87
|
-
...length.get
|
90
|
+
...length.get()
|
88
91
|
],
|
89
92
|
|
90
93
|
pop: (pointer, length) => [
|
91
94
|
// if length == 0, noop
|
92
|
-
...length.
|
95
|
+
...length.getCachedI32(),
|
93
96
|
[ Opcodes.i32_eqz ],
|
94
97
|
[ Opcodes.if, Blocktype.void ],
|
95
98
|
...number(UNDEFINED),
|
@@ -100,13 +103,13 @@ export const PrototypeFuncs = function() {
|
|
100
103
|
|
101
104
|
// decrement length by 1
|
102
105
|
...length.setI32([
|
103
|
-
...length.
|
106
|
+
...length.getCachedI32(),
|
104
107
|
...number(1, Valtype.i32),
|
105
108
|
[ Opcodes.i32_sub ]
|
106
109
|
]),
|
107
110
|
|
108
111
|
// load last element
|
109
|
-
...length.
|
112
|
+
...length.getCachedI32(),
|
110
113
|
...number(ValtypeSize[valtype], Valtype.i32),
|
111
114
|
[ Opcodes.i32_mul ],
|
112
115
|
|
@@ -115,7 +118,7 @@ export const PrototypeFuncs = function() {
|
|
115
118
|
|
116
119
|
shift: (pointer, length) => [
|
117
120
|
// if length == 0, noop
|
118
|
-
...length.
|
121
|
+
...length.getCachedI32(),
|
119
122
|
Opcodes.i32_eqz,
|
120
123
|
[ Opcodes.if, Blocktype.void ],
|
121
124
|
...number(UNDEFINED),
|
@@ -126,7 +129,7 @@ export const PrototypeFuncs = function() {
|
|
126
129
|
|
127
130
|
// decrement length by 1
|
128
131
|
...length.setI32([
|
129
|
-
...length.
|
132
|
+
...length.getCachedI32(),
|
130
133
|
...number(1, Valtype.i32),
|
131
134
|
[ Opcodes.i32_sub ]
|
132
135
|
]),
|
@@ -140,11 +143,66 @@ export const PrototypeFuncs = function() {
|
|
140
143
|
...number(pointer + ValtypeSize.i32 + ValtypeSize[valtype], Valtype.i32), // src = base array index + length size + an index
|
141
144
|
...number(pageSize - ValtypeSize.i32 - ValtypeSize[valtype], Valtype.i32), // size = PageSize - length size - an index
|
142
145
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ]
|
146
|
+
],
|
147
|
+
|
148
|
+
fill: (pointer, length, wElement, iTmp) => [
|
149
|
+
...wElement,
|
150
|
+
[ Opcodes.local_set, iTmp ],
|
151
|
+
|
152
|
+
// use cached length i32 as pointer
|
153
|
+
...length.getCachedI32(),
|
154
|
+
|
155
|
+
// length - 1 for indexes
|
156
|
+
...number(1, Valtype.i32),
|
157
|
+
[ Opcodes.i32_sub ],
|
158
|
+
|
159
|
+
// * sizeof value
|
160
|
+
...number(ValtypeSize[valtype], Valtype.i32),
|
161
|
+
[ Opcodes.i32_mul ],
|
162
|
+
|
163
|
+
...length.setCachedI32(),
|
164
|
+
|
165
|
+
...(noUnlikelyChecks ? [] : [
|
166
|
+
...length.getCachedI32(),
|
167
|
+
...number(0, Valtype.i32),
|
168
|
+
[ Opcodes.i32_lt_s ],
|
169
|
+
[ Opcodes.if, Blocktype.void ],
|
170
|
+
...number(pointer),
|
171
|
+
[ Opcodes.br, 1 ],
|
172
|
+
[ Opcodes.end ]
|
173
|
+
]),
|
174
|
+
|
175
|
+
[ Opcodes.loop, Blocktype.void ],
|
176
|
+
|
177
|
+
// set element using pointer
|
178
|
+
...length.getCachedI32(),
|
179
|
+
[ Opcodes.local_get, iTmp ],
|
180
|
+
[ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32) ],
|
181
|
+
|
182
|
+
// pointer - sizeof value
|
183
|
+
...length.getCachedI32(),
|
184
|
+
...number(ValtypeSize[valtype], Valtype.i32),
|
185
|
+
[ Opcodes.i32_sub ],
|
186
|
+
|
187
|
+
...length.setCachedI32(),
|
188
|
+
|
189
|
+
// if pointer >= 0, loop
|
190
|
+
...length.getCachedI32(),
|
191
|
+
...number(0, Valtype.i32),
|
192
|
+
[ Opcodes.i32_ge_s ],
|
193
|
+
[ Opcodes.br_if, 0 ],
|
194
|
+
|
195
|
+
[ Opcodes.end ],
|
196
|
+
|
197
|
+
// return this array
|
198
|
+
...number(pointer)
|
143
199
|
]
|
144
200
|
};
|
145
201
|
|
146
202
|
this[TYPES._array].at.local = Valtype.i32;
|
147
203
|
this[TYPES._array].push.noArgRetLength = true;
|
204
|
+
this[TYPES._array].fill.local = valtypeBinary;
|
205
|
+
this[TYPES._array].fill.returnType = TYPES._array;
|
148
206
|
|
149
207
|
this[TYPES.string] = {
|
150
208
|
at: (pointer, length, wIndex, iTmp, arrayShell) => {
|
@@ -166,7 +224,7 @@ export const PrototypeFuncs = function() {
|
|
166
224
|
[ Opcodes.i32_lt_s ],
|
167
225
|
[ Opcodes.if, Blocktype.void ],
|
168
226
|
[ Opcodes.local_get, iTmp ],
|
169
|
-
...length.
|
227
|
+
...length.getCachedI32(),
|
170
228
|
[ Opcodes.i32_add ],
|
171
229
|
[ Opcodes.local_set, iTmp ],
|
172
230
|
[ Opcodes.end ],
|
@@ -177,7 +235,7 @@ export const PrototypeFuncs = function() {
|
|
177
235
|
[ Opcodes.i32_lt_s ],
|
178
236
|
|
179
237
|
[ Opcodes.local_get, iTmp ],
|
180
|
-
...length.
|
238
|
+
...length.getCachedI32(),
|
181
239
|
[ Opcodes.i32_ge_s ],
|
182
240
|
[ Opcodes.i32_or ],
|
183
241
|
|
@@ -233,27 +291,31 @@ export const PrototypeFuncs = function() {
|
|
233
291
|
return [
|
234
292
|
...wIndex,
|
235
293
|
Opcodes.i32_to,
|
236
|
-
[ Opcodes.local_set, iTmp ],
|
237
294
|
|
238
|
-
|
239
|
-
|
295
|
+
...(zeroChecks.charcodeat ? [] : [
|
296
|
+
[ Opcodes.local_set, iTmp ],
|
297
|
+
|
298
|
+
// index < 0
|
299
|
+
...(noUnlikelyChecks ? [] : [
|
300
|
+
[ Opcodes.local_get, iTmp ],
|
301
|
+
...number(0, Valtype.i32),
|
302
|
+
[ Opcodes.i32_lt_s ],
|
303
|
+
]),
|
304
|
+
|
305
|
+
// index >= length
|
240
306
|
[ Opcodes.local_get, iTmp ],
|
241
|
-
...
|
242
|
-
[ Opcodes.
|
243
|
-
]),
|
307
|
+
...length.getCachedI32(),
|
308
|
+
[ Opcodes.i32_ge_s ],
|
244
309
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
310
|
+
...(noUnlikelyChecks ? [] : [ [ Opcodes.i32_or ] ]),
|
311
|
+
[ Opcodes.if, Blocktype.void ],
|
312
|
+
...number(NaN),
|
313
|
+
[ Opcodes.br, 1 ],
|
314
|
+
[ Opcodes.end ],
|
249
315
|
|
250
|
-
|
251
|
-
|
252
|
-
...number(NaN),
|
253
|
-
[ Opcodes.br, 1 ],
|
254
|
-
[ Opcodes.end ],
|
316
|
+
[ Opcodes.local_get, iTmp ],
|
317
|
+
]),
|
255
318
|
|
256
|
-
[ Opcodes.local_get, iTmp ],
|
257
319
|
...number(ValtypeSize.i16, Valtype.i32),
|
258
320
|
[ Opcodes.i32_mul ],
|
259
321
|
|
package/compiler/wrap.js
CHANGED
@@ -34,11 +34,18 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
|
|
34
34
|
times.push(performance.now() - t1);
|
35
35
|
if (flags.includes('info')) console.log(bold(`compiled in ${times[0].toFixed(2)}ms`));
|
36
36
|
|
37
|
+
const getString = pointer => {
|
38
|
+
const length = new Int32Array(memory.buffer, pointer, 1);
|
39
|
+
|
40
|
+
return Array.from(new Uint16Array(memory.buffer, pointer + 4, length)).map(x => String.fromCharCode(x)).join('');
|
41
|
+
};
|
42
|
+
|
37
43
|
const t2 = performance.now();
|
38
44
|
const { instance } = await WebAssembly.instantiate(wasm, {
|
39
45
|
'': {
|
40
46
|
p: valtype === 'i64' ? i => print(Number(i).toString()) : i => print(i.toString()),
|
41
47
|
c: valtype === 'i64' ? i => print(String.fromCharCode(Number(i))) : i => print(String.fromCharCode(i)),
|
48
|
+
s: valtype === 'i64' ? i => print(getString(Number(i))) : i => print(getString(i)),
|
42
49
|
a: c => { if (!Number(c)) throw new Error(`assert failed`); },
|
43
50
|
t: _ => performance.now(),
|
44
51
|
...customImports
|
package/package.json
CHANGED
package/r.js
CHANGED
@@ -1 +1,39 @@
|
|
1
|
-
|
1
|
+
compareArray.isSameValue = function(a, b) {
|
2
|
+
if (a === 0 && b === 0) return 1 / a === 1 / b;
|
3
|
+
if (a !== a && b !== b) return true;
|
4
|
+
|
5
|
+
return a === b;
|
6
|
+
};
|
7
|
+
|
8
|
+
function compareArray(a, b) {
|
9
|
+
// if either are nullish
|
10
|
+
if (a == null || b == null) return false;
|
11
|
+
|
12
|
+
// megahack: all arrays from now on will be >0 pointer
|
13
|
+
const _hack = '';
|
14
|
+
|
15
|
+
// hack: enforce type inference of being arrays
|
16
|
+
a ??= [];
|
17
|
+
b ??= [];
|
18
|
+
|
19
|
+
if (b.length !== a.length) {
|
20
|
+
return false;
|
21
|
+
}
|
22
|
+
|
23
|
+
for (var i = 0; i < a.length; i++) {
|
24
|
+
if (!compareArray.isSameValue(b[i], a[i])) {
|
25
|
+
return false;
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
return true;
|
30
|
+
}
|
31
|
+
|
32
|
+
console.log(compareArray(null, []));
|
33
|
+
console.log(compareArray(undefined, []));
|
34
|
+
|
35
|
+
console.log(compareArray([], []));
|
36
|
+
console.log(compareArray([ 1 ], []));
|
37
|
+
console.log(compareArray([ 1 ], [ 1 ]));
|
38
|
+
console.log(compareArray([ 1, 2 ], [ 1 ]));
|
39
|
+
console.log(compareArray([ 1, 2 ], [ 1, 2 ]));
|
package/rhemyn/compile.js
CHANGED
@@ -21,7 +21,7 @@ const generate = (node, negated = false, get = true, func = 'test') => {
|
|
21
21
|
out = [
|
22
22
|
// set length local
|
23
23
|
[ Opcodes.local_get, BasePointer ],
|
24
|
-
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1,
|
24
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
25
25
|
[ Opcodes.local_set, Length ],
|
26
26
|
|
27
27
|
// set iter pointer local as base + sizeof i32 initially
|
package/runner/index.js
CHANGED
@@ -34,12 +34,14 @@ const source = fs.readFileSync(file, 'utf8');
|
|
34
34
|
|
35
35
|
let cache = '';
|
36
36
|
const print = str => {
|
37
|
-
cache += str;
|
37
|
+
/* cache += str;
|
38
38
|
|
39
39
|
if (str === '\n') {
|
40
40
|
process.stdout.write(cache);
|
41
41
|
cache = '';
|
42
|
-
}
|
42
|
+
} */
|
43
|
+
|
44
|
+
process.stdout.write(str);
|
43
45
|
};
|
44
46
|
|
45
47
|
try {
|
@@ -49,5 +51,5 @@ try {
|
|
49
51
|
if (cache) process.stdout.write(cache);
|
50
52
|
} catch (e) {
|
51
53
|
if (cache) process.stdout.write(cache);
|
52
|
-
console.error(`${e.constructor.name}: ${e.message}`);
|
54
|
+
console.error(process.argv.includes('-i') ? e : `${e.constructor.name}: ${e.message}`);
|
53
55
|
}
|
package/runner/info.js
CHANGED
@@ -36,7 +36,7 @@ const print = str => {
|
|
36
36
|
};
|
37
37
|
|
38
38
|
const t0 = performance.now();
|
39
|
-
const { wasm, exports } = await compile(source, raw ? [ 'module' ] : [ 'module', 'info' ], {}, print);
|
39
|
+
const { wasm, exports, pages } = await compile(source, raw ? [ 'module' ] : [ 'module', 'info' ], {}, print);
|
40
40
|
|
41
41
|
if (!raw && typeof Deno === 'undefined') fs.writeFileSync('out.wasm', Buffer.from(wasm));
|
42
42
|
|
@@ -51,4 +51,39 @@ if (!process.argv.includes('-no-run')) {
|
|
51
51
|
}
|
52
52
|
|
53
53
|
if (!raw) console.log(bold(`wasm binary is ${wasm.byteLength} bytes`));
|
54
|
-
if (!raw) console.log(`total: ${(performance.now() - t0).toFixed(2)}ms`);
|
54
|
+
if (!raw) console.log(`total: ${(performance.now() - t0).toFixed(2)}ms`);
|
55
|
+
|
56
|
+
if (!raw && process.argv.includes('-mem') && exports.$) {
|
57
|
+
console.log();
|
58
|
+
|
59
|
+
let lastMemory, lastPages;
|
60
|
+
const PageSize = 65536;
|
61
|
+
const memoryToString = mem => {
|
62
|
+
let out = '';
|
63
|
+
const pages = lastPages.length;
|
64
|
+
const wasmPages = mem.buffer.byteLength / PageSize;
|
65
|
+
|
66
|
+
out += `\x1B[1mallocated ${mem.buffer.byteLength / 1024}KiB\x1B[0m for ${pages} things using ${wasmPages} Wasm page${wasmPages === 1 ? '' : 's'}\n`;
|
67
|
+
|
68
|
+
const buf = new Uint8Array(mem.buffer);
|
69
|
+
|
70
|
+
for (let i = 0; i < pages; i++) {
|
71
|
+
out += `\x1B[36m${lastPages[i]}\x1B[2m | \x1B[0m`;
|
72
|
+
|
73
|
+
for (let j = 0; j < 50; j++) {
|
74
|
+
const val = buf[i * pageSize + j];
|
75
|
+
if (val === 0) out += '\x1B[2m';
|
76
|
+
out += val.toString(16).padStart(2, '0');
|
77
|
+
if (val === 0) out += '\x1B[0m';
|
78
|
+
out += ' ';
|
79
|
+
}
|
80
|
+
out += '\n';
|
81
|
+
}
|
82
|
+
|
83
|
+
return out;
|
84
|
+
};
|
85
|
+
|
86
|
+
lastPages = [...pages.keys()];
|
87
|
+
lastMemory = exports.$;
|
88
|
+
console.log(memoryToString(lastMemory));
|
89
|
+
}
|