porffor 0.2.0-08a272e → 0.2.0-1afe9b8
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 +16 -5
- package/compiler/builtins.js +151 -25
- package/compiler/codeGen.js +307 -84
- package/compiler/decompile.js +3 -3
- package/compiler/index.js +1 -1
- package/compiler/opt.js +14 -2
- package/compiler/prototype.js +171 -16
- package/compiler/wasmSpec.js +6 -2
- package/compiler/wrap.js +101 -8
- package/package.json +1 -1
- package/r.js +4 -0
package/compiler/codeGen.js
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
|
2
|
-
import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
|
2
|
+
import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from "./encoding.js";
|
3
3
|
import { operatorOpcode } from "./expression.js";
|
4
4
|
import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
|
5
5
|
import { PrototypeFuncs } from "./prototype.js";
|
@@ -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);
|
@@ -68,7 +68,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
68
68
|
|
69
69
|
case 'ArrowFunctionExpression':
|
70
70
|
case 'FunctionDeclaration':
|
71
|
-
generateFunc(scope, decl);
|
71
|
+
const func = generateFunc(scope, decl);
|
72
|
+
|
73
|
+
if (decl.type.endsWith('Expression')) {
|
74
|
+
return number(func.index);
|
75
|
+
}
|
76
|
+
|
72
77
|
return [];
|
73
78
|
|
74
79
|
case 'BlockStatement':
|
@@ -81,7 +86,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
81
86
|
return generateExp(scope, decl);
|
82
87
|
|
83
88
|
case 'CallExpression':
|
84
|
-
return generateCall(scope, decl, global, name);
|
89
|
+
return generateCall(scope, decl, global, name, valueUnused);
|
85
90
|
|
86
91
|
case 'NewExpression':
|
87
92
|
return generateNew(scope, decl, global, name);
|
@@ -189,19 +194,6 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
189
194
|
}
|
190
195
|
|
191
196
|
return out;
|
192
|
-
},
|
193
|
-
|
194
|
-
__internal_print_type: str => {
|
195
|
-
const type = getType(scope, str) - TYPES.number;
|
196
|
-
|
197
|
-
return [
|
198
|
-
...number(type),
|
199
|
-
[ Opcodes.call, importedFuncs.print ],
|
200
|
-
|
201
|
-
// newline
|
202
|
-
...number(10),
|
203
|
-
[ Opcodes.call, importedFuncs.printChar ]
|
204
|
-
];
|
205
197
|
}
|
206
198
|
}
|
207
199
|
|
@@ -269,25 +261,25 @@ const generateIdent = (scope, decl) => {
|
|
269
261
|
const name = mapName(rawName);
|
270
262
|
let local = scope.locals[rawName];
|
271
263
|
|
272
|
-
if (builtinVars
|
264
|
+
if (Object.hasOwn(builtinVars, name)) {
|
273
265
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
274
266
|
return builtinVars[name];
|
275
267
|
}
|
276
268
|
|
277
|
-
if (builtinFuncs
|
269
|
+
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
278
270
|
// todo: return an actual something
|
279
271
|
return number(1);
|
280
272
|
}
|
281
273
|
|
282
|
-
if (local === undefined) {
|
274
|
+
if (local?.idx === undefined) {
|
283
275
|
// no local var with name
|
284
|
-
if (
|
285
|
-
if (funcIndex
|
276
|
+
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
277
|
+
if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
|
286
278
|
|
287
|
-
if (globals
|
279
|
+
if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
|
288
280
|
}
|
289
281
|
|
290
|
-
if (local === undefined && rawName.startsWith('__')) {
|
282
|
+
if (local?.idx === undefined && rawName.startsWith('__')) {
|
291
283
|
// return undefined if unknown key in already known var
|
292
284
|
let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
|
293
285
|
if (parent.includes('_')) parent = '__' + parent;
|
@@ -296,7 +288,7 @@ const generateIdent = (scope, decl) => {
|
|
296
288
|
if (!parentLookup[1]) return number(UNDEFINED);
|
297
289
|
}
|
298
290
|
|
299
|
-
if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
291
|
+
if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
300
292
|
|
301
293
|
return [ [ Opcodes.local_get, local.idx ] ];
|
302
294
|
};
|
@@ -680,6 +672,15 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
680
672
|
[ Opcodes.i32_eqz ], */
|
681
673
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
682
674
|
],
|
675
|
+
[TYPES._bytestring]: [ // duplicate of string
|
676
|
+
[ Opcodes.local_get, tmp ],
|
677
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
678
|
+
|
679
|
+
// get length
|
680
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
681
|
+
|
682
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
683
|
+
],
|
683
684
|
default: def
|
684
685
|
}, intOut ? Valtype.i32 : valtypeBinary)
|
685
686
|
];
|
@@ -707,6 +708,17 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
707
708
|
[ Opcodes.i32_eqz ],
|
708
709
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
709
710
|
],
|
711
|
+
[TYPES._bytestring]: [ // duplicate of string
|
712
|
+
[ Opcodes.local_get, tmp ],
|
713
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
714
|
+
|
715
|
+
// get length
|
716
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
717
|
+
|
718
|
+
// if length == 0
|
719
|
+
[ Opcodes.i32_eqz ],
|
720
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
721
|
+
],
|
710
722
|
default: [
|
711
723
|
// if value == 0
|
712
724
|
[ Opcodes.local_get, tmp ],
|
@@ -947,6 +959,18 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
947
959
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
948
960
|
}
|
949
961
|
|
962
|
+
if (typeof wasm === 'function') {
|
963
|
+
const scope = {
|
964
|
+
name,
|
965
|
+
params,
|
966
|
+
locals,
|
967
|
+
returns,
|
968
|
+
localInd: allLocals.length,
|
969
|
+
};
|
970
|
+
|
971
|
+
wasm = wasm(scope, { TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString });
|
972
|
+
}
|
973
|
+
|
950
974
|
let baseGlobalIdx, i = 0;
|
951
975
|
for (const type of globalTypes) {
|
952
976
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -1027,7 +1051,8 @@ const TYPES = {
|
|
1027
1051
|
|
1028
1052
|
// these are not "typeof" types but tracked internally
|
1029
1053
|
_array: 0x10,
|
1030
|
-
_regexp: 0x11
|
1054
|
+
_regexp: 0x11,
|
1055
|
+
_bytestring: 0x12
|
1031
1056
|
};
|
1032
1057
|
|
1033
1058
|
const TYPE_NAMES = {
|
@@ -1041,7 +1066,8 @@ const TYPE_NAMES = {
|
|
1041
1066
|
[TYPES.bigint]: 'BigInt',
|
1042
1067
|
|
1043
1068
|
[TYPES._array]: 'Array',
|
1044
|
-
[TYPES._regexp]: 'RegExp'
|
1069
|
+
[TYPES._regexp]: 'RegExp',
|
1070
|
+
[TYPES._bytestring]: 'ByteString'
|
1045
1071
|
};
|
1046
1072
|
|
1047
1073
|
const getType = (scope, _name) => {
|
@@ -1094,6 +1120,8 @@ const getNodeType = (scope, node) => {
|
|
1094
1120
|
if (node.type === 'Literal') {
|
1095
1121
|
if (node.regex) return TYPES._regexp;
|
1096
1122
|
|
1123
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
|
1124
|
+
|
1097
1125
|
return TYPES[typeof node.value];
|
1098
1126
|
}
|
1099
1127
|
|
@@ -1107,6 +1135,15 @@ const getNodeType = (scope, node) => {
|
|
1107
1135
|
|
1108
1136
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1109
1137
|
const name = node.callee.name;
|
1138
|
+
if (!name) {
|
1139
|
+
// iife
|
1140
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1141
|
+
|
1142
|
+
// presume
|
1143
|
+
// todo: warn here?
|
1144
|
+
return TYPES.number;
|
1145
|
+
}
|
1146
|
+
|
1110
1147
|
const func = funcs.find(x => x.name === name);
|
1111
1148
|
|
1112
1149
|
if (func) {
|
@@ -1125,7 +1162,7 @@ const getNodeType = (scope, node) => {
|
|
1125
1162
|
const spl = name.slice(2).split('_');
|
1126
1163
|
|
1127
1164
|
const func = spl[spl.length - 1];
|
1128
|
-
const protoFuncs = Object.
|
1165
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
|
1129
1166
|
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1130
1167
|
}
|
1131
1168
|
|
@@ -1201,7 +1238,7 @@ const getNodeType = (scope, node) => {
|
|
1201
1238
|
if (node.operator === '!') return TYPES.boolean;
|
1202
1239
|
if (node.operator === 'void') return TYPES.undefined;
|
1203
1240
|
if (node.operator === 'delete') return TYPES.boolean;
|
1204
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1241
|
+
if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
|
1205
1242
|
|
1206
1243
|
return TYPES.number;
|
1207
1244
|
}
|
@@ -1210,7 +1247,13 @@ const getNodeType = (scope, node) => {
|
|
1210
1247
|
// hack: if something.length, number type
|
1211
1248
|
if (node.property.name === 'length') return TYPES.number;
|
1212
1249
|
|
1213
|
-
//
|
1250
|
+
// ts hack
|
1251
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1252
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
|
1253
|
+
|
1254
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1255
|
+
|
1256
|
+
// presume
|
1214
1257
|
return TYPES.number;
|
1215
1258
|
}
|
1216
1259
|
|
@@ -1230,8 +1273,8 @@ const getNodeType = (scope, node) => {
|
|
1230
1273
|
const generateLiteral = (scope, decl, global, name) => {
|
1231
1274
|
if (decl.value === null) return number(NULL);
|
1232
1275
|
|
1276
|
+
// hack: just return 1 for regex literals
|
1233
1277
|
if (decl.regex) {
|
1234
|
-
scope.regex[name] = decl.regex;
|
1235
1278
|
return number(1);
|
1236
1279
|
}
|
1237
1280
|
|
@@ -1244,16 +1287,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1244
1287
|
return number(decl.value ? 1 : 0);
|
1245
1288
|
|
1246
1289
|
case 'string':
|
1247
|
-
|
1248
|
-
const rawElements = new Array(str.length);
|
1249
|
-
let j = 0;
|
1250
|
-
for (let i = 0; i < str.length; i++) {
|
1251
|
-
rawElements[i] = str.charCodeAt(i);
|
1252
|
-
}
|
1253
|
-
|
1254
|
-
return makeArray(scope, {
|
1255
|
-
rawElements
|
1256
|
-
}, global, name, false, 'i16')[0];
|
1290
|
+
return makeString(scope, decl.value, global, name);
|
1257
1291
|
|
1258
1292
|
default:
|
1259
1293
|
return todo(`cannot generate literal of type ${typeof decl.value}`);
|
@@ -1274,9 +1308,9 @@ const countLeftover = wasm => {
|
|
1274
1308
|
|
1275
1309
|
if (depth === 0)
|
1276
1310
|
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1277
|
-
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)) {}
|
1311
|
+
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)) {}
|
1278
1312
|
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1279
|
-
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
1313
|
+
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1280
1314
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1281
1315
|
else if (inst[0] === Opcodes.return) count = 0;
|
1282
1316
|
else if (inst[0] === Opcodes.call) {
|
@@ -1302,7 +1336,7 @@ const disposeLeftover = wasm => {
|
|
1302
1336
|
const generateExp = (scope, decl) => {
|
1303
1337
|
const expression = decl.expression;
|
1304
1338
|
|
1305
|
-
const out = generate(scope, expression);
|
1339
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1306
1340
|
disposeLeftover(out);
|
1307
1341
|
|
1308
1342
|
return out;
|
@@ -1360,7 +1394,7 @@ const RTArrayUtil = {
|
|
1360
1394
|
]
|
1361
1395
|
};
|
1362
1396
|
|
1363
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1397
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1364
1398
|
/* const callee = decl.callee;
|
1365
1399
|
const args = decl.arguments;
|
1366
1400
|
|
@@ -1426,8 +1460,8 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1426
1460
|
// literal.func()
|
1427
1461
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1428
1462
|
// megahack for /regex/.func()
|
1429
|
-
|
1430
|
-
|
1463
|
+
const funcName = decl.callee.property.name;
|
1464
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1431
1465
|
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1432
1466
|
|
1433
1467
|
funcIndex[func.name] = func.index;
|
@@ -1469,8 +1503,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1469
1503
|
|
1470
1504
|
if (protoName) {
|
1471
1505
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1472
|
-
|
1473
|
-
if (f) acc[x] = f;
|
1506
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1474
1507
|
return acc;
|
1475
1508
|
}, {});
|
1476
1509
|
|
@@ -1479,10 +1512,18 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1479
1512
|
// use local for cached i32 length as commonly used
|
1480
1513
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1481
1514
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1482
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1483
1515
|
|
1484
1516
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1485
1517
|
|
1518
|
+
const rawPointer = [
|
1519
|
+
...generate(scope, target),
|
1520
|
+
Opcodes.i32_to_u
|
1521
|
+
];
|
1522
|
+
|
1523
|
+
const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
|
1524
|
+
const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
|
1525
|
+
|
1526
|
+
let allOptUnused = true;
|
1486
1527
|
let lengthI32CacheUsed = false;
|
1487
1528
|
const protoBC = {};
|
1488
1529
|
for (const x in protoCands) {
|
@@ -1502,6 +1543,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1502
1543
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1503
1544
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1504
1545
|
|
1546
|
+
let optUnused = false;
|
1505
1547
|
const protoOut = protoFunc(getPointer, {
|
1506
1548
|
getCachedI32: () => {
|
1507
1549
|
lengthI32CacheUsed = true;
|
@@ -1516,10 +1558,15 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1516
1558
|
return makeArray(scope, {
|
1517
1559
|
rawElements: new Array(length)
|
1518
1560
|
}, _global, _name, true, itemType);
|
1561
|
+
}, () => {
|
1562
|
+
optUnused = true;
|
1563
|
+
return unusedValue;
|
1519
1564
|
});
|
1520
1565
|
|
1566
|
+
if (!optUnused) allOptUnused = false;
|
1567
|
+
|
1521
1568
|
protoBC[x] = [
|
1522
|
-
[ Opcodes.block, valtypeBinary ],
|
1569
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1523
1570
|
...protoOut,
|
1524
1571
|
|
1525
1572
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
@@ -1528,11 +1575,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1528
1575
|
];
|
1529
1576
|
}
|
1530
1577
|
|
1531
|
-
|
1532
|
-
...generate(scope, target),
|
1578
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1533
1579
|
|
1534
|
-
|
1535
|
-
|
1580
|
+
return [
|
1581
|
+
...(usePointerCache ? [
|
1582
|
+
...rawPointer,
|
1583
|
+
[ Opcodes.local_set, pointerLocal ],
|
1584
|
+
] : []),
|
1536
1585
|
|
1537
1586
|
...(!lengthI32CacheUsed ? [] : [
|
1538
1587
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1544,7 +1593,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1544
1593
|
|
1545
1594
|
// TODO: error better
|
1546
1595
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1547
|
-
}, valtypeBinary),
|
1596
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1548
1597
|
];
|
1549
1598
|
}
|
1550
1599
|
}
|
@@ -1591,7 +1640,9 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1591
1640
|
const func = funcs.find(x => x.index === idx);
|
1592
1641
|
|
1593
1642
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1594
|
-
const
|
1643
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1644
|
+
const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
|
1645
|
+
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1595
1646
|
|
1596
1647
|
let args = decl.arguments;
|
1597
1648
|
if (func && args.length < paramCount) {
|
@@ -1609,12 +1660,12 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1609
1660
|
let out = [];
|
1610
1661
|
for (const arg of args) {
|
1611
1662
|
out = out.concat(generate(scope, arg));
|
1612
|
-
if (
|
1663
|
+
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1613
1664
|
}
|
1614
1665
|
|
1615
1666
|
out.push([ Opcodes.call, idx ]);
|
1616
1667
|
|
1617
|
-
if (!
|
1668
|
+
if (!typedReturn) {
|
1618
1669
|
// let type;
|
1619
1670
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1620
1671
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1665,14 +1716,102 @@ const knownType = (scope, type) => {
|
|
1665
1716
|
return null;
|
1666
1717
|
};
|
1667
1718
|
|
1719
|
+
const brTable = (input, bc, returns) => {
|
1720
|
+
const out = [];
|
1721
|
+
const keys = Object.keys(bc);
|
1722
|
+
const count = keys.length;
|
1723
|
+
|
1724
|
+
if (count === 1) {
|
1725
|
+
// return [
|
1726
|
+
// ...input,
|
1727
|
+
// ...bc[keys[0]]
|
1728
|
+
// ];
|
1729
|
+
return bc[keys[0]];
|
1730
|
+
}
|
1731
|
+
|
1732
|
+
if (count === 2) {
|
1733
|
+
// just use if else
|
1734
|
+
const other = keys.find(x => x !== 'default');
|
1735
|
+
return [
|
1736
|
+
...input,
|
1737
|
+
...number(other, Valtype.i32),
|
1738
|
+
[ Opcodes.i32_eq ],
|
1739
|
+
[ Opcodes.if, returns ],
|
1740
|
+
...bc[other],
|
1741
|
+
[ Opcodes.else ],
|
1742
|
+
...bc.default,
|
1743
|
+
[ Opcodes.end ]
|
1744
|
+
];
|
1745
|
+
}
|
1746
|
+
|
1747
|
+
for (let i = 0; i < count; i++) {
|
1748
|
+
if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
1749
|
+
else out.push([ Opcodes.block, Blocktype.void ]);
|
1750
|
+
}
|
1751
|
+
|
1752
|
+
const nums = keys.filter(x => +x);
|
1753
|
+
const offset = Math.min(...nums);
|
1754
|
+
const max = Math.max(...nums);
|
1755
|
+
|
1756
|
+
const table = [];
|
1757
|
+
let br = 1;
|
1758
|
+
|
1759
|
+
for (let i = offset; i <= max; i++) {
|
1760
|
+
// if branch for this num, go to that block
|
1761
|
+
if (bc[i]) {
|
1762
|
+
table.push(br);
|
1763
|
+
br++;
|
1764
|
+
continue;
|
1765
|
+
}
|
1766
|
+
|
1767
|
+
// else default
|
1768
|
+
table.push(0);
|
1769
|
+
}
|
1770
|
+
|
1771
|
+
out.push(
|
1772
|
+
[ Opcodes.block, Blocktype.void ],
|
1773
|
+
...input,
|
1774
|
+
...(offset > 0 ? [
|
1775
|
+
...number(offset, Valtype.i32),
|
1776
|
+
[ Opcodes.i32_sub ]
|
1777
|
+
] : []),
|
1778
|
+
[ Opcodes.br_table, ...encodeVector(table), 0 ]
|
1779
|
+
);
|
1780
|
+
|
1781
|
+
// if you can guess why we sort the wrong way and then reverse
|
1782
|
+
// (instead of just sorting the correct way)
|
1783
|
+
// dm me and if you are correct and the first person
|
1784
|
+
// I will somehow shout you out or something
|
1785
|
+
const orderedBc = keys.sort((a, b) => b - a).reverse();
|
1786
|
+
|
1787
|
+
br = count - 1;
|
1788
|
+
for (const x of orderedBc) {
|
1789
|
+
out.push(
|
1790
|
+
[ Opcodes.end ],
|
1791
|
+
...bc[x],
|
1792
|
+
...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
|
1793
|
+
);
|
1794
|
+
br--;
|
1795
|
+
}
|
1796
|
+
|
1797
|
+
return [
|
1798
|
+
...out,
|
1799
|
+
[ Opcodes.end, 'br table end' ]
|
1800
|
+
];
|
1801
|
+
};
|
1802
|
+
|
1668
1803
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1804
|
+
if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
|
1805
|
+
|
1669
1806
|
const known = knownType(scope, type);
|
1670
1807
|
if (known != null) {
|
1671
1808
|
return bc[known] ?? bc.default;
|
1672
1809
|
}
|
1673
1810
|
|
1674
|
-
|
1811
|
+
if (process.argv.includes('-typeswitch-use-brtable'))
|
1812
|
+
return brTable(type, bc, returns);
|
1675
1813
|
|
1814
|
+
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
1676
1815
|
const out = [
|
1677
1816
|
...type,
|
1678
1817
|
[ Opcodes.local_set, tmp ],
|
@@ -1762,6 +1901,8 @@ const extractTypeAnnotation = decl => {
|
|
1762
1901
|
const typeName = type;
|
1763
1902
|
type = typeAnnoToPorfType(type);
|
1764
1903
|
|
1904
|
+
if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
|
1905
|
+
|
1765
1906
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1766
1907
|
|
1767
1908
|
return { type, typeName, elementType };
|
@@ -1778,6 +1919,8 @@ const generateVar = (scope, decl) => {
|
|
1778
1919
|
for (const x of decl.declarations) {
|
1779
1920
|
const name = mapName(x.id.name);
|
1780
1921
|
|
1922
|
+
if (!name) return todo('destructuring is not supported yet');
|
1923
|
+
|
1781
1924
|
if (x.init && isFuncType(x.init.type)) {
|
1782
1925
|
// hack for let a = function () { ... }
|
1783
1926
|
x.init.id = { name };
|
@@ -1916,6 +2059,8 @@ const generateAssign = (scope, decl) => {
|
|
1916
2059
|
];
|
1917
2060
|
}
|
1918
2061
|
|
2062
|
+
if (!name) return todo('destructuring is not supported yet');
|
2063
|
+
|
1919
2064
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1920
2065
|
|
1921
2066
|
if (local === undefined) {
|
@@ -2054,6 +2199,8 @@ const generateUnary = (scope, decl) => {
|
|
2054
2199
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2055
2200
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
2056
2201
|
|
2202
|
+
[TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2203
|
+
|
2057
2204
|
// object and internal types
|
2058
2205
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2059
2206
|
});
|
@@ -2159,8 +2306,10 @@ const generateFor = (scope, decl) => {
|
|
2159
2306
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2160
2307
|
depth.push('for');
|
2161
2308
|
|
2162
|
-
out.push(...generate(scope, decl.test));
|
2163
|
-
|
2309
|
+
if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2310
|
+
else out.push(...number(1, Valtype.i32));
|
2311
|
+
|
2312
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
2164
2313
|
depth.push('if');
|
2165
2314
|
|
2166
2315
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2168,8 +2317,7 @@ const generateFor = (scope, decl) => {
|
|
2168
2317
|
out.push(...generate(scope, decl.body));
|
2169
2318
|
out.push([ Opcodes.end ]);
|
2170
2319
|
|
2171
|
-
out.push(...generate(scope, decl.update));
|
2172
|
-
depth.pop();
|
2320
|
+
if (decl.update) out.push(...generate(scope, decl.update));
|
2173
2321
|
|
2174
2322
|
out.push([ Opcodes.br, 1 ]);
|
2175
2323
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2226,7 +2374,13 @@ const generateForOf = (scope, decl) => {
|
|
2226
2374
|
// setup local for left
|
2227
2375
|
generate(scope, decl.left);
|
2228
2376
|
|
2229
|
-
|
2377
|
+
let leftName = decl.left.declarations?.[0]?.id?.name;
|
2378
|
+
if (!leftName && decl.left.name) {
|
2379
|
+
leftName = decl.left.name;
|
2380
|
+
|
2381
|
+
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2382
|
+
}
|
2383
|
+
|
2230
2384
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2231
2385
|
|
2232
2386
|
depth.push('block');
|
@@ -2235,13 +2389,14 @@ const generateForOf = (scope, decl) => {
|
|
2235
2389
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2236
2390
|
// hack: this is naughty and will break things!
|
2237
2391
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2238
|
-
if (pages.
|
2392
|
+
if (pages.hasAnyString) {
|
2239
2393
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2240
2394
|
rawElements: new Array(1)
|
2241
2395
|
}, isGlobal, leftName, true, 'i16');
|
2242
2396
|
}
|
2243
2397
|
|
2244
2398
|
// set type for local
|
2399
|
+
// todo: optimize away counter and use end pointer
|
2245
2400
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2246
2401
|
[TYPES._array]: [
|
2247
2402
|
...setType(scope, leftName, TYPES.number),
|
@@ -2366,7 +2521,7 @@ const generateThrow = (scope, decl) => {
|
|
2366
2521
|
// hack: throw new X("...") -> throw "..."
|
2367
2522
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2368
2523
|
constructor = decl.argument.callee.name;
|
2369
|
-
message = decl.argument.arguments[0]
|
2524
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2370
2525
|
}
|
2371
2526
|
|
2372
2527
|
if (tags.length === 0) tags.push({
|
@@ -2427,6 +2582,8 @@ const allocPage = (reason, type) => {
|
|
2427
2582
|
|
2428
2583
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2429
2584
|
if (reason.startsWith('string:')) pages.hasString = true;
|
2585
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
2586
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2430
2587
|
|
2431
2588
|
const ind = pages.size;
|
2432
2589
|
pages.set(reason, { ind, type });
|
@@ -2460,7 +2617,8 @@ const StoreOps = {
|
|
2460
2617
|
f64: Opcodes.f64_store,
|
2461
2618
|
|
2462
2619
|
// expects i32 input!
|
2463
|
-
|
2620
|
+
i8: Opcodes.i32_store8,
|
2621
|
+
i16: Opcodes.i32_store16,
|
2464
2622
|
};
|
2465
2623
|
|
2466
2624
|
let data = [];
|
@@ -2479,6 +2637,15 @@ const compileBytes = (val, itemType, signed = true) => {
|
|
2479
2637
|
}
|
2480
2638
|
};
|
2481
2639
|
|
2640
|
+
const getAllocType = itemType => {
|
2641
|
+
switch (itemType) {
|
2642
|
+
case 'i8': return 'bytestring';
|
2643
|
+
case 'i16': return 'string';
|
2644
|
+
|
2645
|
+
default: return 'array';
|
2646
|
+
}
|
2647
|
+
};
|
2648
|
+
|
2482
2649
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2483
2650
|
const out = [];
|
2484
2651
|
|
@@ -2488,7 +2655,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2488
2655
|
|
2489
2656
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2490
2657
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2491
|
-
arrays.set(name, allocPage(`${itemType
|
2658
|
+
arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2492
2659
|
}
|
2493
2660
|
|
2494
2661
|
const pointer = arrays.get(name);
|
@@ -2534,7 +2701,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2534
2701
|
out.push(
|
2535
2702
|
...number(0, Valtype.i32),
|
2536
2703
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2537
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2704
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2538
2705
|
);
|
2539
2706
|
}
|
2540
2707
|
|
@@ -2544,15 +2711,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2544
2711
|
return [ out, pointer ];
|
2545
2712
|
};
|
2546
2713
|
|
2714
|
+
const byteStringable = str => {
|
2715
|
+
if (!process.argv.includes('-bytestring')) return false;
|
2716
|
+
|
2717
|
+
for (let i = 0; i < str.length; i++) {
|
2718
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
2719
|
+
}
|
2720
|
+
|
2721
|
+
return true;
|
2722
|
+
};
|
2723
|
+
|
2547
2724
|
const makeString = (scope, str, global = false, name = '$undeclared') => {
|
2548
2725
|
const rawElements = new Array(str.length);
|
2726
|
+
let byteStringable = process.argv.includes('-bytestring');
|
2549
2727
|
for (let i = 0; i < str.length; i++) {
|
2550
|
-
|
2728
|
+
const c = str.charCodeAt(i);
|
2729
|
+
rawElements[i] = c;
|
2730
|
+
|
2731
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2551
2732
|
}
|
2552
2733
|
|
2553
2734
|
return makeArray(scope, {
|
2554
2735
|
rawElements
|
2555
|
-
}, global, name, false, 'i16')[0];
|
2736
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2556
2737
|
};
|
2557
2738
|
|
2558
2739
|
let arrays = new Map();
|
@@ -2580,10 +2761,13 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2580
2761
|
];
|
2581
2762
|
}
|
2582
2763
|
|
2764
|
+
const object = generate(scope, decl.object);
|
2765
|
+
const property = generate(scope, decl.property);
|
2766
|
+
|
2583
2767
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2584
2768
|
// hack: this is naughty and will break things!
|
2585
2769
|
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2586
|
-
if (pages.
|
2770
|
+
if (pages.hasAnyString) {
|
2587
2771
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2588
2772
|
rawElements: new Array(1)
|
2589
2773
|
}, _global, _name, true, 'i16');
|
@@ -2592,7 +2776,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2592
2776
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2593
2777
|
[TYPES._array]: [
|
2594
2778
|
// get index as valtype
|
2595
|
-
...
|
2779
|
+
...property,
|
2596
2780
|
|
2597
2781
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2598
2782
|
Opcodes.i32_to_u,
|
@@ -2600,7 +2784,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2600
2784
|
[ Opcodes.i32_mul ],
|
2601
2785
|
|
2602
2786
|
...(aotPointer ? [] : [
|
2603
|
-
...
|
2787
|
+
...object,
|
2604
2788
|
Opcodes.i32_to_u,
|
2605
2789
|
[ Opcodes.i32_add ]
|
2606
2790
|
]),
|
@@ -2619,14 +2803,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2619
2803
|
|
2620
2804
|
...number(0, Valtype.i32), // base 0 for store later
|
2621
2805
|
|
2622
|
-
...
|
2623
|
-
|
2806
|
+
...property,
|
2624
2807
|
Opcodes.i32_to_u,
|
2808
|
+
|
2625
2809
|
...number(ValtypeSize.i16, Valtype.i32),
|
2626
2810
|
[ Opcodes.i32_mul ],
|
2627
2811
|
|
2628
2812
|
...(aotPointer ? [] : [
|
2629
|
-
...
|
2813
|
+
...object,
|
2630
2814
|
Opcodes.i32_to_u,
|
2631
2815
|
[ Opcodes.i32_add ]
|
2632
2816
|
]),
|
@@ -2643,8 +2827,36 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2643
2827
|
...number(TYPES.string, Valtype.i32),
|
2644
2828
|
setLastType(scope)
|
2645
2829
|
],
|
2830
|
+
[TYPES._bytestring]: [
|
2831
|
+
// setup new/out array
|
2832
|
+
...newOut,
|
2833
|
+
[ Opcodes.drop ],
|
2834
|
+
|
2835
|
+
...number(0, Valtype.i32), // base 0 for store later
|
2836
|
+
|
2837
|
+
...property,
|
2838
|
+
Opcodes.i32_to_u,
|
2839
|
+
|
2840
|
+
...(aotPointer ? [] : [
|
2841
|
+
...object,
|
2842
|
+
Opcodes.i32_to_u,
|
2843
|
+
[ Opcodes.i32_add ]
|
2844
|
+
]),
|
2845
|
+
|
2846
|
+
// load current string ind {arg}
|
2847
|
+
[ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2848
|
+
|
2849
|
+
// store to new string ind 0
|
2850
|
+
[ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2851
|
+
|
2852
|
+
// return new string (page)
|
2853
|
+
...number(newPointer),
|
2854
|
+
|
2855
|
+
...number(TYPES._bytestring, Valtype.i32),
|
2856
|
+
setLastType(scope)
|
2857
|
+
],
|
2646
2858
|
|
2647
|
-
default:
|
2859
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
|
2648
2860
|
});
|
2649
2861
|
};
|
2650
2862
|
|
@@ -2661,11 +2873,14 @@ const objectHack = node => {
|
|
2661
2873
|
// if object is not identifier or another member exp, give up
|
2662
2874
|
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
2663
2875
|
|
2664
|
-
if (!objectName) objectName = objectHack(node.object)
|
2876
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2665
2877
|
|
2666
2878
|
// if .length, give up (hack within a hack!)
|
2667
2879
|
if (node.property.name === 'length') return node;
|
2668
2880
|
|
2881
|
+
// no object name, give up
|
2882
|
+
if (!objectName) return node;
|
2883
|
+
|
2669
2884
|
const name = '__' + objectName + '_' + node.property.name;
|
2670
2885
|
if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2671
2886
|
|
@@ -2724,10 +2939,8 @@ const generateFunc = (scope, decl) => {
|
|
2724
2939
|
const func = {
|
2725
2940
|
name,
|
2726
2941
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2727
|
-
|
2728
|
-
|
2729
|
-
throws: innerScope.throws,
|
2730
|
-
index: currentFuncIndex++
|
2942
|
+
index: currentFuncIndex++,
|
2943
|
+
...innerScope
|
2731
2944
|
};
|
2732
2945
|
funcIndex[name] = func.index;
|
2733
2946
|
|
@@ -2765,6 +2978,16 @@ const generateCode = (scope, decl) => {
|
|
2765
2978
|
};
|
2766
2979
|
|
2767
2980
|
const internalConstrs = {
|
2981
|
+
Boolean: {
|
2982
|
+
generate: (scope, decl) => {
|
2983
|
+
if (decl.arguments.length === 0) return number(0);
|
2984
|
+
|
2985
|
+
// should generate/run all args
|
2986
|
+
return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
|
2987
|
+
},
|
2988
|
+
type: TYPES.boolean
|
2989
|
+
},
|
2990
|
+
|
2768
2991
|
Array: {
|
2769
2992
|
generate: (scope, decl, global, name) => {
|
2770
2993
|
// new Array(i0, i1, ...)
|