porffor 0.2.0-29c477a → 0.2.0-30ecc4a
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 +5 -2
- package/compiler/codeGen.js +142 -29
- package/compiler/opt.js +1 -0
- package/compiler/prototype.js +171 -16
- package/compiler/wasmSpec.js +3 -0
- package/compiler/wrap.js +9 -1
- package/package.json +1 -1
package/README.md
CHANGED
@@ -209,10 +209,13 @@ Porffor can run Test262 via some hacks/transforms which remove unsupported featu
|
|
209
209
|
- `test262`: test262 runner and utils
|
210
210
|
|
211
211
|
## Usecases
|
212
|
-
Basically none (other than giving people headaches). Potential ideas
|
212
|
+
Basically none right now (other than giving people headaches). Potential ideas:
|
213
|
+
- Safety. As Porffor is written in JS, a memory-safe language\*, and compiles JS to Wasm, a fully sandboxed environment\*, it is quite safe. (\* These rely on the underlying implementations being secure. You could also run Wasm, or even Porffor itself, with an interpreter instead of a JIT for bonus security points too.)
|
214
|
+
- Compiling JS to native binaries. This is still very early, [`2c`](#2c) is not that good yet :(
|
215
|
+
- More in future probably?
|
213
216
|
|
214
217
|
## Usage
|
215
|
-
Basically nothing will work :). See files in `test` for examples.
|
218
|
+
Basically nothing will work :). See files in `test` and `bench` for examples.
|
216
219
|
|
217
220
|
1. Clone repo
|
218
221
|
2. `npm install`
|
package/compiler/codeGen.js
CHANGED
@@ -55,7 +55,7 @@ const todo = msg => {
|
|
55
55
|
};
|
56
56
|
|
57
57
|
const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
58
|
-
const generate = (scope, decl, global = false, name = undefined) => {
|
58
|
+
const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
|
59
59
|
switch (decl.type) {
|
60
60
|
case 'BinaryExpression':
|
61
61
|
return generateBinaryExp(scope, decl, global, name);
|
@@ -86,7 +86,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
86
86
|
return generateExp(scope, decl);
|
87
87
|
|
88
88
|
case 'CallExpression':
|
89
|
-
return generateCall(scope, decl, global, name);
|
89
|
+
return generateCall(scope, decl, global, name, valueUnused);
|
90
90
|
|
91
91
|
case 'NewExpression':
|
92
92
|
return generateNew(scope, decl, global, name);
|
@@ -685,6 +685,15 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
685
685
|
[ Opcodes.i32_eqz ], */
|
686
686
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
687
687
|
],
|
688
|
+
[TYPES._bytestring]: [ // duplicate of string
|
689
|
+
[ Opcodes.local_get, tmp ],
|
690
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
691
|
+
|
692
|
+
// get length
|
693
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
694
|
+
|
695
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
696
|
+
],
|
688
697
|
default: def
|
689
698
|
}, intOut ? Valtype.i32 : valtypeBinary)
|
690
699
|
];
|
@@ -712,6 +721,17 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
712
721
|
[ Opcodes.i32_eqz ],
|
713
722
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
714
723
|
],
|
724
|
+
[TYPES._bytestring]: [ // duplicate of string
|
725
|
+
[ Opcodes.local_get, tmp ],
|
726
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
727
|
+
|
728
|
+
// get length
|
729
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
730
|
+
|
731
|
+
// if length == 0
|
732
|
+
[ Opcodes.i32_eqz ],
|
733
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
734
|
+
],
|
715
735
|
default: [
|
716
736
|
// if value == 0
|
717
737
|
[ Opcodes.local_get, tmp ],
|
@@ -1044,7 +1064,8 @@ const TYPES = {
|
|
1044
1064
|
|
1045
1065
|
// these are not "typeof" types but tracked internally
|
1046
1066
|
_array: 0x10,
|
1047
|
-
_regexp: 0x11
|
1067
|
+
_regexp: 0x11,
|
1068
|
+
_bytestring: 0x12
|
1048
1069
|
};
|
1049
1070
|
|
1050
1071
|
const TYPE_NAMES = {
|
@@ -1058,7 +1079,8 @@ const TYPE_NAMES = {
|
|
1058
1079
|
[TYPES.bigint]: 'BigInt',
|
1059
1080
|
|
1060
1081
|
[TYPES._array]: 'Array',
|
1061
|
-
[TYPES._regexp]: 'RegExp'
|
1082
|
+
[TYPES._regexp]: 'RegExp',
|
1083
|
+
[TYPES._bytestring]: 'ByteString'
|
1062
1084
|
};
|
1063
1085
|
|
1064
1086
|
const getType = (scope, _name) => {
|
@@ -1111,6 +1133,8 @@ const getNodeType = (scope, node) => {
|
|
1111
1133
|
if (node.type === 'Literal') {
|
1112
1134
|
if (node.regex) return TYPES._regexp;
|
1113
1135
|
|
1136
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
|
1137
|
+
|
1114
1138
|
return TYPES[typeof node.value];
|
1115
1139
|
}
|
1116
1140
|
|
@@ -1124,6 +1148,15 @@ const getNodeType = (scope, node) => {
|
|
1124
1148
|
|
1125
1149
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1126
1150
|
const name = node.callee.name;
|
1151
|
+
if (!name) {
|
1152
|
+
// iife
|
1153
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1154
|
+
|
1155
|
+
// presume
|
1156
|
+
// todo: warn here?
|
1157
|
+
return TYPES.number;
|
1158
|
+
}
|
1159
|
+
|
1127
1160
|
const func = funcs.find(x => x.name === name);
|
1128
1161
|
|
1129
1162
|
if (func) {
|
@@ -1218,7 +1251,7 @@ const getNodeType = (scope, node) => {
|
|
1218
1251
|
if (node.operator === '!') return TYPES.boolean;
|
1219
1252
|
if (node.operator === 'void') return TYPES.undefined;
|
1220
1253
|
if (node.operator === 'delete') return TYPES.boolean;
|
1221
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1254
|
+
if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
|
1222
1255
|
|
1223
1256
|
return TYPES.number;
|
1224
1257
|
}
|
@@ -1227,7 +1260,9 @@ const getNodeType = (scope, node) => {
|
|
1227
1260
|
// hack: if something.length, number type
|
1228
1261
|
if (node.property.name === 'length') return TYPES.number;
|
1229
1262
|
|
1230
|
-
|
1263
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1264
|
+
|
1265
|
+
// presume
|
1231
1266
|
return TYPES.number;
|
1232
1267
|
}
|
1233
1268
|
|
@@ -1299,9 +1334,9 @@ const countLeftover = wasm => {
|
|
1299
1334
|
|
1300
1335
|
if (depth === 0)
|
1301
1336
|
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1302
|
-
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1337
|
+
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1303
1338
|
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1304
|
-
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
1339
|
+
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1305
1340
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1306
1341
|
else if (inst[0] === Opcodes.return) count = 0;
|
1307
1342
|
else if (inst[0] === Opcodes.call) {
|
@@ -1327,7 +1362,7 @@ const disposeLeftover = wasm => {
|
|
1327
1362
|
const generateExp = (scope, decl) => {
|
1328
1363
|
const expression = decl.expression;
|
1329
1364
|
|
1330
|
-
const out = generate(scope, expression);
|
1365
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1331
1366
|
disposeLeftover(out);
|
1332
1367
|
|
1333
1368
|
return out;
|
@@ -1385,7 +1420,7 @@ const RTArrayUtil = {
|
|
1385
1420
|
]
|
1386
1421
|
};
|
1387
1422
|
|
1388
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1423
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1389
1424
|
/* const callee = decl.callee;
|
1390
1425
|
const args = decl.arguments;
|
1391
1426
|
|
@@ -1504,10 +1539,18 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1504
1539
|
// use local for cached i32 length as commonly used
|
1505
1540
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1506
1541
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1507
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1508
1542
|
|
1509
1543
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1510
1544
|
|
1545
|
+
const rawPointer = [
|
1546
|
+
...generate(scope, target),
|
1547
|
+
Opcodes.i32_to_u
|
1548
|
+
];
|
1549
|
+
|
1550
|
+
const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
|
1551
|
+
const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
|
1552
|
+
|
1553
|
+
let allOptUnused = true;
|
1511
1554
|
let lengthI32CacheUsed = false;
|
1512
1555
|
const protoBC = {};
|
1513
1556
|
for (const x in protoCands) {
|
@@ -1527,6 +1570,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1527
1570
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1528
1571
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1529
1572
|
|
1573
|
+
let optUnused = false;
|
1530
1574
|
const protoOut = protoFunc(getPointer, {
|
1531
1575
|
getCachedI32: () => {
|
1532
1576
|
lengthI32CacheUsed = true;
|
@@ -1541,10 +1585,15 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1541
1585
|
return makeArray(scope, {
|
1542
1586
|
rawElements: new Array(length)
|
1543
1587
|
}, _global, _name, true, itemType);
|
1588
|
+
}, () => {
|
1589
|
+
optUnused = true;
|
1590
|
+
return unusedValue;
|
1544
1591
|
});
|
1545
1592
|
|
1593
|
+
if (!optUnused) allOptUnused = false;
|
1594
|
+
|
1546
1595
|
protoBC[x] = [
|
1547
|
-
[ Opcodes.block, valtypeBinary ],
|
1596
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1548
1597
|
...protoOut,
|
1549
1598
|
|
1550
1599
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
@@ -1553,11 +1602,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1553
1602
|
];
|
1554
1603
|
}
|
1555
1604
|
|
1556
|
-
|
1557
|
-
...generate(scope, target),
|
1605
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1558
1606
|
|
1559
|
-
|
1560
|
-
|
1607
|
+
return [
|
1608
|
+
...(usePointerCache ? [
|
1609
|
+
...rawPointer,
|
1610
|
+
[ Opcodes.local_set, pointerLocal ],
|
1611
|
+
] : []),
|
1561
1612
|
|
1562
1613
|
...(!lengthI32CacheUsed ? [] : [
|
1563
1614
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1569,7 +1620,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1569
1620
|
|
1570
1621
|
// TODO: error better
|
1571
1622
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1572
|
-
}, valtypeBinary),
|
1623
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1573
1624
|
];
|
1574
1625
|
}
|
1575
1626
|
}
|
@@ -1777,6 +1828,8 @@ const brTable = (input, bc, returns) => {
|
|
1777
1828
|
};
|
1778
1829
|
|
1779
1830
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1831
|
+
if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
|
1832
|
+
|
1780
1833
|
const known = knownType(scope, type);
|
1781
1834
|
if (known != null) {
|
1782
1835
|
return bc[known] ?? bc.default;
|
@@ -2167,6 +2220,8 @@ const generateUnary = (scope, decl) => {
|
|
2167
2220
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2168
2221
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
2169
2222
|
|
2223
|
+
[TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2224
|
+
|
2170
2225
|
// object and internal types
|
2171
2226
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2172
2227
|
});
|
@@ -2348,13 +2403,14 @@ const generateForOf = (scope, decl) => {
|
|
2348
2403
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2349
2404
|
// hack: this is naughty and will break things!
|
2350
2405
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2351
|
-
if (pages.
|
2406
|
+
if (pages.hasAnyString) {
|
2352
2407
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2353
2408
|
rawElements: new Array(1)
|
2354
2409
|
}, isGlobal, leftName, true, 'i16');
|
2355
2410
|
}
|
2356
2411
|
|
2357
2412
|
// set type for local
|
2413
|
+
// todo: optimize away counter and use end pointer
|
2358
2414
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2359
2415
|
[TYPES._array]: [
|
2360
2416
|
...setType(scope, leftName, TYPES.number),
|
@@ -2540,6 +2596,8 @@ const allocPage = (reason, type) => {
|
|
2540
2596
|
|
2541
2597
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2542
2598
|
if (reason.startsWith('string:')) pages.hasString = true;
|
2599
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
2600
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2543
2601
|
|
2544
2602
|
const ind = pages.size;
|
2545
2603
|
pages.set(reason, { ind, type });
|
@@ -2573,7 +2631,8 @@ const StoreOps = {
|
|
2573
2631
|
f64: Opcodes.f64_store,
|
2574
2632
|
|
2575
2633
|
// expects i32 input!
|
2576
|
-
|
2634
|
+
i8: Opcodes.i32_store8,
|
2635
|
+
i16: Opcodes.i32_store16,
|
2577
2636
|
};
|
2578
2637
|
|
2579
2638
|
let data = [];
|
@@ -2592,6 +2651,15 @@ const compileBytes = (val, itemType, signed = true) => {
|
|
2592
2651
|
}
|
2593
2652
|
};
|
2594
2653
|
|
2654
|
+
const getAllocType = itemType => {
|
2655
|
+
switch (itemType) {
|
2656
|
+
case 'i8': return 'bytestring';
|
2657
|
+
case 'i16': return 'string';
|
2658
|
+
|
2659
|
+
default: return 'array';
|
2660
|
+
}
|
2661
|
+
};
|
2662
|
+
|
2595
2663
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2596
2664
|
const out = [];
|
2597
2665
|
|
@@ -2601,7 +2669,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2601
2669
|
|
2602
2670
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2603
2671
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2604
|
-
arrays.set(name, allocPage(`${itemType
|
2672
|
+
arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2605
2673
|
}
|
2606
2674
|
|
2607
2675
|
const pointer = arrays.get(name);
|
@@ -2647,7 +2715,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2647
2715
|
out.push(
|
2648
2716
|
...number(0, Valtype.i32),
|
2649
2717
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2650
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2718
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2651
2719
|
);
|
2652
2720
|
}
|
2653
2721
|
|
@@ -2657,15 +2725,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2657
2725
|
return [ out, pointer ];
|
2658
2726
|
};
|
2659
2727
|
|
2728
|
+
const byteStringable = str => {
|
2729
|
+
if (!process.argv.includes('-bytestring')) return false;
|
2730
|
+
|
2731
|
+
for (let i = 0; i < str.length; i++) {
|
2732
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
2733
|
+
}
|
2734
|
+
|
2735
|
+
return true;
|
2736
|
+
};
|
2737
|
+
|
2660
2738
|
const makeString = (scope, str, global = false, name = '$undeclared') => {
|
2661
2739
|
const rawElements = new Array(str.length);
|
2740
|
+
let byteStringable = process.argv.includes('-bytestring');
|
2662
2741
|
for (let i = 0; i < str.length; i++) {
|
2663
|
-
|
2742
|
+
const c = str.charCodeAt(i);
|
2743
|
+
rawElements[i] = c;
|
2744
|
+
|
2745
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2664
2746
|
}
|
2665
2747
|
|
2666
2748
|
return makeArray(scope, {
|
2667
2749
|
rawElements
|
2668
|
-
}, global, name, false, 'i16')[0];
|
2750
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2669
2751
|
};
|
2670
2752
|
|
2671
2753
|
let arrays = new Map();
|
@@ -2693,10 +2775,13 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2693
2775
|
];
|
2694
2776
|
}
|
2695
2777
|
|
2778
|
+
const object = generate(scope, decl.object);
|
2779
|
+
const property = generate(scope, decl.property);
|
2780
|
+
|
2696
2781
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2697
2782
|
// hack: this is naughty and will break things!
|
2698
2783
|
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2699
|
-
if (pages.
|
2784
|
+
if (pages.hasAnyString) {
|
2700
2785
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2701
2786
|
rawElements: new Array(1)
|
2702
2787
|
}, _global, _name, true, 'i16');
|
@@ -2705,7 +2790,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2705
2790
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2706
2791
|
[TYPES._array]: [
|
2707
2792
|
// get index as valtype
|
2708
|
-
...
|
2793
|
+
...property,
|
2709
2794
|
|
2710
2795
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2711
2796
|
Opcodes.i32_to_u,
|
@@ -2713,7 +2798,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2713
2798
|
[ Opcodes.i32_mul ],
|
2714
2799
|
|
2715
2800
|
...(aotPointer ? [] : [
|
2716
|
-
...
|
2801
|
+
...object,
|
2717
2802
|
Opcodes.i32_to_u,
|
2718
2803
|
[ Opcodes.i32_add ]
|
2719
2804
|
]),
|
@@ -2732,14 +2817,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2732
2817
|
|
2733
2818
|
...number(0, Valtype.i32), // base 0 for store later
|
2734
2819
|
|
2735
|
-
...
|
2736
|
-
|
2820
|
+
...property,
|
2737
2821
|
Opcodes.i32_to_u,
|
2822
|
+
|
2738
2823
|
...number(ValtypeSize.i16, Valtype.i32),
|
2739
2824
|
[ Opcodes.i32_mul ],
|
2740
2825
|
|
2741
2826
|
...(aotPointer ? [] : [
|
2742
|
-
...
|
2827
|
+
...object,
|
2743
2828
|
Opcodes.i32_to_u,
|
2744
2829
|
[ Opcodes.i32_add ]
|
2745
2830
|
]),
|
@@ -2756,6 +2841,34 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2756
2841
|
...number(TYPES.string, Valtype.i32),
|
2757
2842
|
setLastType(scope)
|
2758
2843
|
],
|
2844
|
+
[TYPES._bytestring]: [
|
2845
|
+
// setup new/out array
|
2846
|
+
...newOut,
|
2847
|
+
[ Opcodes.drop ],
|
2848
|
+
|
2849
|
+
...number(0, Valtype.i32), // base 0 for store later
|
2850
|
+
|
2851
|
+
...property,
|
2852
|
+
Opcodes.i32_to_u,
|
2853
|
+
|
2854
|
+
...(aotPointer ? [] : [
|
2855
|
+
...object,
|
2856
|
+
Opcodes.i32_to_u,
|
2857
|
+
[ Opcodes.i32_add ]
|
2858
|
+
]),
|
2859
|
+
|
2860
|
+
// load current string ind {arg}
|
2861
|
+
[ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2862
|
+
|
2863
|
+
// store to new string ind 0
|
2864
|
+
[ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2865
|
+
|
2866
|
+
// return new string (page)
|
2867
|
+
...number(newPointer),
|
2868
|
+
|
2869
|
+
...number(TYPES._bytestring, Valtype.i32),
|
2870
|
+
setLastType(scope)
|
2871
|
+
],
|
2759
2872
|
|
2760
2873
|
default: [ [ Opcodes.unreachable ] ]
|
2761
2874
|
});
|
package/compiler/opt.js
CHANGED
@@ -192,6 +192,7 @@ export default (funcs, globals, pages, tags) => {
|
|
192
192
|
let missing = false;
|
193
193
|
if (type === 'Array') missing = !pages.hasArray;
|
194
194
|
if (type === 'String') missing = !pages.hasString;
|
195
|
+
if (type === 'ByteString') missing = !pages.hasByteString;
|
195
196
|
|
196
197
|
if (missing) {
|
197
198
|
let j = i, depth = 0;
|
package/compiler/prototype.js
CHANGED
@@ -16,7 +16,8 @@ const TYPES = {
|
|
16
16
|
|
17
17
|
// these are not "typeof" types but tracked internally
|
18
18
|
_array: 0x10,
|
19
|
-
_regexp: 0x11
|
19
|
+
_regexp: 0x11,
|
20
|
+
_bytestring: 0x12
|
20
21
|
};
|
21
22
|
|
22
23
|
// todo: turn these into built-ins once arrays and these become less hacky
|
@@ -71,7 +72,7 @@ export const PrototypeFuncs = function() {
|
|
71
72
|
],
|
72
73
|
|
73
74
|
// todo: only for 1 argument
|
74
|
-
push: (pointer, length, wNewMember) => [
|
75
|
+
push: (pointer, length, wNewMember, _1, _2, _3, unusedValue) => [
|
75
76
|
// get memory offset of array at last index (length)
|
76
77
|
...length.getCachedI32(),
|
77
78
|
...number(ValtypeSize[valtype], Valtype.i32),
|
@@ -92,22 +93,28 @@ export const PrototypeFuncs = function() {
|
|
92
93
|
...number(1, Valtype.i32),
|
93
94
|
[ Opcodes.i32_add ],
|
94
95
|
|
95
|
-
...
|
96
|
-
|
96
|
+
...(unusedValue() ? [] : [
|
97
|
+
...length.setCachedI32(),
|
98
|
+
...length.getCachedI32(),
|
99
|
+
])
|
97
100
|
]),
|
98
101
|
|
99
|
-
...
|
100
|
-
|
102
|
+
...(unusedValue() ? [] : [
|
103
|
+
...length.getCachedI32(),
|
104
|
+
Opcodes.i32_from_u
|
105
|
+
])
|
101
106
|
|
102
107
|
// ...length.get()
|
103
108
|
],
|
104
109
|
|
105
|
-
pop: (pointer, length) => [
|
110
|
+
pop: (pointer, length, _1, _2, _3, _4, unusedValue) => [
|
106
111
|
// if length == 0, noop
|
107
112
|
...length.getCachedI32(),
|
108
113
|
[ Opcodes.i32_eqz ],
|
109
114
|
[ Opcodes.if, Blocktype.void ],
|
110
|
-
...
|
115
|
+
...(unusedValue() ? [] : [
|
116
|
+
...number(UNDEFINED),
|
117
|
+
]),
|
111
118
|
[ Opcodes.br, 1 ],
|
112
119
|
[ Opcodes.end ],
|
113
120
|
|
@@ -119,19 +126,23 @@ export const PrototypeFuncs = function() {
|
|
119
126
|
...number(1, Valtype.i32),
|
120
127
|
[ Opcodes.i32_sub ],
|
121
128
|
|
122
|
-
...
|
123
|
-
|
129
|
+
...(unusedValue() ? [] : [
|
130
|
+
...length.setCachedI32(),
|
131
|
+
...length.getCachedI32(),
|
132
|
+
])
|
124
133
|
]),
|
125
134
|
|
126
135
|
// load last element
|
127
|
-
...
|
128
|
-
|
129
|
-
|
136
|
+
...(unusedValue() ? [] : [
|
137
|
+
...length.getCachedI32(),
|
138
|
+
...number(ValtypeSize[valtype], Valtype.i32),
|
139
|
+
[ Opcodes.i32_mul ],
|
130
140
|
|
131
|
-
|
132
|
-
|
141
|
+
...pointer,
|
142
|
+
[ Opcodes.i32_add ],
|
133
143
|
|
134
|
-
|
144
|
+
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ]
|
145
|
+
])
|
135
146
|
],
|
136
147
|
|
137
148
|
shift: (pointer, length) => [
|
@@ -472,8 +483,152 @@ export const PrototypeFuncs = function() {
|
|
472
483
|
this[TYPES.string].at.returnType = TYPES.string;
|
473
484
|
this[TYPES.string].charAt.returnType = TYPES.string;
|
474
485
|
this[TYPES.string].charCodeAt.local = Valtype.i32;
|
486
|
+
this[TYPES.string].charCodeAt.noPointerCache = zeroChecks.charcodeat;
|
475
487
|
|
476
488
|
this[TYPES.string].isWellFormed.local = Valtype.i32;
|
477
489
|
this[TYPES.string].isWellFormed.local2 = Valtype.i32;
|
478
490
|
this[TYPES.string].isWellFormed.returnType = TYPES.boolean;
|
491
|
+
|
492
|
+
if (process.argv.includes('-bytestring')) {
|
493
|
+
this[TYPES._bytestring] = {
|
494
|
+
at: (pointer, length, wIndex, iTmp, _, arrayShell) => {
|
495
|
+
const [ newOut, newPointer ] = arrayShell(1, 'i16');
|
496
|
+
|
497
|
+
return [
|
498
|
+
// setup new/out array
|
499
|
+
...newOut,
|
500
|
+
[ Opcodes.drop ],
|
501
|
+
|
502
|
+
...number(0, Valtype.i32), // base 0 for store later
|
503
|
+
|
504
|
+
...wIndex,
|
505
|
+
Opcodes.i32_to_u,
|
506
|
+
[ Opcodes.local_tee, iTmp ],
|
507
|
+
|
508
|
+
// if index < 0: access index + array length
|
509
|
+
...number(0, Valtype.i32),
|
510
|
+
[ Opcodes.i32_lt_s ],
|
511
|
+
[ Opcodes.if, Blocktype.void ],
|
512
|
+
[ Opcodes.local_get, iTmp ],
|
513
|
+
...length.getCachedI32(),
|
514
|
+
[ Opcodes.i32_add ],
|
515
|
+
[ Opcodes.local_set, iTmp ],
|
516
|
+
[ Opcodes.end ],
|
517
|
+
|
518
|
+
// if still < 0 or >= length: return undefined
|
519
|
+
[ Opcodes.local_get, iTmp ],
|
520
|
+
...number(0, Valtype.i32),
|
521
|
+
[ Opcodes.i32_lt_s ],
|
522
|
+
|
523
|
+
[ Opcodes.local_get, iTmp ],
|
524
|
+
...length.getCachedI32(),
|
525
|
+
[ Opcodes.i32_ge_s ],
|
526
|
+
[ Opcodes.i32_or ],
|
527
|
+
|
528
|
+
[ Opcodes.if, Blocktype.void ],
|
529
|
+
...number(UNDEFINED),
|
530
|
+
[ Opcodes.br, 1 ],
|
531
|
+
[ Opcodes.end ],
|
532
|
+
|
533
|
+
[ Opcodes.local_get, iTmp ],
|
534
|
+
|
535
|
+
...pointer,
|
536
|
+
[ Opcodes.i32_add ],
|
537
|
+
|
538
|
+
// load current string ind {arg}
|
539
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
540
|
+
|
541
|
+
// store to new string ind 0
|
542
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
543
|
+
|
544
|
+
// return new string (pointer)
|
545
|
+
...number(newPointer)
|
546
|
+
];
|
547
|
+
},
|
548
|
+
|
549
|
+
// todo: out of bounds properly
|
550
|
+
charAt: (pointer, length, wIndex, _1, _2, arrayShell) => {
|
551
|
+
const [ newOut, newPointer ] = arrayShell(1, 'i16');
|
552
|
+
|
553
|
+
return [
|
554
|
+
// setup new/out array
|
555
|
+
...newOut,
|
556
|
+
[ Opcodes.drop ],
|
557
|
+
|
558
|
+
...number(0, Valtype.i32), // base 0 for store later
|
559
|
+
|
560
|
+
...wIndex,
|
561
|
+
|
562
|
+
Opcodes.i32_to,
|
563
|
+
|
564
|
+
...pointer,
|
565
|
+
[ Opcodes.i32_add ],
|
566
|
+
|
567
|
+
// load current string ind {arg}
|
568
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
569
|
+
|
570
|
+
// store to new string ind 0
|
571
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
572
|
+
|
573
|
+
// return new string (page)
|
574
|
+
...number(newPointer)
|
575
|
+
];
|
576
|
+
},
|
577
|
+
|
578
|
+
charCodeAt: (pointer, length, wIndex, iTmp) => {
|
579
|
+
return [
|
580
|
+
...wIndex,
|
581
|
+
Opcodes.i32_to,
|
582
|
+
|
583
|
+
...(zeroChecks.charcodeat ? [] : [
|
584
|
+
[ Opcodes.local_set, iTmp ],
|
585
|
+
|
586
|
+
// index < 0
|
587
|
+
...(noUnlikelyChecks ? [] : [
|
588
|
+
[ Opcodes.local_get, iTmp ],
|
589
|
+
...number(0, Valtype.i32),
|
590
|
+
[ Opcodes.i32_lt_s ],
|
591
|
+
]),
|
592
|
+
|
593
|
+
// index >= length
|
594
|
+
[ Opcodes.local_get, iTmp ],
|
595
|
+
...length.getCachedI32(),
|
596
|
+
[ Opcodes.i32_ge_s ],
|
597
|
+
|
598
|
+
...(noUnlikelyChecks ? [] : [ [ Opcodes.i32_or ] ]),
|
599
|
+
[ Opcodes.if, Blocktype.void ],
|
600
|
+
...number(NaN),
|
601
|
+
[ Opcodes.br, 1 ],
|
602
|
+
[ Opcodes.end ],
|
603
|
+
|
604
|
+
[ Opcodes.local_get, iTmp ],
|
605
|
+
]),
|
606
|
+
|
607
|
+
...pointer,
|
608
|
+
[ Opcodes.i32_add ],
|
609
|
+
|
610
|
+
// load current string ind {arg}
|
611
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
612
|
+
Opcodes.i32_from_u
|
613
|
+
];
|
614
|
+
},
|
615
|
+
|
616
|
+
isWellFormed: () => {
|
617
|
+
return [
|
618
|
+
// we know it must be true as it is a bytestring lol
|
619
|
+
...number(1)
|
620
|
+
]
|
621
|
+
}
|
622
|
+
};
|
623
|
+
|
624
|
+
this[TYPES._bytestring].at.local = Valtype.i32;
|
625
|
+
this[TYPES._bytestring].at.returnType = TYPES._bytestring;
|
626
|
+
this[TYPES._bytestring].charAt.returnType = TYPES._bytestring;
|
627
|
+
this[TYPES._bytestring].charCodeAt.local = Valtype.i32;
|
628
|
+
this[TYPES._bytestring].charCodeAt.noPointerCache = zeroChecks.charcodeat;
|
629
|
+
|
630
|
+
this[TYPES._bytestring].isWellFormed.local = Valtype.i32;
|
631
|
+
this[TYPES._bytestring].isWellFormed.local2 = Valtype.i32;
|
632
|
+
this[TYPES._bytestring].isWellFormed.returnType = TYPES.boolean;
|
633
|
+
}
|
479
634
|
};
|
package/compiler/wasmSpec.js
CHANGED
package/compiler/wrap.js
CHANGED
@@ -19,7 +19,8 @@ const TYPES = {
|
|
19
19
|
|
20
20
|
// internal
|
21
21
|
[internalTypeBase]: '_array',
|
22
|
-
[internalTypeBase + 1]: '_regexp'
|
22
|
+
[internalTypeBase + 1]: '_regexp',
|
23
|
+
[internalTypeBase + 2]: '_bytestring'
|
23
24
|
};
|
24
25
|
|
25
26
|
export default async (source, flags = [ 'module' ], customImports = {}, print = str => process.stdout.write(str)) => {
|
@@ -177,6 +178,13 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
|
|
177
178
|
return Array.from(new Uint16Array(memory.buffer, pointer + 4, length)).map(x => String.fromCharCode(x)).join('');
|
178
179
|
}
|
179
180
|
|
181
|
+
case '_bytestring': {
|
182
|
+
const pointer = ret;
|
183
|
+
const length = new Int32Array(memory.buffer, pointer, 1);
|
184
|
+
|
185
|
+
return Array.from(new Uint8Array(memory.buffer, pointer + 4, length)).map(x => String.fromCharCode(x)).join('');
|
186
|
+
}
|
187
|
+
|
180
188
|
case 'function': {
|
181
189
|
// wasm func index, including all imports
|
182
190
|
const func = funcs.find(x => (x.originalIndex ?? x.index) === ret);
|
package/package.json
CHANGED