porffor 0.19.8 → 0.19.9
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/codegen.js +293 -255
- 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
|
};
|
@@ -1273,14 +1273,14 @@ const getType = (scope, _name) => {
|
|
1273
1273
|
// if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
|
1274
1274
|
|
1275
1275
|
if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
|
1276
|
-
if (scope.locals
|
1276
|
+
if (Object.hasOwn(scope.locals, name)) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
1277
1277
|
|
1278
1278
|
if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
|
1279
|
-
if (globals
|
1279
|
+
if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1280
1280
|
|
1281
1281
|
let type = TYPES.undefined;
|
1282
|
-
if (builtinVars
|
1283
|
-
if (builtinFuncs
|
1282
|
+
if (Object.hasOwn(builtinVars, name)) type = builtinVars[name].type ?? TYPES.number;
|
1283
|
+
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(importedFuncs, name) || Object.hasOwn(funcIndex, name) || Object.hasOwn(internalConstrs, name)) type = TYPES.function;
|
1284
1284
|
|
1285
1285
|
if (isExistingProtoFunc(name)) type = TYPES.function;
|
1286
1286
|
|
@@ -1293,13 +1293,13 @@ const setType = (scope, _name, type) => {
|
|
1293
1293
|
const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
|
1294
1294
|
|
1295
1295
|
if (typedInput && scope.locals[name]?.metadata?.type != null) return [];
|
1296
|
-
if (scope.locals
|
1296
|
+
if (Object.hasOwn(scope.locals, name)) return [
|
1297
1297
|
...out,
|
1298
1298
|
[ Opcodes.local_set, scope.locals[name + '#type'].idx ]
|
1299
1299
|
];
|
1300
1300
|
|
1301
1301
|
if (typedInput && globals[name]?.metadata?.type != null) return [];
|
1302
|
-
if (globals
|
1302
|
+
if (Object.hasOwn(globals, name)) return [
|
1303
1303
|
...out,
|
1304
1304
|
[ Opcodes.global_set, globals[name + '#type'].idx ]
|
1305
1305
|
];
|
@@ -1342,7 +1342,7 @@ const getNodeType = (scope, node) => {
|
|
1342
1342
|
|
1343
1343
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1344
1344
|
const name = node.callee.name;
|
1345
|
-
if (
|
1345
|
+
if (name == null) {
|
1346
1346
|
// iife
|
1347
1347
|
if (scope.locals['#last_type']) return getLastType(scope);
|
1348
1348
|
|
@@ -1356,8 +1356,8 @@ const getNodeType = (scope, node) => {
|
|
1356
1356
|
if (func.returnType != null) return func.returnType;
|
1357
1357
|
}
|
1358
1358
|
|
1359
|
-
if (builtinFuncs
|
1360
|
-
if (internalConstrs
|
1359
|
+
if (Object.hasOwn(builtinFuncs, name) && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1360
|
+
if (Object.hasOwn(internalConstrs, name)) return internalConstrs[name].type;
|
1361
1361
|
|
1362
1362
|
// check if this is a prototype function
|
1363
1363
|
// if so and there is only one impl (eg charCodeAt)
|
@@ -1601,6 +1601,10 @@ const generateSequence = (scope, decl) => {
|
|
1601
1601
|
return out;
|
1602
1602
|
};
|
1603
1603
|
|
1604
|
+
const generateChain = (scope, decl) => {
|
1605
|
+
return generate(scope, decl.expression);
|
1606
|
+
};
|
1607
|
+
|
1604
1608
|
const CTArrayUtil = {
|
1605
1609
|
getLengthI32: pointer => [
|
1606
1610
|
...number(0, Valtype.i32),
|
@@ -1927,120 +1931,219 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1927
1931
|
// TODO: only allows callee as identifier
|
1928
1932
|
// if (!name) return todo(scope, `only identifier callees (got ${decl.callee.type})`);
|
1929
1933
|
|
1930
|
-
let idx
|
1931
|
-
if (
|
1932
|
-
if (
|
1933
|
-
if (
|
1934
|
+
let idx;
|
1935
|
+
if (Object.hasOwn(funcIndex, name)) idx = funcIndex[name];
|
1936
|
+
else if (Object.hasOwn(importedFuncs, name)) idx = importedFuncs[name];
|
1937
|
+
else if (Object.hasOwn(builtinFuncs, name)) {
|
1938
|
+
if (builtinFuncs[name].floatOnly && valtype !== 'f64') throw new Error(`Cannot use built-in ${unhackName(name)} with integer valtype`);
|
1939
|
+
if (decl._new && !builtinFuncs[name].constr) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a constructor`, true);
|
1934
1940
|
|
1935
|
-
|
1936
|
-
|
1937
|
-
|
1941
|
+
includeBuiltin(scope, name);
|
1942
|
+
idx = funcIndex[name];
|
1943
|
+
} else if (Object.hasOwn(internalConstrs, name)) {
|
1944
|
+
if (decl._new && internalConstrs[name].notConstr) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a constructor`, true);
|
1945
|
+
return internalConstrs[name].generate(scope, decl, _global, _name);
|
1946
|
+
} else if (!decl._new && name && name.startsWith('__Porffor_wasm_')) {
|
1947
|
+
const wasmOps = {
|
1948
|
+
// pointer, align, offset
|
1949
|
+
i32_load: { imms: 2, args: [ true ], returns: 1 },
|
1950
|
+
// pointer, value, align, offset
|
1951
|
+
i32_store: { imms: 2, args: [ true, true ], returns: 0 },
|
1952
|
+
// pointer, align, offset
|
1953
|
+
i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
|
1954
|
+
// pointer, value, align, offset
|
1955
|
+
i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
|
1956
|
+
// pointer, align, offset
|
1957
|
+
i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
|
1958
|
+
// pointer, value, align, offset
|
1959
|
+
i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
|
1960
|
+
|
1961
|
+
// pointer, align, offset
|
1962
|
+
f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
|
1963
|
+
// pointer, value, align, offset
|
1964
|
+
f64_store: { imms: 2, args: [ true, false ], returns: 0 },
|
1965
|
+
|
1966
|
+
// value
|
1967
|
+
i32_const: { imms: 1, args: [], returns: 0 },
|
1968
|
+
};
|
1938
1969
|
|
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
|
-
}
|
1970
|
+
const opName = name.slice('__Porffor_wasm_'.length);
|
1943
1971
|
|
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
|
-
};
|
1972
|
+
if (wasmOps[opName]) {
|
1973
|
+
const op = wasmOps[opName];
|
1967
1974
|
|
1968
|
-
|
1975
|
+
const argOut = [];
|
1976
|
+
for (let i = 0; i < op.args.length; i++) argOut.push(
|
1977
|
+
...generate(scope, decl.arguments[i]),
|
1978
|
+
...(op.args[i] ? [ Opcodes.i32_to ] : [])
|
1979
|
+
);
|
1969
1980
|
|
1970
|
-
|
1971
|
-
|
1981
|
+
// literals only
|
1982
|
+
const imms = decl.arguments.slice(op.args.length).map(x => x.value);
|
1972
1983
|
|
1973
|
-
|
1974
|
-
|
1975
|
-
|
1976
|
-
|
1977
|
-
|
1984
|
+
return [
|
1985
|
+
...argOut,
|
1986
|
+
[ Opcodes[opName], ...imms ],
|
1987
|
+
...(new Array(op.returns).fill(Opcodes.i32_from))
|
1988
|
+
];
|
1989
|
+
}
|
1990
|
+
} else {
|
1991
|
+
if (!Prefs.indirectCalls) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1978
1992
|
|
1979
|
-
//
|
1980
|
-
const imms = decl.arguments.slice(op.args.length).map(x => x.value);
|
1993
|
+
// todo: only works when function uses typedParams and typedReturns
|
1981
1994
|
|
1982
|
-
|
1983
|
-
|
1984
|
-
|
1985
|
-
|
1986
|
-
|
1987
|
-
|
1988
|
-
|
1995
|
+
const indirectMode = Prefs.indirectCallMode ?? 'vararg';
|
1996
|
+
// options: vararg, strict
|
1997
|
+
// - strict: simpler, smaller size usage, no func lut needed.
|
1998
|
+
// ONLY works when arg count of call == arg count of function being called
|
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
|
1989
2002
|
|
1990
|
-
|
1991
|
-
|
2003
|
+
funcs.table = true;
|
2004
|
+
scope.table = true;
|
1992
2005
|
|
1993
|
-
|
2006
|
+
let args = decl.arguments;
|
2007
|
+
let locals = [];
|
1994
2008
|
|
1995
|
-
|
1996
|
-
|
1997
|
-
// - strict: simpler, smaller size usage, no func lut needed.
|
1998
|
-
// ONLY works when arg count of call == arg count of function being called
|
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
|
2009
|
+
if (indirectMode === 'vararg') {
|
2010
|
+
const minArgc = Prefs.indirectCallMinArgc ?? 3;
|
2002
2011
|
|
2003
|
-
|
2004
|
-
|
2012
|
+
if (args.length < minArgc) {
|
2013
|
+
args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
|
2014
|
+
}
|
2015
|
+
}
|
2005
2016
|
|
2006
|
-
|
2007
|
-
|
2017
|
+
for (let i = 0; i < args.length; i++) {
|
2018
|
+
const arg = args[i];
|
2019
|
+
out = out.concat(generate(scope, arg));
|
2008
2020
|
|
2009
|
-
|
2010
|
-
|
2021
|
+
if (valtypeBinary !== Valtype.i32 && (
|
2022
|
+
(builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
|
2023
|
+
(importedFuncs[name] && name.startsWith('profile'))
|
2024
|
+
)) {
|
2025
|
+
out.push(Opcodes.i32_to);
|
2026
|
+
}
|
2011
2027
|
|
2012
|
-
|
2013
|
-
|
2014
|
-
|
2015
|
-
|
2028
|
+
out = out.concat(getNodeType(scope, arg));
|
2029
|
+
|
2030
|
+
if (indirectMode === 'vararg') {
|
2031
|
+
const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
|
2032
|
+
const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
|
2016
2033
|
|
2017
|
-
|
2018
|
-
const arg = args[i];
|
2019
|
-
out = out.concat(generate(scope, arg));
|
2034
|
+
locals.push([valLocal, typeLocal]);
|
2020
2035
|
|
2021
|
-
|
2022
|
-
|
2023
|
-
|
2024
|
-
|
2025
|
-
|
2036
|
+
out.push(
|
2037
|
+
[ Opcodes.local_set, typeLocal ],
|
2038
|
+
[ Opcodes.local_set, valLocal ]
|
2039
|
+
);
|
2040
|
+
}
|
2026
2041
|
}
|
2027
2042
|
|
2028
|
-
|
2043
|
+
if (indirectMode === 'strict') {
|
2044
|
+
return [
|
2045
|
+
...generate(scope, decl.callee),
|
2046
|
+
[ Opcodes.local_set, localTmp(scope, '#indirect_callee') ],
|
2047
|
+
|
2048
|
+
...typeSwitch(scope, getNodeType(scope, decl.callee), {
|
2049
|
+
[TYPES.function]: [
|
2050
|
+
...out,
|
2051
|
+
|
2052
|
+
[ Opcodes.local_get, localTmp(scope, '#indirect_callee') ],
|
2053
|
+
...generate(scope, decl.callee),
|
2054
|
+
Opcodes.i32_to_u,
|
2055
|
+
[ Opcodes.call_indirect, args.length, 0 ],
|
2056
|
+
...setLastType(scope)
|
2057
|
+
],
|
2058
|
+
default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
|
2059
|
+
})
|
2060
|
+
];
|
2061
|
+
}
|
2029
2062
|
|
2030
|
-
|
2031
|
-
|
2032
|
-
|
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
|
+
}
|
2033
2088
|
|
2034
|
-
|
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
|
+
];
|
2035
2099
|
|
2036
|
-
|
2037
|
-
|
2038
|
-
|
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
|
+
]),
|
2039
2137
|
);
|
2138
|
+
};
|
2139
|
+
|
2140
|
+
const tableBc = {};
|
2141
|
+
for (let i = 0; i <= args.length; i++) {
|
2142
|
+
tableBc[i] = gen(i);
|
2040
2143
|
}
|
2041
|
-
}
|
2042
2144
|
|
2043
|
-
|
2145
|
+
// todo/perf: check if we should use br_table here or just generate our own big if..elses
|
2146
|
+
|
2044
2147
|
return [
|
2045
2148
|
...generate(scope, decl.callee),
|
2046
2149
|
[ Opcodes.local_set, localTmp(scope, '#indirect_callee') ],
|
@@ -2050,145 +2153,42 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
2050
2153
|
...out,
|
2051
2154
|
|
2052
2155
|
[ Opcodes.local_get, localTmp(scope, '#indirect_callee') ],
|
2053
|
-
...generate(scope, decl.callee),
|
2054
2156
|
Opcodes.i32_to_u,
|
2055
|
-
[ Opcodes.
|
2056
|
-
|
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)
|
2057
2186
|
],
|
2058
2187
|
default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
|
2059
2188
|
})
|
2060
2189
|
];
|
2061
2190
|
}
|
2062
2191
|
|
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
2192
|
const func = funcs[idx - importedFuncs.length]; // idx === scope.index ? scope : funcs.find(x => x.index === idx);
|
2193
2193
|
const userFunc = func && !func.internal;
|
2194
2194
|
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
@@ -2464,7 +2464,7 @@ const allocVar = (scope, name, global = false, type = true) => {
|
|
2464
2464
|
const target = global ? globals : scope.locals;
|
2465
2465
|
|
2466
2466
|
// already declared
|
2467
|
-
if (target
|
2467
|
+
if (Object.hasOwn(target, name)) {
|
2468
2468
|
// parser should catch this but sanity check anyway
|
2469
2469
|
// if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
|
2470
2470
|
|
@@ -2660,7 +2660,7 @@ const generateVar = (scope, decl) => {
|
|
2660
2660
|
continue;
|
2661
2661
|
}
|
2662
2662
|
|
2663
|
-
if (topLevel && builtinVars
|
2663
|
+
if (topLevel && Object.hasOwn(builtinVars, name)) {
|
2664
2664
|
// cannot redeclare
|
2665
2665
|
if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
|
2666
2666
|
|
@@ -2994,7 +2994,7 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
|
2994
2994
|
// only allow = for this
|
2995
2995
|
if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
|
2996
2996
|
|
2997
|
-
if (builtinVars
|
2997
|
+
if (Object.hasOwn(builtinVars, name)) {
|
2998
2998
|
// just return rhs (eg `NaN = 2`)
|
2999
2999
|
return generate(scope, decl.right);
|
3000
3000
|
}
|
@@ -3045,6 +3045,15 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
|
3045
3045
|
];
|
3046
3046
|
};
|
3047
3047
|
|
3048
|
+
const ifIdentifierErrors = (scope, decl) => {
|
3049
|
+
if (decl.type === 'Identifier') {
|
3050
|
+
const out = generateIdent(scope, decl);
|
3051
|
+
if (out[1]) return true;
|
3052
|
+
}
|
3053
|
+
|
3054
|
+
return false;
|
3055
|
+
};
|
3056
|
+
|
3048
3057
|
const generateUnary = (scope, decl) => {
|
3049
3058
|
switch (decl.operator) {
|
3050
3059
|
case '+':
|
@@ -3118,16 +3127,9 @@ const generateUnary = (scope, decl) => {
|
|
3118
3127
|
|
3119
3128
|
case 'typeof': {
|
3120
3129
|
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
|
-
}
|
3130
|
+
if (ifIdentifierErrors(scope, decl.argument)) {
|
3131
|
+
overrideType = number(TYPES.undefined, Valtype.i32);
|
3132
|
+
toGenerate = false;
|
3131
3133
|
}
|
3132
3134
|
|
3133
3135
|
const out = toGenerate ? generate(scope, decl.argument) : [];
|
@@ -4321,15 +4323,17 @@ const generateMember = (scope, decl, _global, _name) => {
|
|
4321
4323
|
|
4322
4324
|
// hack: .length
|
4323
4325
|
if (decl.property.name === 'length') {
|
4326
|
+
// todo: support optional
|
4327
|
+
|
4324
4328
|
const func = funcs.find(x => x.name === name);
|
4325
4329
|
if (func) {
|
4326
4330
|
const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
|
4327
4331
|
return withType(scope, number(typedParams ? Math.floor(func.params.length / 2) : (func.constr ? (func.params.length - 1) : func.params.length)), TYPES.number);
|
4328
4332
|
}
|
4329
4333
|
|
4330
|
-
if (builtinFuncs
|
4331
|
-
if (importedFuncs
|
4332
|
-
if (internalConstrs
|
4334
|
+
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);
|
4335
|
+
if (Object.hasOwn(importedFuncs, name)) return withType(scope, number(importedFuncs[name].params.length ?? importedFuncs[name].params), TYPES.number);
|
4336
|
+
if (Object.hasOwn(internalConstrs, name)) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
|
4333
4337
|
|
4334
4338
|
const out = [
|
4335
4339
|
...generate(scope, decl.object),
|
@@ -4381,6 +4385,7 @@ const generateMember = (scope, decl, _global, _name) => {
|
|
4381
4385
|
|
4382
4386
|
// todo: generate this array procedurally during builtinFuncs creation
|
4383
4387
|
if (['size', 'description', 'byteLength', 'byteOffset', 'buffer', 'detached', 'resizable', 'growable', 'maxByteLength'].includes(decl.property.name)) {
|
4388
|
+
// todo: support optional
|
4384
4389
|
const bc = {};
|
4385
4390
|
const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
|
4386
4391
|
|
@@ -4407,12 +4412,13 @@ const generateMember = (scope, decl, _global, _name) => {
|
|
4407
4412
|
}
|
4408
4413
|
|
4409
4414
|
const object = decl.object;
|
4410
|
-
const objectWasm = generate(scope, object);
|
4411
4415
|
const property = decl.computed ? decl.property : {
|
4412
4416
|
type: 'Literal',
|
4413
4417
|
value: decl.property.name
|
4414
4418
|
};
|
4415
|
-
|
4419
|
+
|
4420
|
+
const objectWasm = [ [ Opcodes.local_get, localTmp(scope, '#member_obj') ] ];
|
4421
|
+
const propertyWasm = [ [ Opcodes.local_get, localTmp(scope, '#member_prop') ] ];
|
4416
4422
|
|
4417
4423
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
4418
4424
|
// hack: this is naughty and will break things!
|
@@ -4428,7 +4434,7 @@ const generateMember = (scope, decl, _global, _name) => {
|
|
4428
4434
|
|
4429
4435
|
includeBuiltin(scope, '__Map_prototype_get');
|
4430
4436
|
|
4431
|
-
|
4437
|
+
const out = typeSwitch(scope, getNodeType(scope, object), {
|
4432
4438
|
[TYPES.array]: [
|
4433
4439
|
...loadArray(scope, objectWasm, propertyWasm),
|
4434
4440
|
...setLastType(scope)
|
@@ -4588,6 +4594,38 @@ const generateMember = (scope, decl, _global, _name) => {
|
|
4588
4594
|
|
4589
4595
|
default: internalThrow(scope, 'TypeError', 'Unsupported member expression object', true)
|
4590
4596
|
});
|
4597
|
+
|
4598
|
+
if (decl.optional) {
|
4599
|
+
out.unshift(
|
4600
|
+
[ Opcodes.block, valtypeBinary ],
|
4601
|
+
...generate(scope, object),
|
4602
|
+
[ Opcodes.local_tee, localTmp(scope, '#member_obj') ],
|
4603
|
+
|
4604
|
+
...nullish(scope, [], getNodeType(scope, object), false, true),
|
4605
|
+
[ Opcodes.if, Blocktype.void ],
|
4606
|
+
...setLastType(scope, TYPES.undefined),
|
4607
|
+
...number(0),
|
4608
|
+
[ Opcodes.br, 1 ],
|
4609
|
+
[ Opcodes.end ],
|
4610
|
+
|
4611
|
+
...generate(scope, property),
|
4612
|
+
[ Opcodes.local_set, localTmp(scope, '#member_prop') ]
|
4613
|
+
);
|
4614
|
+
|
4615
|
+
out.push(
|
4616
|
+
[ Opcodes.end ]
|
4617
|
+
);
|
4618
|
+
} else {
|
4619
|
+
out.unshift(
|
4620
|
+
...generate(scope, object),
|
4621
|
+
[ Opcodes.local_set, localTmp(scope, '#member_obj') ],
|
4622
|
+
|
4623
|
+
...generate(scope, property),
|
4624
|
+
[ Opcodes.local_set, localTmp(scope, '#member_prop') ]
|
4625
|
+
);
|
4626
|
+
}
|
4627
|
+
|
4628
|
+
return out;
|
4591
4629
|
};
|
4592
4630
|
|
4593
4631
|
const randId = () => Math.random().toString(16).slice(1, -2).padEnd(12, '0');
|
package/package.json
CHANGED