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.
@@ -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
  };
@@ -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[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
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[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
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[name]) type = builtinVars[name].type ?? TYPES.number;
1283
- if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
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[name]) return [
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[name]) return [
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 (!name) {
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[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1360
- if (internalConstrs[name]) return internalConstrs[name].type;
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 = 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);
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
- includeBuiltin(scope, name);
1936
- idx = funcIndex[name];
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
- 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
- }
1970
+ const opName = name.slice('__Porffor_wasm_'.length);
1943
1971
 
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
- };
1972
+ if (wasmOps[opName]) {
1973
+ const op = wasmOps[opName];
1967
1974
 
1968
- const opName = name.slice('__Porffor_wasm_'.length);
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
- if (wasmOps[opName]) {
1971
- const op = wasmOps[opName];
1981
+ // literals only
1982
+ const imms = decl.arguments.slice(op.args.length).map(x => x.value);
1972
1983
 
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
- );
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
- // literals only
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
- return [
1983
- ...argOut,
1984
- [ Opcodes[opName], ...imms ],
1985
- ...(new Array(op.returns).fill(Opcodes.i32_from))
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
- if (idx === undefined) {
1991
- if (!Prefs.indirectCalls) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
2003
+ funcs.table = true;
2004
+ scope.table = true;
1992
2005
 
1993
- // todo: only works when function uses typedParams and typedReturns
2006
+ let args = decl.arguments;
2007
+ let locals = [];
1994
2008
 
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
2009
+ if (indirectMode === 'vararg') {
2010
+ const minArgc = Prefs.indirectCallMinArgc ?? 3;
2002
2011
 
2003
- funcs.table = true;
2004
- scope.table = true;
2012
+ if (args.length < minArgc) {
2013
+ args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
2014
+ }
2015
+ }
2005
2016
 
2006
- let args = decl.arguments;
2007
- let locals = [];
2017
+ for (let i = 0; i < args.length; i++) {
2018
+ const arg = args[i];
2019
+ out = out.concat(generate(scope, arg));
2008
2020
 
2009
- if (indirectMode === 'vararg') {
2010
- const minArgc = Prefs.indirectCallMinArgc ?? 3;
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
- if (args.length < minArgc) {
2013
- args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
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
- for (let i = 0; i < args.length; i++) {
2018
- const arg = args[i];
2019
- out = out.concat(generate(scope, arg));
2034
+ locals.push([valLocal, typeLocal]);
2020
2035
 
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);
2036
+ out.push(
2037
+ [ Opcodes.local_set, typeLocal ],
2038
+ [ Opcodes.local_set, valLocal ]
2039
+ );
2040
+ }
2026
2041
  }
2027
2042
 
2028
- out = out.concat(getNodeType(scope, arg));
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
- if (indirectMode === 'vararg') {
2031
- const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
2032
- const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
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
- locals.push([valLocal, typeLocal]);
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
- out.push(
2037
- [ Opcodes.local_set, typeLocal ],
2038
- [ Opcodes.local_set, valLocal ]
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
- if (indirectMode === 'strict') {
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.call_indirect, args.length, 0 ],
2056
- ...setLastType(scope)
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[name]) {
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[name]) {
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[name]) {
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
- 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
- }
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[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);
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
- const propertyWasm = generate(scope, property);
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
- return typeSwitch(scope, getNodeType(scope, object), {
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "porffor",
3
3
  "description": "a basic experimental wip aot optimizing js -> wasm engine/compiler/runtime in js",
4
- "version": "0.19.8+92c5cf593",
4
+ "version": "0.19.9+a07f4dac1",
5
5
  "author": "CanadaHonk",
6
6
  "license": "MIT",
7
7
  "scripts": {},
package/runner/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from 'node:fs';
3
- globalThis.version = '0.19.8+92c5cf593';
3
+ globalThis.version = '0.19.9+a07f4dac1';
4
4
 
5
5
  // deno compat
6
6
  if (typeof process === 'undefined' && typeof Deno !== 'undefined') {