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.
@@ -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[name] != null || builtinFuncs[name] != null || importedFuncs[name] != null || internalConstrs[name] != null;
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
- let local = scope.locals[name];
264
- if (local) return [ local, false ];
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(wasm, { name });
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 = (func, scope) => {
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(wasm, func);
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[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
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[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
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[name]) type = builtinVars[name].type ?? TYPES.number;
1283
- if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
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[name]) return [
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[name]) return [
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 (!name) {
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[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1360
- if (internalConstrs[name]) return internalConstrs[name].type;
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 = funcIndex[name] ?? importedFuncs[name];
1931
- if (idx === undefined && builtinFuncs[name]) {
1932
- if (builtinFuncs[name].floatOnly && valtype !== 'f64') throw new Error(`Cannot use built-in ${unhackName(name)} with integer valtype`);
1933
- if (decl._new && !builtinFuncs[name].constr) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a constructor`, true);
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
- includeBuiltin(scope, name);
1936
- idx = funcIndex[name];
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
- if (idx === undefined && internalConstrs[name]) {
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
- if (idx === undefined && !decl._new && name && name.startsWith('__Porffor_wasm_')) {
1945
- const wasmOps = {
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
- const opName = name.slice('__Porffor_wasm_'.length);
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
- if (wasmOps[opName]) {
1971
- const op = wasmOps[opName];
2015
+ // literals only
2016
+ const imms = decl.arguments.slice(op.args.length).map(x => x.value);
1972
2017
 
1973
- const argOut = [];
1974
- for (let i = 0; i < op.args.length; i++) argOut.push(
1975
- ...generate(scope, decl.arguments[i]),
1976
- ...(op.args[i] ? [ Opcodes.i32_to ] : [])
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
- // literals only
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
- return [
1983
- ...argOut,
1984
- [ Opcodes[opName], ...imms ],
1985
- ...(new Array(op.returns).fill(Opcodes.i32_from))
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
- if (idx === undefined) {
1991
- if (!Prefs.indirectCalls) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
2040
+ let args = decl.arguments;
2041
+ let locals = [];
1992
2042
 
1993
- // todo: only works when function uses typedParams and typedReturns
2043
+ if (indirectMode === 'vararg') {
2044
+ const minArgc = Prefs.indirectCallMinArgc ?? 3;
1994
2045
 
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
2046
+ if (args.length < minArgc) {
2047
+ args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
2048
+ }
2049
+ }
2002
2050
 
2003
- funcs.table = true;
2004
- scope.table = true;
2051
+ for (let i = 0; i < args.length; i++) {
2052
+ const arg = args[i];
2053
+ out = out.concat(generate(scope, arg));
2005
2054
 
2006
- let args = decl.arguments;
2007
- let locals = [];
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
- if (indirectMode === 'vararg') {
2010
- const minArgc = Prefs.indirectCallMinArgc ?? 3;
2062
+ out = out.concat(getNodeType(scope, arg));
2011
2063
 
2012
- if (args.length < minArgc) {
2013
- args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
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
- for (let i = 0; i < args.length; i++) {
2018
- const arg = args[i];
2019
- out = out.concat(generate(scope, arg));
2068
+ locals.push([valLocal, typeLocal]);
2020
2069
 
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);
2070
+ out.push(
2071
+ [ Opcodes.local_set, typeLocal ],
2072
+ [ Opcodes.local_set, valLocal ]
2073
+ );
2074
+ }
2026
2075
  }
2027
2076
 
2028
- out = out.concat(getNodeType(scope, arg));
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
- if (indirectMode === 'vararg') {
2031
- const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
2032
- const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
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
- locals.push([valLocal, typeLocal]);
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
- out.push(
2037
- [ Opcodes.local_set, typeLocal ],
2038
- [ Opcodes.local_set, valLocal ]
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
- if (indirectMode === 'strict') {
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.call_indirect, args.length, 0 ],
2056
- ...setLastType(scope)
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[name]) {
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[name]) {
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 generated = generate(scope, x.init, global, name);
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
- out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2688
- if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
2689
- // generated.pop();
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
- out = out.concat(generated);
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
- out.push(...setType(scope, name, getNodeType(scope, x.init)));
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[name]) {
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
- if (decl.argument.type === 'Identifier') {
3123
- const out = generateIdent(scope, decl.argument);
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[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);
4331
- if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params.length ?? importedFuncs[name].params), TYPES.number);
4332
- if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
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
- const propertyWasm = generate(scope, property);
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
- return typeSwitch(scope, getNodeType(scope, object), {
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');