porffor 0.19.8 → 0.19.10
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/compiler/builtins/symbol.ts +10 -0
- package/compiler/codegen.js +343 -269
- package/compiler/generated_builtins.js +526 -519
- package/compiler/precompile.js +71 -36
- package/compiler/wrap.js +9 -5
- package/package.json +1 -1
- package/runner/index.js +1 -1
package/compiler/codegen.js
CHANGED
@@ -39,7 +39,7 @@ const todo = (scope, msg, expectsValue = undefined) => {
|
|
39
39
|
const isFuncType = type =>
|
40
40
|
type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
41
41
|
const hasFuncWithName = name =>
|
42
|
-
funcIndex
|
42
|
+
Object.hasOwn(funcIndex, name) != null || Object.hasOwn(builtinFuncs, name) != null || Object.hasOwn(importedFuncs, name) != null || Object.hasOwn(internalConstrs, name) != null;
|
43
43
|
|
44
44
|
const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
|
45
45
|
switch (decl.type) {
|
@@ -75,6 +75,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
75
75
|
case 'SequenceExpression':
|
76
76
|
return generateSequence(scope, decl);
|
77
77
|
|
78
|
+
case 'ChainExpression':
|
79
|
+
return generateChain(scope, decl);
|
80
|
+
|
78
81
|
case 'CallExpression':
|
79
82
|
return generateCall(scope, decl, global, name, valueUnused);
|
80
83
|
|
@@ -260,11 +263,8 @@ const mapName = x => {
|
|
260
263
|
const lookupName = (scope, _name) => {
|
261
264
|
const name = mapName(_name);
|
262
265
|
|
263
|
-
|
264
|
-
if (
|
265
|
-
|
266
|
-
let global = globals[name];
|
267
|
-
if (global) return [ global, true ];
|
266
|
+
if (Object.hasOwn(scope.locals, name)) return [ scope.locals[name], false ];
|
267
|
+
if (Object.hasOwn(globals, name)) return [ globals[name], true ];
|
268
268
|
|
269
269
|
return [ undefined, undefined ];
|
270
270
|
};
|
@@ -297,7 +297,7 @@ const generateIdent = (scope, decl) => {
|
|
297
297
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
298
298
|
|
299
299
|
let wasm = builtinVars[name];
|
300
|
-
if (typeof wasm === 'function') wasm = asmFuncToAsm(
|
300
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm({ name }, wasm);
|
301
301
|
return wasm.slice();
|
302
302
|
}
|
303
303
|
|
@@ -1119,7 +1119,7 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
1119
1119
|
return out;
|
1120
1120
|
};
|
1121
1121
|
|
1122
|
-
const asmFuncToAsm = (
|
1122
|
+
const asmFuncToAsm = (scope, func) => {
|
1123
1123
|
return func(scope, {
|
1124
1124
|
TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
|
1125
1125
|
builtin: n => {
|
@@ -1131,6 +1131,39 @@ const asmFuncToAsm = (func, scope) => {
|
|
1131
1131
|
|
1132
1132
|
if (idx == null) throw new Error(`builtin('${n}') failed to find a func (inside ${scope.name})`);
|
1133
1133
|
return idx;
|
1134
|
+
},
|
1135
|
+
glbl: (opcode, name, type) => {
|
1136
|
+
if (!globals[name]) {
|
1137
|
+
const idx = globals['#ind']++;
|
1138
|
+
globals[name] = { idx, type };
|
1139
|
+
|
1140
|
+
const tmpIdx = globals['#ind']++;
|
1141
|
+
globals[name + '#glbl_inited'] = { idx: tmpIdx, type: Valtype.i32 };
|
1142
|
+
|
1143
|
+
if (scope.globalInits[name]) return [
|
1144
|
+
[ Opcodes.global_get, tmpIdx ],
|
1145
|
+
[ Opcodes.i32_eqz ],
|
1146
|
+
[ Opcodes.if, Blocktype.void ],
|
1147
|
+
...asmFuncToAsm(scope, scope.globalInits[name]),
|
1148
|
+
...number(1, Valtype.i32),
|
1149
|
+
[ Opcodes.global_set, tmpIdx ],
|
1150
|
+
[ Opcodes.end ],
|
1151
|
+
|
1152
|
+
[ opcode, globals[name].idx ]
|
1153
|
+
];
|
1154
|
+
}
|
1155
|
+
|
1156
|
+
return [
|
1157
|
+
[ opcode, globals[name].idx ]
|
1158
|
+
];
|
1159
|
+
},
|
1160
|
+
loc: (name, type) => {
|
1161
|
+
if (!scope.locals[name]) {
|
1162
|
+
const idx = scope.localInd++;
|
1163
|
+
scope.locals[name] = { idx, type };
|
1164
|
+
}
|
1165
|
+
|
1166
|
+
return scope.locals[name].idx;
|
1134
1167
|
}
|
1135
1168
|
});
|
1136
1169
|
};
|
@@ -1165,7 +1198,8 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
1165
1198
|
internal: true,
|
1166
1199
|
index: currentFuncIndex++,
|
1167
1200
|
table,
|
1168
|
-
constr
|
1201
|
+
constr,
|
1202
|
+
globalInits
|
1169
1203
|
};
|
1170
1204
|
|
1171
1205
|
funcs.push(func);
|
@@ -1173,7 +1207,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
1173
1207
|
|
1174
1208
|
if (typeof wasm === 'function') {
|
1175
1209
|
if (globalThis.precompile) wasm = [];
|
1176
|
-
else wasm = asmFuncToAsm(
|
1210
|
+
else wasm = asmFuncToAsm(func, wasm);
|
1177
1211
|
}
|
1178
1212
|
|
1179
1213
|
let baseGlobalIdx, i = 0;
|
@@ -1273,14 +1307,14 @@ const getType = (scope, _name) => {
|
|
1273
1307
|
// if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
|
1274
1308
|
|
1275
1309
|
if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
|
1276
|
-
if (scope.locals
|
1310
|
+
if (Object.hasOwn(scope.locals, name)) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
1277
1311
|
|
1278
1312
|
if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
|
1279
|
-
if (globals
|
1313
|
+
if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1280
1314
|
|
1281
1315
|
let type = TYPES.undefined;
|
1282
|
-
if (builtinVars
|
1283
|
-
if (builtinFuncs
|
1316
|
+
if (Object.hasOwn(builtinVars, name)) type = builtinVars[name].type ?? TYPES.number;
|
1317
|
+
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(importedFuncs, name) || Object.hasOwn(funcIndex, name) || Object.hasOwn(internalConstrs, name)) type = TYPES.function;
|
1284
1318
|
|
1285
1319
|
if (isExistingProtoFunc(name)) type = TYPES.function;
|
1286
1320
|
|
@@ -1293,13 +1327,13 @@ const setType = (scope, _name, type) => {
|
|
1293
1327
|
const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
|
1294
1328
|
|
1295
1329
|
if (typedInput && scope.locals[name]?.metadata?.type != null) return [];
|
1296
|
-
if (scope.locals
|
1330
|
+
if (Object.hasOwn(scope.locals, name)) return [
|
1297
1331
|
...out,
|
1298
1332
|
[ Opcodes.local_set, scope.locals[name + '#type'].idx ]
|
1299
1333
|
];
|
1300
1334
|
|
1301
1335
|
if (typedInput && globals[name]?.metadata?.type != null) return [];
|
1302
|
-
if (globals
|
1336
|
+
if (Object.hasOwn(globals, name)) return [
|
1303
1337
|
...out,
|
1304
1338
|
[ Opcodes.global_set, globals[name + '#type'].idx ]
|
1305
1339
|
];
|
@@ -1342,7 +1376,7 @@ const getNodeType = (scope, node) => {
|
|
1342
1376
|
|
1343
1377
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1344
1378
|
const name = node.callee.name;
|
1345
|
-
if (
|
1379
|
+
if (name == null) {
|
1346
1380
|
// iife
|
1347
1381
|
if (scope.locals['#last_type']) return getLastType(scope);
|
1348
1382
|
|
@@ -1356,8 +1390,8 @@ const getNodeType = (scope, node) => {
|
|
1356
1390
|
if (func.returnType != null) return func.returnType;
|
1357
1391
|
}
|
1358
1392
|
|
1359
|
-
if (builtinFuncs
|
1360
|
-
if (internalConstrs
|
1393
|
+
if (Object.hasOwn(builtinFuncs, name) && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1394
|
+
if (Object.hasOwn(internalConstrs, name)) return internalConstrs[name].type;
|
1361
1395
|
|
1362
1396
|
// check if this is a prototype function
|
1363
1397
|
// if so and there is only one impl (eg charCodeAt)
|
@@ -1601,6 +1635,10 @@ const generateSequence = (scope, decl) => {
|
|
1601
1635
|
return out;
|
1602
1636
|
};
|
1603
1637
|
|
1638
|
+
const generateChain = (scope, decl) => {
|
1639
|
+
return generate(scope, decl.expression);
|
1640
|
+
};
|
1641
|
+
|
1604
1642
|
const CTArrayUtil = {
|
1605
1643
|
getLengthI32: pointer => [
|
1606
1644
|
...number(0, Valtype.i32),
|
@@ -1927,120 +1965,219 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1927
1965
|
// TODO: only allows callee as identifier
|
1928
1966
|
// if (!name) return todo(scope, `only identifier callees (got ${decl.callee.type})`);
|
1929
1967
|
|
1930
|
-
let idx
|
1931
|
-
if (
|
1932
|
-
if (
|
1933
|
-
if (
|
1968
|
+
let idx;
|
1969
|
+
if (Object.hasOwn(funcIndex, name)) idx = funcIndex[name];
|
1970
|
+
else if (Object.hasOwn(importedFuncs, name)) idx = importedFuncs[name];
|
1971
|
+
else if (Object.hasOwn(builtinFuncs, name)) {
|
1972
|
+
if (builtinFuncs[name].floatOnly && valtype !== 'f64') throw new Error(`Cannot use built-in ${unhackName(name)} with integer valtype`);
|
1973
|
+
if (decl._new && !builtinFuncs[name].constr) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a constructor`, true);
|
1934
1974
|
|
1935
|
-
|
1936
|
-
|
1937
|
-
|
1975
|
+
includeBuiltin(scope, name);
|
1976
|
+
idx = funcIndex[name];
|
1977
|
+
} else if (Object.hasOwn(internalConstrs, name)) {
|
1978
|
+
if (decl._new && internalConstrs[name].notConstr) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a constructor`, true);
|
1979
|
+
return internalConstrs[name].generate(scope, decl, _global, _name);
|
1980
|
+
} else if (!decl._new && name && name.startsWith('__Porffor_wasm_')) {
|
1981
|
+
const wasmOps = {
|
1982
|
+
// pointer, align, offset
|
1983
|
+
i32_load: { imms: 2, args: [ true ], returns: 1 },
|
1984
|
+
// pointer, value, align, offset
|
1985
|
+
i32_store: { imms: 2, args: [ true, true ], returns: 0 },
|
1986
|
+
// pointer, align, offset
|
1987
|
+
i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
|
1988
|
+
// pointer, value, align, offset
|
1989
|
+
i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
|
1990
|
+
// pointer, align, offset
|
1991
|
+
i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
|
1992
|
+
// pointer, value, align, offset
|
1993
|
+
i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
|
1994
|
+
|
1995
|
+
// pointer, align, offset
|
1996
|
+
f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
|
1997
|
+
// pointer, value, align, offset
|
1998
|
+
f64_store: { imms: 2, args: [ true, false ], returns: 0 },
|
1999
|
+
|
2000
|
+
// value
|
2001
|
+
i32_const: { imms: 1, args: [], returns: 0 },
|
2002
|
+
};
|
1938
2003
|
|
1939
|
-
|
1940
|
-
if (decl._new && internalConstrs[name].notConstr) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a constructor`, true);
|
1941
|
-
return internalConstrs[name].generate(scope, decl, _global, _name);
|
1942
|
-
}
|
2004
|
+
const opName = name.slice('__Porffor_wasm_'.length);
|
1943
2005
|
|
1944
|
-
|
1945
|
-
|
1946
|
-
// pointer, align, offset
|
1947
|
-
i32_load: { imms: 2, args: [ true ], returns: 1 },
|
1948
|
-
// pointer, value, align, offset
|
1949
|
-
i32_store: { imms: 2, args: [ true, true ], returns: 0 },
|
1950
|
-
// pointer, align, offset
|
1951
|
-
i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
|
1952
|
-
// pointer, value, align, offset
|
1953
|
-
i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
|
1954
|
-
// pointer, align, offset
|
1955
|
-
i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
|
1956
|
-
// pointer, value, align, offset
|
1957
|
-
i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
|
1958
|
-
|
1959
|
-
// pointer, align, offset
|
1960
|
-
f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
|
1961
|
-
// pointer, value, align, offset
|
1962
|
-
f64_store: { imms: 2, args: [ true, false ], returns: 0 },
|
1963
|
-
|
1964
|
-
// value
|
1965
|
-
i32_const: { imms: 1, args: [], returns: 0 },
|
1966
|
-
};
|
2006
|
+
if (wasmOps[opName]) {
|
2007
|
+
const op = wasmOps[opName];
|
1967
2008
|
|
1968
|
-
|
2009
|
+
const argOut = [];
|
2010
|
+
for (let i = 0; i < op.args.length; i++) argOut.push(
|
2011
|
+
...generate(scope, decl.arguments[i]),
|
2012
|
+
...(op.args[i] ? [ Opcodes.i32_to ] : [])
|
2013
|
+
);
|
1969
2014
|
|
1970
|
-
|
1971
|
-
|
2015
|
+
// literals only
|
2016
|
+
const imms = decl.arguments.slice(op.args.length).map(x => x.value);
|
1972
2017
|
|
1973
|
-
|
1974
|
-
|
1975
|
-
|
1976
|
-
|
1977
|
-
|
2018
|
+
return [
|
2019
|
+
...argOut,
|
2020
|
+
[ Opcodes[opName], ...imms ],
|
2021
|
+
...(new Array(op.returns).fill(Opcodes.i32_from))
|
2022
|
+
];
|
2023
|
+
}
|
2024
|
+
} else {
|
2025
|
+
if (!Prefs.indirectCalls) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1978
2026
|
|
1979
|
-
//
|
1980
|
-
const imms = decl.arguments.slice(op.args.length).map(x => x.value);
|
2027
|
+
// todo: only works when function uses typedParams and typedReturns
|
1981
2028
|
|
1982
|
-
|
1983
|
-
|
1984
|
-
|
1985
|
-
|
1986
|
-
|
1987
|
-
|
1988
|
-
|
2029
|
+
const indirectMode = Prefs.indirectCallMode ?? 'vararg';
|
2030
|
+
// options: vararg, strict
|
2031
|
+
// - strict: simpler, smaller size usage, no func lut needed.
|
2032
|
+
// ONLY works when arg count of call == arg count of function being called
|
2033
|
+
// - vararg: large size usage, cursed.
|
2034
|
+
// works when arg count of call != arg count of function being called*
|
2035
|
+
// * most of the time, some edgecases
|
2036
|
+
|
2037
|
+
funcs.table = true;
|
2038
|
+
scope.table = true;
|
1989
2039
|
|
1990
|
-
|
1991
|
-
|
2040
|
+
let args = decl.arguments;
|
2041
|
+
let locals = [];
|
1992
2042
|
|
1993
|
-
|
2043
|
+
if (indirectMode === 'vararg') {
|
2044
|
+
const minArgc = Prefs.indirectCallMinArgc ?? 3;
|
1994
2045
|
|
1995
|
-
|
1996
|
-
|
1997
|
-
|
1998
|
-
|
1999
|
-
// - vararg: large size usage, cursed.
|
2000
|
-
// works when arg count of call != arg count of function being called*
|
2001
|
-
// * most of the time, some edgecases
|
2046
|
+
if (args.length < minArgc) {
|
2047
|
+
args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
|
2048
|
+
}
|
2049
|
+
}
|
2002
2050
|
|
2003
|
-
|
2004
|
-
|
2051
|
+
for (let i = 0; i < args.length; i++) {
|
2052
|
+
const arg = args[i];
|
2053
|
+
out = out.concat(generate(scope, arg));
|
2005
2054
|
|
2006
|
-
|
2007
|
-
|
2055
|
+
if (valtypeBinary !== Valtype.i32 && (
|
2056
|
+
(builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
|
2057
|
+
(importedFuncs[name] && name.startsWith('profile'))
|
2058
|
+
)) {
|
2059
|
+
out.push(Opcodes.i32_to);
|
2060
|
+
}
|
2008
2061
|
|
2009
|
-
|
2010
|
-
const minArgc = Prefs.indirectCallMinArgc ?? 3;
|
2062
|
+
out = out.concat(getNodeType(scope, arg));
|
2011
2063
|
|
2012
|
-
|
2013
|
-
|
2014
|
-
|
2015
|
-
}
|
2064
|
+
if (indirectMode === 'vararg') {
|
2065
|
+
const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
|
2066
|
+
const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
|
2016
2067
|
|
2017
|
-
|
2018
|
-
const arg = args[i];
|
2019
|
-
out = out.concat(generate(scope, arg));
|
2068
|
+
locals.push([valLocal, typeLocal]);
|
2020
2069
|
|
2021
|
-
|
2022
|
-
|
2023
|
-
|
2024
|
-
|
2025
|
-
|
2070
|
+
out.push(
|
2071
|
+
[ Opcodes.local_set, typeLocal ],
|
2072
|
+
[ Opcodes.local_set, valLocal ]
|
2073
|
+
);
|
2074
|
+
}
|
2026
2075
|
}
|
2027
2076
|
|
2028
|
-
|
2077
|
+
if (indirectMode === 'strict') {
|
2078
|
+
return [
|
2079
|
+
...generate(scope, decl.callee),
|
2080
|
+
[ Opcodes.local_set, localTmp(scope, '#indirect_callee') ],
|
2081
|
+
|
2082
|
+
...typeSwitch(scope, getNodeType(scope, decl.callee), {
|
2083
|
+
[TYPES.function]: [
|
2084
|
+
...out,
|
2085
|
+
|
2086
|
+
[ Opcodes.local_get, localTmp(scope, '#indirect_callee') ],
|
2087
|
+
...generate(scope, decl.callee),
|
2088
|
+
Opcodes.i32_to_u,
|
2089
|
+
[ Opcodes.call_indirect, args.length, 0 ],
|
2090
|
+
...setLastType(scope)
|
2091
|
+
],
|
2092
|
+
default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
|
2093
|
+
})
|
2094
|
+
];
|
2095
|
+
}
|
2029
2096
|
|
2030
|
-
|
2031
|
-
|
2032
|
-
|
2097
|
+
// hi, I will now explain how vararg mode works:
|
2098
|
+
// wasm's indirect_call instruction requires you know the func type at compile-time
|
2099
|
+
// since we have varargs (variable argument count), we do not know it.
|
2100
|
+
// we could just store args in memory and not use wasm func args,
|
2101
|
+
// but that is slow (probably) and breaks js exports.
|
2102
|
+
// instead, we generate every* possibility of argc and use different indirect_call
|
2103
|
+
// ops for each one, with type depending on argc for that branch.
|
2104
|
+
// then we load the argc for the wanted function from a memory lut,
|
2105
|
+
// and call the branch with the matching argc we require.
|
2106
|
+
// sorry, yes it is very cursed (and size inefficient), but indirect calls
|
2107
|
+
// are kind of rare anyway (mostly callbacks) so I am not concerned atm.
|
2108
|
+
// *for argc 0-3, in future (todo:) the max number should be
|
2109
|
+
// dynamically changed to the max argc of any func in the js file.
|
2110
|
+
|
2111
|
+
const funcLocal = localTmp(scope, '#indirect_func', Valtype.i32);
|
2112
|
+
const flags = localTmp(scope, '#indirect_flags', Valtype.i32);
|
2113
|
+
|
2114
|
+
const gen = argc => {
|
2115
|
+
const argsOut = [];
|
2116
|
+
for (let i = 0; i < argc; i++) {
|
2117
|
+
argsOut.push(
|
2118
|
+
[ Opcodes.local_get, locals[i][0] ],
|
2119
|
+
[ Opcodes.local_get, locals[i][1] ]
|
2120
|
+
);
|
2121
|
+
}
|
2033
2122
|
|
2034
|
-
|
2123
|
+
const checkFlag = (flag, pass, fail) => [
|
2124
|
+
[ Opcodes.local_get, flags ],
|
2125
|
+
...number(flag, Valtype.i32),
|
2126
|
+
[ Opcodes.i32_and ],
|
2127
|
+
[ Opcodes.if, valtypeBinary ],
|
2128
|
+
...pass,
|
2129
|
+
[ Opcodes.else ],
|
2130
|
+
...fail,
|
2131
|
+
[ Opcodes.end ]
|
2132
|
+
];
|
2035
2133
|
|
2036
|
-
|
2037
|
-
|
2038
|
-
|
2134
|
+
// pain.
|
2135
|
+
// return checkFlag(0b10, [ // constr
|
2136
|
+
// [ Opcodes.i32_const, decl._new ? 1 : 0 ],
|
2137
|
+
// ...argsOut,
|
2138
|
+
// [ Opcodes.local_get, funcLocal ],
|
2139
|
+
// [ Opcodes.call_indirect, argc, 0, 'constr' ],
|
2140
|
+
// ...setLastType(scope),
|
2141
|
+
// ], [
|
2142
|
+
// ...argsOut,
|
2143
|
+
// [ Opcodes.local_get, funcLocal ],
|
2144
|
+
// [ Opcodes.call_indirect, argc, 0 ],
|
2145
|
+
// ...setLastType(scope),
|
2146
|
+
// ]);
|
2147
|
+
|
2148
|
+
return checkFlag(0b1, // no type return
|
2149
|
+
checkFlag(0b10, [ // no type return & constr
|
2150
|
+
[ Opcodes.i32_const, decl._new ? 1 : 0 ],
|
2151
|
+
...argsOut,
|
2152
|
+
[ Opcodes.local_get, funcLocal ],
|
2153
|
+
[ Opcodes.call_indirect, argc, 0, 'no_type_return', 'constr' ],
|
2154
|
+
], [
|
2155
|
+
...argsOut,
|
2156
|
+
[ Opcodes.local_get, funcLocal ],
|
2157
|
+
[ Opcodes.call_indirect, argc, 0, 'no_type_return' ]
|
2158
|
+
]),
|
2159
|
+
checkFlag(0b10, [ // type return & constr
|
2160
|
+
[ Opcodes.i32_const, decl._new ? 1 : 0 ],
|
2161
|
+
...argsOut,
|
2162
|
+
[ Opcodes.local_get, funcLocal ],
|
2163
|
+
[ Opcodes.call_indirect, argc, 0, 'constr' ],
|
2164
|
+
...setLastType(scope),
|
2165
|
+
], [
|
2166
|
+
...argsOut,
|
2167
|
+
[ Opcodes.local_get, funcLocal ],
|
2168
|
+
[ Opcodes.call_indirect, argc, 0 ],
|
2169
|
+
...setLastType(scope),
|
2170
|
+
]),
|
2039
2171
|
);
|
2172
|
+
};
|
2173
|
+
|
2174
|
+
const tableBc = {};
|
2175
|
+
for (let i = 0; i <= args.length; i++) {
|
2176
|
+
tableBc[i] = gen(i);
|
2040
2177
|
}
|
2041
|
-
}
|
2042
2178
|
|
2043
|
-
|
2179
|
+
// todo/perf: check if we should use br_table here or just generate our own big if..elses
|
2180
|
+
|
2044
2181
|
return [
|
2045
2182
|
...generate(scope, decl.callee),
|
2046
2183
|
[ Opcodes.local_set, localTmp(scope, '#indirect_callee') ],
|
@@ -2050,145 +2187,42 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
2050
2187
|
...out,
|
2051
2188
|
|
2052
2189
|
[ Opcodes.local_get, localTmp(scope, '#indirect_callee') ],
|
2053
|
-
...generate(scope, decl.callee),
|
2054
2190
|
Opcodes.i32_to_u,
|
2055
|
-
[ Opcodes.
|
2056
|
-
|
2191
|
+
[ Opcodes.local_set, funcLocal ],
|
2192
|
+
|
2193
|
+
// get if func we are calling is a constructor or not
|
2194
|
+
[ Opcodes.local_get, funcLocal ],
|
2195
|
+
...number(3, Valtype.i32),
|
2196
|
+
[ Opcodes.i32_mul ],
|
2197
|
+
...number(2, Valtype.i32),
|
2198
|
+
[ Opcodes.i32_add ],
|
2199
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(allocPage(scope, 'func lut') * pageSize), 'read func lut' ],
|
2200
|
+
[ Opcodes.local_set, flags ],
|
2201
|
+
|
2202
|
+
// check if non-constructor was called with new, if so throw
|
2203
|
+
[ Opcodes.local_get, flags ],
|
2204
|
+
...number(0b10, Valtype.i32),
|
2205
|
+
[ Opcodes.i32_and ],
|
2206
|
+
[ Opcodes.i32_eqz ],
|
2207
|
+
[ Opcodes.i32_const, decl._new ? 1 : 0 ],
|
2208
|
+
[ Opcodes.i32_and ],
|
2209
|
+
[ Opcodes.if, Blocktype.void ],
|
2210
|
+
...internalThrow(scope, 'TypeError', `${unhackName(name)} is not a constructor`),
|
2211
|
+
[ Opcodes.end ],
|
2212
|
+
|
2213
|
+
...brTable([
|
2214
|
+
// get argc of func we are calling
|
2215
|
+
[ Opcodes.local_get, funcLocal ],
|
2216
|
+
...number(3, Valtype.i32),
|
2217
|
+
[ Opcodes.i32_mul ],
|
2218
|
+
[ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func lut') * pageSize), 'read func lut' ]
|
2219
|
+
], tableBc, valtypeBinary)
|
2057
2220
|
],
|
2058
2221
|
default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
|
2059
2222
|
})
|
2060
2223
|
];
|
2061
2224
|
}
|
2062
2225
|
|
2063
|
-
// hi, I will now explain how vararg mode works:
|
2064
|
-
// wasm's indirect_call instruction requires you know the func type at compile-time
|
2065
|
-
// since we have varargs (variable argument count), we do not know it.
|
2066
|
-
// we could just store args in memory and not use wasm func args,
|
2067
|
-
// but that is slow (probably) and breaks js exports.
|
2068
|
-
// instead, we generate every* possibility of argc and use different indirect_call
|
2069
|
-
// ops for each one, with type depending on argc for that branch.
|
2070
|
-
// then we load the argc for the wanted function from a memory lut,
|
2071
|
-
// and call the branch with the matching argc we require.
|
2072
|
-
// sorry, yes it is very cursed (and size inefficient), but indirect calls
|
2073
|
-
// are kind of rare anyway (mostly callbacks) so I am not concerned atm.
|
2074
|
-
// *for argc 0-3, in future (todo:) the max number should be
|
2075
|
-
// dynamically changed to the max argc of any func in the js file.
|
2076
|
-
|
2077
|
-
const funcLocal = localTmp(scope, '#indirect_func', Valtype.i32);
|
2078
|
-
const flags = localTmp(scope, '#indirect_flags', Valtype.i32);
|
2079
|
-
|
2080
|
-
const gen = argc => {
|
2081
|
-
const argsOut = [];
|
2082
|
-
for (let i = 0; i < argc; i++) {
|
2083
|
-
argsOut.push(
|
2084
|
-
[ Opcodes.local_get, locals[i][0] ],
|
2085
|
-
[ Opcodes.local_get, locals[i][1] ]
|
2086
|
-
);
|
2087
|
-
}
|
2088
|
-
|
2089
|
-
const checkFlag = (flag, pass, fail) => [
|
2090
|
-
[ Opcodes.local_get, flags ],
|
2091
|
-
...number(flag, Valtype.i32),
|
2092
|
-
[ Opcodes.i32_and ],
|
2093
|
-
[ Opcodes.if, valtypeBinary ],
|
2094
|
-
...pass,
|
2095
|
-
[ Opcodes.else ],
|
2096
|
-
...fail,
|
2097
|
-
[ Opcodes.end ]
|
2098
|
-
];
|
2099
|
-
|
2100
|
-
// pain.
|
2101
|
-
// return checkFlag(0b10, [ // constr
|
2102
|
-
// [ Opcodes.i32_const, decl._new ? 1 : 0 ],
|
2103
|
-
// ...argsOut,
|
2104
|
-
// [ Opcodes.local_get, funcLocal ],
|
2105
|
-
// [ Opcodes.call_indirect, argc, 0, 'constr' ],
|
2106
|
-
// ...setLastType(scope),
|
2107
|
-
// ], [
|
2108
|
-
// ...argsOut,
|
2109
|
-
// [ Opcodes.local_get, funcLocal ],
|
2110
|
-
// [ Opcodes.call_indirect, argc, 0 ],
|
2111
|
-
// ...setLastType(scope),
|
2112
|
-
// ]);
|
2113
|
-
|
2114
|
-
return checkFlag(0b1, // no type return
|
2115
|
-
checkFlag(0b10, [ // no type return & constr
|
2116
|
-
[ Opcodes.i32_const, decl._new ? 1 : 0 ],
|
2117
|
-
...argsOut,
|
2118
|
-
[ Opcodes.local_get, funcLocal ],
|
2119
|
-
[ Opcodes.call_indirect, argc, 0, 'no_type_return', 'constr' ],
|
2120
|
-
], [
|
2121
|
-
...argsOut,
|
2122
|
-
[ Opcodes.local_get, funcLocal ],
|
2123
|
-
[ Opcodes.call_indirect, argc, 0, 'no_type_return' ]
|
2124
|
-
]),
|
2125
|
-
checkFlag(0b10, [ // type return & constr
|
2126
|
-
[ Opcodes.i32_const, decl._new ? 1 : 0 ],
|
2127
|
-
...argsOut,
|
2128
|
-
[ Opcodes.local_get, funcLocal ],
|
2129
|
-
[ Opcodes.call_indirect, argc, 0, 'constr' ],
|
2130
|
-
...setLastType(scope),
|
2131
|
-
], [
|
2132
|
-
...argsOut,
|
2133
|
-
[ Opcodes.local_get, funcLocal ],
|
2134
|
-
[ Opcodes.call_indirect, argc, 0 ],
|
2135
|
-
...setLastType(scope),
|
2136
|
-
]),
|
2137
|
-
);
|
2138
|
-
};
|
2139
|
-
|
2140
|
-
const tableBc = {};
|
2141
|
-
for (let i = 0; i <= args.length; i++) {
|
2142
|
-
tableBc[i] = gen(i);
|
2143
|
-
}
|
2144
|
-
|
2145
|
-
// todo/perf: check if we should use br_table here or just generate our own big if..elses
|
2146
|
-
|
2147
|
-
return [
|
2148
|
-
...generate(scope, decl.callee),
|
2149
|
-
[ Opcodes.local_set, localTmp(scope, '#indirect_callee') ],
|
2150
|
-
|
2151
|
-
...typeSwitch(scope, getNodeType(scope, decl.callee), {
|
2152
|
-
[TYPES.function]: [
|
2153
|
-
...out,
|
2154
|
-
|
2155
|
-
[ Opcodes.local_get, localTmp(scope, '#indirect_callee') ],
|
2156
|
-
Opcodes.i32_to_u,
|
2157
|
-
[ Opcodes.local_set, funcLocal ],
|
2158
|
-
|
2159
|
-
// get if func we are calling is a constructor or not
|
2160
|
-
[ Opcodes.local_get, funcLocal ],
|
2161
|
-
...number(3, Valtype.i32),
|
2162
|
-
[ Opcodes.i32_mul ],
|
2163
|
-
...number(2, Valtype.i32),
|
2164
|
-
[ Opcodes.i32_add ],
|
2165
|
-
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(allocPage(scope, 'func lut') * pageSize), 'read func lut' ],
|
2166
|
-
[ Opcodes.local_set, flags ],
|
2167
|
-
|
2168
|
-
// check if non-constructor was called with new, if so throw
|
2169
|
-
[ Opcodes.local_get, flags ],
|
2170
|
-
...number(0b10, Valtype.i32),
|
2171
|
-
[ Opcodes.i32_and ],
|
2172
|
-
[ Opcodes.i32_eqz ],
|
2173
|
-
[ Opcodes.i32_const, decl._new ? 1 : 0 ],
|
2174
|
-
[ Opcodes.i32_and ],
|
2175
|
-
[ Opcodes.if, Blocktype.void ],
|
2176
|
-
...internalThrow(scope, 'TypeError', `${unhackName(name)} is not a constructor`),
|
2177
|
-
[ Opcodes.end ],
|
2178
|
-
|
2179
|
-
...brTable([
|
2180
|
-
// get argc of func we are calling
|
2181
|
-
[ Opcodes.local_get, funcLocal ],
|
2182
|
-
...number(3, Valtype.i32),
|
2183
|
-
[ Opcodes.i32_mul ],
|
2184
|
-
[ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func lut') * pageSize), 'read func lut' ]
|
2185
|
-
], tableBc, valtypeBinary)
|
2186
|
-
],
|
2187
|
-
default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
|
2188
|
-
})
|
2189
|
-
];
|
2190
|
-
}
|
2191
|
-
|
2192
2226
|
const func = funcs[idx - importedFuncs.length]; // idx === scope.index ? scope : funcs.find(x => x.index === idx);
|
2193
2227
|
const userFunc = func && !func.internal;
|
2194
2228
|
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
@@ -2464,7 +2498,7 @@ const allocVar = (scope, name, global = false, type = true) => {
|
|
2464
2498
|
const target = global ? globals : scope.locals;
|
2465
2499
|
|
2466
2500
|
// already declared
|
2467
|
-
if (target
|
2501
|
+
if (Object.hasOwn(target, name)) {
|
2468
2502
|
// parser should catch this but sanity check anyway
|
2469
2503
|
// if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
|
2470
2504
|
|
@@ -2660,7 +2694,7 @@ const generateVar = (scope, decl) => {
|
|
2660
2694
|
continue;
|
2661
2695
|
}
|
2662
2696
|
|
2663
|
-
if (topLevel && builtinVars
|
2697
|
+
if (topLevel && Object.hasOwn(builtinVars, name)) {
|
2664
2698
|
// cannot redeclare
|
2665
2699
|
if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
|
2666
2700
|
|
@@ -2681,21 +2715,23 @@ const generateVar = (scope, decl) => {
|
|
2681
2715
|
if (x.init) {
|
2682
2716
|
const alreadyArray = scope.arrays?.get(name) != null;
|
2683
2717
|
|
2684
|
-
const
|
2718
|
+
const newOut = generate(scope, x.init, global, name);
|
2685
2719
|
if (!alreadyArray && scope.arrays?.get(name) != null) {
|
2686
2720
|
// hack to set local as pointer before
|
2687
|
-
|
2688
|
-
if (
|
2689
|
-
|
2690
|
-
generated.push([ Opcodes.drop ]);
|
2691
|
-
|
2692
|
-
out = out.concat(generated);
|
2721
|
+
newOut.unshift(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2722
|
+
if (newOut.at(-1) == Opcodes.i32_from_u) newOut.pop();
|
2723
|
+
newOut.push([ Opcodes.drop ]);
|
2693
2724
|
} else {
|
2694
|
-
|
2695
|
-
out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2725
|
+
newOut.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2696
2726
|
}
|
2697
2727
|
|
2698
|
-
|
2728
|
+
newOut.push(...setType(scope, name, getNodeType(scope, x.init)));
|
2729
|
+
out = out.concat(newOut);
|
2730
|
+
|
2731
|
+
if (globalThis.precompile && global) {
|
2732
|
+
scope.globalInits ??= {};
|
2733
|
+
scope.globalInits[name] = newOut;
|
2734
|
+
}
|
2699
2735
|
}
|
2700
2736
|
|
2701
2737
|
// hack: this follows spec properly but is mostly unneeded 😅
|
@@ -2994,7 +3030,7 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
|
2994
3030
|
// only allow = for this
|
2995
3031
|
if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
|
2996
3032
|
|
2997
|
-
if (builtinVars
|
3033
|
+
if (Object.hasOwn(builtinVars, name)) {
|
2998
3034
|
// just return rhs (eg `NaN = 2`)
|
2999
3035
|
return generate(scope, decl.right);
|
3000
3036
|
}
|
@@ -3045,6 +3081,15 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
|
3045
3081
|
];
|
3046
3082
|
};
|
3047
3083
|
|
3084
|
+
const ifIdentifierErrors = (scope, decl) => {
|
3085
|
+
if (decl.type === 'Identifier') {
|
3086
|
+
const out = generateIdent(scope, decl);
|
3087
|
+
if (out[1]) return true;
|
3088
|
+
}
|
3089
|
+
|
3090
|
+
return false;
|
3091
|
+
};
|
3092
|
+
|
3048
3093
|
const generateUnary = (scope, decl) => {
|
3049
3094
|
switch (decl.operator) {
|
3050
3095
|
case '+':
|
@@ -3118,16 +3163,9 @@ const generateUnary = (scope, decl) => {
|
|
3118
3163
|
|
3119
3164
|
case 'typeof': {
|
3120
3165
|
let overrideType, toGenerate = true;
|
3121
|
-
|
3122
|
-
|
3123
|
-
|
3124
|
-
|
3125
|
-
// if ReferenceError (undeclared var), ignore and return undefined
|
3126
|
-
if (out[1]) {
|
3127
|
-
// does not exist (2 ops from throw)
|
3128
|
-
overrideType = number(TYPES.undefined, Valtype.i32);
|
3129
|
-
toGenerate = false;
|
3130
|
-
}
|
3166
|
+
if (ifIdentifierErrors(scope, decl.argument)) {
|
3167
|
+
overrideType = number(TYPES.undefined, Valtype.i32);
|
3168
|
+
toGenerate = false;
|
3131
3169
|
}
|
3132
3170
|
|
3133
3171
|
const out = toGenerate ? generate(scope, decl.argument) : [];
|
@@ -4321,15 +4359,17 @@ const generateMember = (scope, decl, _global, _name) => {
|
|
4321
4359
|
|
4322
4360
|
// hack: .length
|
4323
4361
|
if (decl.property.name === 'length') {
|
4362
|
+
// todo: support optional
|
4363
|
+
|
4324
4364
|
const func = funcs.find(x => x.name === name);
|
4325
4365
|
if (func) {
|
4326
4366
|
const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
|
4327
4367
|
return withType(scope, number(typedParams ? Math.floor(func.params.length / 2) : (func.constr ? (func.params.length - 1) : func.params.length)), TYPES.number);
|
4328
4368
|
}
|
4329
4369
|
|
4330
|
-
if (builtinFuncs
|
4331
|
-
if (importedFuncs
|
4332
|
-
if (internalConstrs
|
4370
|
+
if (Object.hasOwn(builtinFuncs, name)) return withType(scope, number(builtinFuncs[name].typedParams ? Math.floor(builtinFuncs[name].params.length / 2) : (builtinFuncs[name].constr ? (builtinFuncs[name].params.length - 1) : builtinFuncs[name].params.length)), TYPES.number);
|
4371
|
+
if (Object.hasOwn(importedFuncs, name)) return withType(scope, number(importedFuncs[name].params.length ?? importedFuncs[name].params), TYPES.number);
|
4372
|
+
if (Object.hasOwn(internalConstrs, name)) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
|
4333
4373
|
|
4334
4374
|
const out = [
|
4335
4375
|
...generate(scope, decl.object),
|
@@ -4381,6 +4421,7 @@ const generateMember = (scope, decl, _global, _name) => {
|
|
4381
4421
|
|
4382
4422
|
// todo: generate this array procedurally during builtinFuncs creation
|
4383
4423
|
if (['size', 'description', 'byteLength', 'byteOffset', 'buffer', 'detached', 'resizable', 'growable', 'maxByteLength'].includes(decl.property.name)) {
|
4424
|
+
// todo: support optional
|
4384
4425
|
const bc = {};
|
4385
4426
|
const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
|
4386
4427
|
|
@@ -4407,12 +4448,13 @@ const generateMember = (scope, decl, _global, _name) => {
|
|
4407
4448
|
}
|
4408
4449
|
|
4409
4450
|
const object = decl.object;
|
4410
|
-
const objectWasm = generate(scope, object);
|
4411
4451
|
const property = decl.computed ? decl.property : {
|
4412
4452
|
type: 'Literal',
|
4413
4453
|
value: decl.property.name
|
4414
4454
|
};
|
4415
|
-
|
4455
|
+
|
4456
|
+
const objectWasm = [ [ Opcodes.local_get, localTmp(scope, '#member_obj') ] ];
|
4457
|
+
const propertyWasm = [ [ Opcodes.local_get, localTmp(scope, '#member_prop') ] ];
|
4416
4458
|
|
4417
4459
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
4418
4460
|
// hack: this is naughty and will break things!
|
@@ -4428,7 +4470,7 @@ const generateMember = (scope, decl, _global, _name) => {
|
|
4428
4470
|
|
4429
4471
|
includeBuiltin(scope, '__Map_prototype_get');
|
4430
4472
|
|
4431
|
-
|
4473
|
+
const out = typeSwitch(scope, getNodeType(scope, object), {
|
4432
4474
|
[TYPES.array]: [
|
4433
4475
|
...loadArray(scope, objectWasm, propertyWasm),
|
4434
4476
|
...setLastType(scope)
|
@@ -4588,6 +4630,38 @@ const generateMember = (scope, decl, _global, _name) => {
|
|
4588
4630
|
|
4589
4631
|
default: internalThrow(scope, 'TypeError', 'Unsupported member expression object', true)
|
4590
4632
|
});
|
4633
|
+
|
4634
|
+
if (decl.optional) {
|
4635
|
+
out.unshift(
|
4636
|
+
[ Opcodes.block, valtypeBinary ],
|
4637
|
+
...generate(scope, object),
|
4638
|
+
[ Opcodes.local_tee, localTmp(scope, '#member_obj') ],
|
4639
|
+
|
4640
|
+
...nullish(scope, [], getNodeType(scope, object), false, true),
|
4641
|
+
[ Opcodes.if, Blocktype.void ],
|
4642
|
+
...setLastType(scope, TYPES.undefined),
|
4643
|
+
...number(0),
|
4644
|
+
[ Opcodes.br, 1 ],
|
4645
|
+
[ Opcodes.end ],
|
4646
|
+
|
4647
|
+
...generate(scope, property),
|
4648
|
+
[ Opcodes.local_set, localTmp(scope, '#member_prop') ]
|
4649
|
+
);
|
4650
|
+
|
4651
|
+
out.push(
|
4652
|
+
[ Opcodes.end ]
|
4653
|
+
);
|
4654
|
+
} else {
|
4655
|
+
out.unshift(
|
4656
|
+
...generate(scope, object),
|
4657
|
+
[ Opcodes.local_set, localTmp(scope, '#member_obj') ],
|
4658
|
+
|
4659
|
+
...generate(scope, property),
|
4660
|
+
[ Opcodes.local_set, localTmp(scope, '#member_prop') ]
|
4661
|
+
);
|
4662
|
+
}
|
4663
|
+
|
4664
|
+
return out;
|
4591
4665
|
};
|
4592
4666
|
|
4593
4667
|
const randId = () => Math.random().toString(16).slice(1, -2).padEnd(12, '0');
|