porffor 0.37.6 → 0.37.8

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.
@@ -390,6 +390,8 @@ const generateReturn = (scope, decl) => {
390
390
  const arg = decl.argument ?? DEFAULT_VALUE();
391
391
 
392
392
  if (scope.async) {
393
+ typeUsed(scope, TYPES.promise);
394
+
393
395
  return [
394
396
  // resolve promise with return value
395
397
  [ Opcodes.local_get, scope.locals['#async_out_promise'].idx ],
@@ -589,7 +591,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false, forceTruthyMod
589
591
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
590
592
 
591
593
  ...typeSwitch(scope, type, [
592
- [ [ TYPES.string, TYPES.bytestring ], [
594
+ [ [ TYPES.string, TYPES.bytestring ], () => [
593
595
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
594
596
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
595
597
 
@@ -643,7 +645,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false, forceTruthyMode
643
645
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
644
646
 
645
647
  ...typeSwitch(scope, type, [
646
- [ [ TYPES.string, TYPES.bytestring ], [
648
+ [ [ TYPES.string, TYPES.bytestring ], () => [
647
649
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
648
650
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
649
651
 
@@ -689,17 +691,6 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
689
691
  ];
690
692
  };
691
693
 
692
- const stringOnly = wasm => {
693
- if (!Array.isArray(wasm[0])) return [ ...wasm, 'string_only' ];
694
- if (wasm.length === 1) return [ [ ...wasm[0], 'string_only' ] ];
695
-
696
- return [
697
- [ ...wasm[0], 'string_only|start' ],
698
- ...wasm.slice(1, -1),
699
- [ ...wasm[wasm.length - 1], 'string_only|end' ]
700
- ];
701
- }
702
-
703
694
  const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
704
695
  if (op === '||' || op === '&&' || op === '??') {
705
696
  return performLogicOp(scope, op, left, right, leftType, rightType);
@@ -787,7 +778,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
787
778
  tmpLeft = localTmp(scope, '__tmpop_left');
788
779
  tmpRight = localTmp(scope, '__tmpop_right');
789
780
 
790
- ops.unshift(...stringOnly([
781
+ ops.unshift(
791
782
  // if left or right are string or bytestring
792
783
  ...leftType,
793
784
  ...number(TYPE_FLAGS.parity, Valtype.i32),
@@ -808,18 +799,18 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
808
799
  [ Opcodes.end ],
809
800
 
810
801
  ...setLastType(scope, TYPES.number)
811
- ]));
802
+ );
812
803
 
813
804
  // add a surrounding block
814
- startOut.push(stringOnly([ Opcodes.block, Valtype.f64 ]));
815
- endOut.unshift(stringOnly([ Opcodes.end ]));
805
+ startOut.push([ Opcodes.block, Valtype.f64 ]);
806
+ endOut.unshift([ Opcodes.end ]);
816
807
  }
817
808
 
818
809
  if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
819
810
  tmpLeft = localTmp(scope, '__tmpop_left');
820
811
  tmpRight = localTmp(scope, '__tmpop_right');
821
812
 
822
- ops.unshift(...stringOnly([
813
+ ops.unshift(
823
814
  // if left or right are string or bytestring
824
815
  ...leftType,
825
816
  ...number(TYPE_FLAGS.parity, Valtype.i32),
@@ -839,18 +830,18 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
839
830
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
840
831
  [ Opcodes.br, 1 ],
841
832
  [ Opcodes.end ]
842
- ]));
833
+ );
843
834
 
844
835
  // add a surrounding block
845
- startOut.push(stringOnly([ Opcodes.block, Valtype.i32 ]));
846
- endOut.unshift(stringOnly([ Opcodes.end ]));
836
+ startOut.push([ Opcodes.block, Valtype.i32 ]);
837
+ endOut.unshift([ Opcodes.end ]);
847
838
  }
848
839
 
849
840
  return finalize([
850
841
  ...left,
851
- ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
842
+ ...(tmpLeft != null ? [ [ Opcodes.local_tee, tmpLeft ] ] : []),
852
843
  ...right,
853
- ...(tmpRight != null ? stringOnly([ [ Opcodes.local_tee, tmpRight ] ]) : []),
844
+ ...(tmpRight != null ? [ [ Opcodes.local_tee, tmpRight ] ] : []),
854
845
  ...ops
855
846
  ]);
856
847
  };
@@ -944,7 +935,6 @@ const asmFuncToAsm = (scope, func) => {
944
935
  builtin: (n, offset = false) => {
945
936
  let idx = funcIndex[n] ?? importedFuncs[n];
946
937
  if (idx == null && builtinFuncs[n]) {
947
- // console.log(scope.name, '->', n);
948
938
  includeBuiltin(scope, n);
949
939
  idx = funcIndex[n];
950
940
  }
@@ -1002,11 +992,21 @@ const asmFuncToAsm = (scope, func) => {
1002
992
  }
1003
993
 
1004
994
  return scope.locals[name].idx;
995
+ },
996
+ t: (types, wasm) => {
997
+ if (types.some(x => usedTypes.has(x))) {
998
+ return wasm();
999
+ } else {
1000
+ return [ [ null, () => {
1001
+ if (types.some(x => usedTypes.has(x))) return wasm();
1002
+ return [];
1003
+ } ] ];
1004
+ }
1005
1005
  }
1006
1006
  });
1007
1007
  };
1008
1008
 
1009
- const asmFunc = (name, { wasm, params = [], typedParams = false, locals: localTypes = [], globals: globalTypes = [], globalInits = [], returns = [], returnType, localNames = [], globalNames = [], data: _data = [], table = false, constr = false, hasRestArgument = false } = {}) => {
1009
+ const asmFunc = (name, { wasm, params = [], typedParams = false, locals: localTypes = [], globals: globalTypes = [], globalInits = [], returns = [], returnType, localNames = [], globalNames = [], data: _data = [], table = false, constr = false, hasRestArgument = false, usedTypes = [] } = {}) => {
1010
1010
  if (wasm == null) { // called with no builtin
1011
1011
  log.warning('codegen', `${name} has no built-in!`);
1012
1012
  wasm = [];
@@ -1080,6 +1080,8 @@ const asmFunc = (name, { wasm, params = [], typedParams = false, locals: localTy
1080
1080
 
1081
1081
  if (hasRestArgument) func.hasRestArgument = true;
1082
1082
 
1083
+ for (const x of usedTypes) typeUsed(func, x);
1084
+
1083
1085
  func.wasm = wasm;
1084
1086
 
1085
1087
  return func;
@@ -1161,6 +1163,8 @@ const getType = (scope, _name) => {
1161
1163
  };
1162
1164
 
1163
1165
  const setType = (scope, _name, type) => {
1166
+ typeUsed(scope, knownType(scope, type));
1167
+
1164
1168
  const name = mapName(_name);
1165
1169
 
1166
1170
  const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
@@ -1200,10 +1204,13 @@ const getLastType = scope => {
1200
1204
  return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1201
1205
  };
1202
1206
 
1203
- const setLastType = (scope, type = []) => [
1204
- ...(typeof type === 'number' ? number(type, Valtype.i32) : type),
1205
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1206
- ];
1207
+ const setLastType = (scope, type = []) => {
1208
+ typeUsed(scope, knownType(scope, type));
1209
+ return [
1210
+ ...(typeof type === 'number' ? number(type, Valtype.i32) : type),
1211
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1212
+ ];
1213
+ };
1207
1214
 
1208
1215
  const getNodeType = (scope, node) => {
1209
1216
  let guess = null;
@@ -1426,6 +1433,8 @@ const getNodeType = (scope, node) => {
1426
1433
  const out = typeof ret === 'number' ? number(ret, Valtype.i32) : ret;
1427
1434
  if (guess != null) out.guess = typeof guess === 'number' ? number(guess, Valtype.i32) : guess;
1428
1435
 
1436
+ typeUsed(scope, knownType(scope, out));
1437
+
1429
1438
  return out;
1430
1439
  };
1431
1440
 
@@ -1468,7 +1477,7 @@ const countLeftover = wasm => {
1468
1477
 
1469
1478
  if (depth === 0)
1470
1479
  if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1471
- else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.f32_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x04)) {}
1480
+ else if ([Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.f32_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x04)) {}
1472
1481
  else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const, Opcodes.memory_size].includes(inst[0])) count++;
1473
1482
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.f32_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1474
1483
  else if (inst[0] === Opcodes.memory_copy[0] && (inst[1] === Opcodes.memory_copy[1] || inst[1] === Opcodes.memory_init[1])) count -= 3;
@@ -1949,7 +1958,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1949
1958
  const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1950
1959
  if (type == null) continue;
1951
1960
 
1952
- protoBC[type] = generate(scope, {
1961
+ protoBC[type] = () => generate(scope, {
1953
1962
  type: 'CallExpression',
1954
1963
  callee: {
1955
1964
  type: 'Identifier',
@@ -1988,8 +1997,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1988
1997
  const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
1989
1998
  const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
1990
1999
 
1991
- let allOptUnused = true;
1992
- let lengthI32CacheUsed = false;
2000
+ const useLengthCache = true; // basically every prototype uses it
1993
2001
  for (const x in protoCands) {
1994
2002
  const protoFunc = protoCands[x];
1995
2003
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -2000,63 +2008,64 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
2000
2008
  continue;
2001
2009
  }
2002
2010
 
2003
- const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
2004
- const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
2005
-
2006
- let optUnused = false;
2007
- const protoOut = protoFunc(getPointer, {
2008
- getCachedI32: () => {
2009
- lengthI32CacheUsed = true;
2010
- return [ [ Opcodes.local_get, lengthLocal ] ];
2011
+ protoBC[x] = () => {
2012
+ const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
2013
+ const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
2014
+
2015
+ let optUnused = false;
2016
+ const protoOut = protoFunc(getPointer, {
2017
+ getCachedI32: () => [ [ Opcodes.local_get, lengthLocal ] ],
2018
+ setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
2019
+ get: () => RTArrayUtil.getLength(getPointer),
2020
+ getI32: () => RTArrayUtil.getLengthI32(getPointer),
2021
+ set: value => RTArrayUtil.setLength(getPointer, value),
2022
+ setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
2011
2023
  },
2012
- setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
2013
- get: () => RTArrayUtil.getLength(getPointer),
2014
- getI32: () => RTArrayUtil.getLengthI32(getPointer),
2015
- set: value => RTArrayUtil.setLength(getPointer, value),
2016
- setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
2017
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE()), getNodeType(scope, decl.arguments[0] ?? DEFAULT_VALUE()), protoLocal, protoLocal2, (length, itemType) => {
2018
- return makeArray(scope, {
2019
- rawElements: new Array(length)
2020
- }, _global, _name, true, itemType, true);
2021
- }, () => {
2022
- optUnused = true;
2023
- return unusedValue;
2024
- });
2025
-
2026
- if (!optUnused) allOptUnused = false;
2027
-
2028
- protoBC[x] = [
2029
- [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
2030
- ...protoOut,
2031
- ...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
2032
- [ Opcodes.end ]
2033
- ];
2024
+ generate(scope, decl.arguments[0] ?? DEFAULT_VALUE()),
2025
+ getNodeType(scope, decl.arguments[0] ?? DEFAULT_VALUE()),
2026
+ protoLocal, protoLocal2,
2027
+ (length, itemType) => {
2028
+ return makeArray(scope, {
2029
+ rawElements: new Array(length)
2030
+ }, _global, _name, true, itemType, true);
2031
+ },
2032
+ () => {
2033
+ optUnused = true;
2034
+ return unusedValue;
2035
+ });
2036
+
2037
+ return [
2038
+ [ Opcodes.block, unusedValue ? Blocktype.void : valtypeBinary ],
2039
+ ...protoOut,
2040
+ ...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
2041
+ ...(unusedValue && !optUnused ? [ [ Opcodes.drop ] ] : []),
2042
+ [ Opcodes.end ]
2043
+ ];
2044
+ };
2034
2045
  }
2035
2046
 
2036
- // todo: if some cands use optUnused and some don't, we will probably crash
2037
-
2038
2047
  return [
2039
2048
  ...(usePointerCache ? [
2040
2049
  ...rawPointer,
2041
2050
  [ Opcodes.local_set, pointerLocal ],
2042
2051
  ] : []),
2043
2052
 
2044
- ...(!lengthI32CacheUsed ? [] : [
2053
+ ...(useLengthCache ? [
2045
2054
  ...RTArrayUtil.getLengthI32(getPointer),
2046
2055
  [ Opcodes.local_set, lengthLocal ],
2047
- ]),
2056
+ ] : []),
2048
2057
 
2049
2058
  ...typeSwitch(scope, getNodeType(scope, target), {
2050
2059
  ...protoBC,
2051
2060
 
2052
2061
  // TODO: error better
2053
- default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
2054
- }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
2062
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`, !unusedValue)
2063
+ }, unusedValue ? Blocktype.void : valtypeBinary),
2055
2064
  ];
2056
2065
  }
2057
2066
 
2058
2067
  if (Object.keys(protoBC).length > 0) {
2059
- let def = internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`);
2068
+ let def = internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`, true);
2060
2069
 
2061
2070
  // fallback to object prototype impl as a basic prototype chain hack
2062
2071
  if (protoBC[TYPES.object]) def = protoBC[TYPES.object];
@@ -2193,7 +2202,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
2193
2202
  [ Opcodes.local_set, localTmp(scope, '#indirect_callee') ],
2194
2203
 
2195
2204
  ...typeSwitch(scope, getNodeType(scope, decl.callee), {
2196
- [TYPES.function]: [
2205
+ [TYPES.function]: () => [
2197
2206
  ...out,
2198
2207
 
2199
2208
  [ Opcodes.local_get, localTmp(scope, '#indirect_callee') ],
@@ -2332,7 +2341,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
2332
2341
  [ Opcodes.local_set, localTmp(scope, '#indirect_callee') ],
2333
2342
 
2334
2343
  ...typeSwitch(scope, getNodeType(scope, callee), {
2335
- [TYPES.function]: [
2344
+ [TYPES.function]: () => [
2336
2345
  ...out,
2337
2346
 
2338
2347
  [ Opcodes.local_get, localTmp(scope, '#indirect_callee') ],
@@ -2584,6 +2593,8 @@ const unhackName = name => {
2584
2593
  };
2585
2594
 
2586
2595
  const knownType = (scope, type) => {
2596
+ if (typeof type === 'number') return type;
2597
+
2587
2598
  if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
2588
2599
  return read_signedLEB128(type[0].slice(1));
2589
2600
  }
@@ -2692,6 +2703,16 @@ const brTable = (input, bc, returns) => {
2692
2703
 
2693
2704
  let typeswitchDepth = 0;
2694
2705
 
2706
+ let usedTypes = new Set();
2707
+ const typeUsed = (scope, x) => {
2708
+ if (x == null) return;
2709
+ usedTypes.add(x);
2710
+
2711
+ // console.log(scope.name, TYPE_NAMES[x]);
2712
+
2713
+ scope.usedTypes ??= new Set();
2714
+ scope.usedTypes.add(x);
2715
+ };
2695
2716
  const typeSwitch = (scope, type, bc, returns = valtypeBinary, fallthrough = false) => {
2696
2717
  const known = knownType(scope, type);
2697
2718
  if (known != null) {
@@ -2704,13 +2725,14 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary, fallthrough = fals
2704
2725
  }
2705
2726
 
2706
2727
  if (Array.isArray(type)) {
2707
- if (type.includes(known)) return wasm;
2708
- } else if (type === known) return wasm;
2728
+ if (type.includes(known)) return typeof wasm === 'function' ? wasm() : wasm;
2729
+ } else if (type === known) return typeof wasm === 'function' ? wasm() : wasm;
2709
2730
  }
2710
2731
 
2711
- return def;
2732
+ return typeof def === 'function' ? def() : def;
2712
2733
  } else {
2713
- return bc[known] ?? bc.default;
2734
+ const wasm = bc[known] ?? bc.default;
2735
+ return typeof wasm === 'function' ? wasm() : wasm;
2714
2736
  }
2715
2737
  }
2716
2738
 
@@ -2720,7 +2742,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary, fallthrough = fals
2720
2742
  }
2721
2743
 
2722
2744
  const tmp = localTmp(scope, `#typeswitch_tmp${++typeswitchDepth}${Prefs.typeswitchUniqueTmp ? uniqId() : ''}`, Valtype.i32);
2723
- const out = [
2745
+ let out = [
2724
2746
  ...type,
2725
2747
  [ Opcodes.local_set, tmp ],
2726
2748
  [ Opcodes.block, returns ]
@@ -2732,46 +2754,67 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary, fallthrough = fals
2732
2754
  if (!Array.isArray(bc)) {
2733
2755
  def = bc.default;
2734
2756
  bc = Object.entries(bc);
2757
+
2758
+ // turn keys back into numbers from keys
2759
+ for (const x of bc) {
2760
+ if (x[0] !== 'default') x[0] = +x[0];
2761
+ }
2735
2762
  }
2736
2763
 
2737
2764
  for (let i = 0; i < bc.length; i++) {
2738
- let [ type, wasm ] = bc[i];
2739
- if (type === 'default') {
2740
- def = wasm;
2765
+ let [ types, wasm ] = bc[i];
2766
+ if (types === 'default') {
2767
+ def = typeof wasm === 'function' ? wasm() : wasm;
2741
2768
  continue;
2742
2769
  }
2770
+ if (!Array.isArray(types)) types = [ types ];
2771
+
2772
+ const add = () => {
2773
+ if (typeof wasm === 'function') wasm = wasm();
2743
2774
 
2744
- if (Array.isArray(type)) {
2745
- for (let j = 0; j < type.length; j++) {
2775
+ for (let j = 0; j < types.length; j++) {
2746
2776
  out.push(
2747
2777
  [ Opcodes.local_get, tmp ],
2748
- ...number(type[j], Valtype.i32),
2778
+ ...number(types[j], Valtype.i32),
2749
2779
  [ Opcodes.i32_eq ]
2750
2780
  );
2751
2781
 
2752
2782
  if (j > 0) out.push([ Opcodes.i32_or ]);
2753
2783
  }
2754
- } else {
2784
+
2755
2785
  out.push(
2756
- [ Opcodes.local_get, tmp ],
2757
- ...number(type, Valtype.i32),
2758
- [ Opcodes.i32_eq ]
2786
+ [ Opcodes.if, Blocktype.void ],
2787
+ ...wasm,
2788
+ ...(fallthrough ? [] : [ [ Opcodes.br, 1 ] ]),
2789
+ [ Opcodes.end ]
2759
2790
  );
2760
- }
2791
+ };
2761
2792
 
2762
- out.push(
2763
- [ Opcodes.if, Blocktype.void, `TYPESWITCH|${Array.isArray(type) ? type.map(t => TYPE_NAMES[t]).join(',') : TYPE_NAMES[type]}` ],
2764
- ...wasm,
2765
- ...(fallthrough ? [] : [ [ Opcodes.br, 1 ] ]),
2766
- [ Opcodes.end ]
2767
- );
2793
+ if (globalThis.precompile) {
2794
+ // just magic precompile things™
2795
+ out.push([ null, 'typeswitch case start', types ]);
2796
+ add();
2797
+ out.push([ null, 'typeswitch case end' ]);
2798
+ } else {
2799
+ if (types.some(x => usedTypes.has(x))) {
2800
+ // type already used, just add it now
2801
+ add();
2802
+ } else {
2803
+ // type not used, add callback
2804
+ out.push([ null, () => {
2805
+ out = [];
2806
+ if (types.some(x => usedTypes.has(x))) add();
2807
+ return out;
2808
+ }]);
2809
+ }
2810
+ }
2768
2811
  }
2769
2812
 
2770
2813
  // default
2771
2814
  if (def) out.push(...def);
2772
2815
  else if (returns !== Blocktype.void) out.push(...number(0, returns));
2773
2816
 
2774
- out.push([ Opcodes.end, 'TYPESWITCH_end' ]);
2817
+ out.push([ Opcodes.end ]);
2775
2818
 
2776
2819
  typeswitchDepth--;
2777
2820
 
@@ -2921,6 +2964,7 @@ const generateVarDstr = (scope, kind, pattern, init, defaultValue, global) => {
2921
2964
 
2922
2965
  const typed = typedInput && pattern.typeAnnotation;
2923
2966
  let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(pattern).type != null));
2967
+ addVarMetadata(scope, name, global, { kind });
2924
2968
 
2925
2969
  if (typed) {
2926
2970
  addVarMetadata(scope, name, global, extractTypeAnnotation(pattern));
@@ -3268,7 +3312,7 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
3268
3312
 
3269
3313
  // todo: review last type usage here
3270
3314
  ...typeSwitch(scope, getNodeType(scope, object), {
3271
- [TYPES.array]: [
3315
+ [TYPES.array]: () => [
3272
3316
  ...objectWasm,
3273
3317
  Opcodes.i32_to_u,
3274
3318
 
@@ -3300,7 +3344,7 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
3300
3344
  ],
3301
3345
 
3302
3346
  ...wrapBC({
3303
- [TYPES.uint8array]: [
3347
+ [TYPES.uint8array]: () => [
3304
3348
  [ Opcodes.i32_add ],
3305
3349
  ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
3306
3350
 
@@ -3314,7 +3358,7 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
3314
3358
  Opcodes.i32_to_u,
3315
3359
  [ Opcodes.i32_store8, 0, 4 ]
3316
3360
  ],
3317
- [TYPES.uint8clampedarray]: [
3361
+ [TYPES.uint8clampedarray]: () => [
3318
3362
  [ Opcodes.i32_add ],
3319
3363
  ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
3320
3364
 
@@ -3332,7 +3376,7 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
3332
3376
  Opcodes.i32_to_u,
3333
3377
  [ Opcodes.i32_store8, 0, 4 ]
3334
3378
  ],
3335
- [TYPES.int8array]: [
3379
+ [TYPES.int8array]: () => [
3336
3380
  [ Opcodes.i32_add ],
3337
3381
  ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
3338
3382
 
@@ -3346,7 +3390,7 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
3346
3390
  Opcodes.i32_to,
3347
3391
  [ Opcodes.i32_store8, 0, 4 ]
3348
3392
  ],
3349
- [TYPES.uint16array]: [
3393
+ [TYPES.uint16array]: () => [
3350
3394
  ...number(2, Valtype.i32),
3351
3395
  [ Opcodes.i32_mul ],
3352
3396
  [ Opcodes.i32_add ],
@@ -3362,7 +3406,7 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
3362
3406
  Opcodes.i32_to_u,
3363
3407
  [ Opcodes.i32_store16, 0, 4 ]
3364
3408
  ],
3365
- [TYPES.int16array]: [
3409
+ [TYPES.int16array]: () => [
3366
3410
  ...number(2, Valtype.i32),
3367
3411
  [ Opcodes.i32_mul ],
3368
3412
  [ Opcodes.i32_add ],
@@ -3378,7 +3422,7 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
3378
3422
  Opcodes.i32_to,
3379
3423
  [ Opcodes.i32_store16, 0, 4 ]
3380
3424
  ],
3381
- [TYPES.uint32array]: [
3425
+ [TYPES.uint32array]: () => [
3382
3426
  ...number(4, Valtype.i32),
3383
3427
  [ Opcodes.i32_mul ],
3384
3428
  [ Opcodes.i32_add ],
@@ -3394,7 +3438,7 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
3394
3438
  Opcodes.i32_to_u,
3395
3439
  [ Opcodes.i32_store, 0, 4 ]
3396
3440
  ],
3397
- [TYPES.int32array]: [
3441
+ [TYPES.int32array]: () => [
3398
3442
  ...number(4, Valtype.i32),
3399
3443
  [ Opcodes.i32_mul ],
3400
3444
  [ Opcodes.i32_add ],
@@ -3410,7 +3454,7 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
3410
3454
  Opcodes.i32_to,
3411
3455
  [ Opcodes.i32_store, 0, 4 ]
3412
3456
  ],
3413
- [TYPES.float32array]: [
3457
+ [TYPES.float32array]: () => [
3414
3458
  ...number(4, Valtype.i32),
3415
3459
  [ Opcodes.i32_mul ],
3416
3460
  [ Opcodes.i32_add ],
@@ -3426,7 +3470,7 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
3426
3470
  [ Opcodes.f32_demote_f64 ],
3427
3471
  [ Opcodes.f32_store, 0, 4 ]
3428
3472
  ],
3429
- [TYPES.float64array]: [
3473
+ [TYPES.float64array]: () => [
3430
3474
  ...number(8, Valtype.i32),
3431
3475
  [ Opcodes.i32_mul ],
3432
3476
  [ Opcodes.i32_add ],
@@ -3520,6 +3564,9 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
3520
3564
  ];
3521
3565
  }
3522
3566
 
3567
+ // check not const
3568
+ if (local.metadata?.kind === 'const') return internalThrow(scope, 'TypeError', `Cannot assign to constant variable ${name}`, true);
3569
+
3523
3570
  if (op === '=') {
3524
3571
  return setLocalWithType(scope, name, isGlobal, decl.right, true);
3525
3572
  }
@@ -3662,16 +3709,15 @@ const generateUnary = (scope, decl) => {
3662
3709
  disposeLeftover(out);
3663
3710
 
3664
3711
  out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), [
3665
- [ TYPES.number, makeString(scope, 'number', false, '#typeof_result') ],
3666
- [ TYPES.boolean, makeString(scope, 'boolean', false, '#typeof_result') ],
3667
- [ TYPES.string, makeString(scope, 'string', false, '#typeof_result') ],
3668
- [ [ TYPES.undefined, TYPES.empty ], makeString(scope, 'undefined', false, '#typeof_result') ],
3669
- [ TYPES.function, makeString(scope, 'function', false, '#typeof_result') ],
3670
- [ TYPES.symbol, makeString(scope, 'symbol', false, '#typeof_result') ],
3671
- [ TYPES.bytestring, makeString(scope, 'string', false, '#typeof_result') ],
3712
+ [ TYPES.number, () => makeString(scope, 'number', false, '#typeof_result') ],
3713
+ [ TYPES.boolean, () => makeString(scope, 'boolean', false, '#typeof_result') ],
3714
+ [ [ TYPES.string, TYPES.bytestring ], () => makeString(scope, 'string', false, '#typeof_result') ],
3715
+ [ [ TYPES.undefined, TYPES.empty ], () => makeString(scope, 'undefined', false, '#typeof_result') ],
3716
+ [ TYPES.function, () => makeString(scope, 'function', false, '#typeof_result') ],
3717
+ [ TYPES.symbol, () => makeString(scope, 'symbol', false, '#typeof_result') ],
3672
3718
 
3673
3719
  // object and internal types
3674
- [ 'default', makeString(scope, 'object', false, '#typeof_result') ],
3720
+ [ 'default', () => makeString(scope, 'object', false, '#typeof_result') ],
3675
3721
  ]));
3676
3722
 
3677
3723
  return out;
@@ -3921,7 +3967,7 @@ const generateForOf = (scope, decl) => {
3921
3967
  // set type for local
3922
3968
  // todo: optimize away counter and use end pointer
3923
3969
  out.push(...typeSwitch(scope, iterType, {
3924
- [TYPES.array]: [
3970
+ [TYPES.array]: () => [
3925
3971
  [ Opcodes.loop, Blocktype.void ],
3926
3972
 
3927
3973
  [ Opcodes.local_get, pointer ],
@@ -3962,7 +4008,7 @@ const generateForOf = (scope, decl) => {
3962
4008
  [ Opcodes.end ]
3963
4009
  ],
3964
4010
 
3965
- [TYPES.string]: [
4011
+ [TYPES.string]: () => [
3966
4012
  ...setType(scope, tmpName, TYPES.string),
3967
4013
 
3968
4014
  // allocate out string
@@ -4017,7 +4063,7 @@ const generateForOf = (scope, decl) => {
4017
4063
  [ Opcodes.end ],
4018
4064
  [ Opcodes.end ]
4019
4065
  ],
4020
- [TYPES.bytestring]: [
4066
+ [TYPES.bytestring]: () => [
4021
4067
  ...setType(scope, tmpName, TYPES.bytestring),
4022
4068
 
4023
4069
  // allocate out string
@@ -4069,7 +4115,7 @@ const generateForOf = (scope, decl) => {
4069
4115
  [ Opcodes.end ]
4070
4116
  ],
4071
4117
 
4072
- [TYPES.set]: [
4118
+ [TYPES.set]: () => [
4073
4119
  [ Opcodes.loop, Blocktype.void ],
4074
4120
 
4075
4121
  [ Opcodes.local_get, pointer ],
@@ -4111,25 +4157,25 @@ const generateForOf = (scope, decl) => {
4111
4157
  ],
4112
4158
 
4113
4159
  ...wrapBC({
4114
- [TYPES.uint8array]: [
4160
+ [TYPES.uint8array]: () => [
4115
4161
  [ Opcodes.i32_add ],
4116
4162
 
4117
4163
  [ Opcodes.i32_load8_u, 0, 4 ],
4118
4164
  Opcodes.i32_from_u
4119
4165
  ],
4120
- [TYPES.uint8clampedarray]: [
4166
+ [TYPES.uint8clampedarray]: () => [
4121
4167
  [ Opcodes.i32_add ],
4122
4168
 
4123
4169
  [ Opcodes.i32_load8_u, 0, 4 ],
4124
4170
  Opcodes.i32_from_u
4125
4171
  ],
4126
- [TYPES.int8array]: [
4172
+ [TYPES.int8array]: () => [
4127
4173
  [ Opcodes.i32_add ],
4128
4174
 
4129
4175
  [ Opcodes.i32_load8_s, 0, 4 ],
4130
4176
  Opcodes.i32_from
4131
4177
  ],
4132
- [TYPES.uint16array]: [
4178
+ [TYPES.uint16array]: () => [
4133
4179
  ...number(2, Valtype.i32),
4134
4180
  [ Opcodes.i32_mul ],
4135
4181
  [ Opcodes.i32_add ],
@@ -4137,7 +4183,7 @@ const generateForOf = (scope, decl) => {
4137
4183
  [ Opcodes.i32_load16_u, 0, 4 ],
4138
4184
  Opcodes.i32_from_u
4139
4185
  ],
4140
- [TYPES.int16array]: [
4186
+ [TYPES.int16array]: () => [
4141
4187
  ...number(2, Valtype.i32),
4142
4188
  [ Opcodes.i32_mul ],
4143
4189
  [ Opcodes.i32_add ],
@@ -4145,7 +4191,7 @@ const generateForOf = (scope, decl) => {
4145
4191
  [ Opcodes.i32_load16_s, 0, 4 ],
4146
4192
  Opcodes.i32_from
4147
4193
  ],
4148
- [TYPES.uint32array]: [
4194
+ [TYPES.uint32array]: () => [
4149
4195
  ...number(4, Valtype.i32),
4150
4196
  [ Opcodes.i32_mul ],
4151
4197
  [ Opcodes.i32_add ],
@@ -4153,7 +4199,7 @@ const generateForOf = (scope, decl) => {
4153
4199
  [ Opcodes.i32_load, 0, 4 ],
4154
4200
  Opcodes.i32_from_u
4155
4201
  ],
4156
- [TYPES.int32array]: [
4202
+ [TYPES.int32array]: () => [
4157
4203
  ...number(4, Valtype.i32),
4158
4204
  [ Opcodes.i32_mul ],
4159
4205
  [ Opcodes.i32_add ],
@@ -4161,7 +4207,7 @@ const generateForOf = (scope, decl) => {
4161
4207
  [ Opcodes.i32_load, 0, 4 ],
4162
4208
  Opcodes.i32_from
4163
4209
  ],
4164
- [TYPES.float32array]: [
4210
+ [TYPES.float32array]: () => [
4165
4211
  ...number(4, Valtype.i32),
4166
4212
  [ Opcodes.i32_mul ],
4167
4213
  [ Opcodes.i32_add ],
@@ -4169,7 +4215,7 @@ const generateForOf = (scope, decl) => {
4169
4215
  [ Opcodes.f32_load, 0, 4 ],
4170
4216
  [ Opcodes.f64_promote_f32 ]
4171
4217
  ],
4172
- [TYPES.float64array]: [
4218
+ [TYPES.float64array]: () => [
4173
4219
  ...number(8, Valtype.i32),
4174
4220
  [ Opcodes.i32_mul ],
4175
4221
  [ Opcodes.i32_add ],
@@ -4348,7 +4394,7 @@ const generateForIn = (scope, decl) => {
4348
4394
  [TYPES.object]: out,
4349
4395
 
4350
4396
  // wrap for of object.keys
4351
- default: generate(scope, {
4397
+ default: () => generate(scope, {
4352
4398
  type: 'ForOfStatement',
4353
4399
  left: decl.left,
4354
4400
  body: decl.body,
@@ -4414,8 +4460,7 @@ const generateSwitch = (scope, decl) => {
4414
4460
  types.push(type);
4415
4461
 
4416
4462
  if (consequent.length !== 0) {
4417
- const o = generate(scope, { type: 'BlockStatement', body: consequent });
4418
- bc.push([ types, o ]);
4463
+ bc.push([ types, () => generate(scope, { type: 'BlockStatement', body: consequent }) ]);
4419
4464
  types = [];
4420
4465
  }
4421
4466
  }
@@ -4685,11 +4730,6 @@ const allocPage = (scope, reason, type) => {
4685
4730
  const ptr = i => i === 0 ? 16 : (i * pageSize);
4686
4731
  if (pages.has(reason)) return ptr(pages.get(reason).ind);
4687
4732
 
4688
- if (reason.startsWith('array:')) pages.hasArray = true;
4689
- if (reason.startsWith('string:')) pages.hasString = true;
4690
- if (reason.startsWith('bytestring:')) pages.hasByteString = true;
4691
- if (reason.includes('string:')) pages.hasAnyString = true;
4692
-
4693
4733
  const ind = pages.size;
4694
4734
  pages.set(reason, { ind, type });
4695
4735
 
@@ -4771,14 +4811,6 @@ const printStaticStr = str => {
4771
4811
  };
4772
4812
 
4773
4813
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype, intOut = false, typed = false) => {
4774
- if (itemType !== 'i16' && itemType !== 'i8') {
4775
- pages.hasArray = true;
4776
- } else {
4777
- pages.hasAnyString = true;
4778
- if (itemType === 'i8') pages.hasByteString = true;
4779
- else pages.hasString = true;
4780
- }
4781
-
4782
4814
  const out = [];
4783
4815
 
4784
4816
  const uniqueName = name === '$undeclared' ? name + uniqId() : name;
@@ -5121,11 +5153,19 @@ const withType = (scope, wasm, type) => [
5121
5153
  const wrapBC = (bc, { prelude = [], postlude = [] } = {}) => {
5122
5154
  const out = {};
5123
5155
  for (const x in bc) {
5124
- out[x] = [
5125
- ...prelude,
5126
- ...bc[x],
5127
- ...postlude
5128
- ];
5156
+ if (typeof bc[x] === 'function') {
5157
+ out[x] = () => [
5158
+ ...prelude,
5159
+ ...bc[x](),
5160
+ ...postlude
5161
+ ];
5162
+ } else {
5163
+ out[x] = [
5164
+ ...prelude,
5165
+ ...bc[x],
5166
+ ...postlude
5167
+ ];
5168
+ }
5129
5169
  }
5130
5170
 
5131
5171
  return out;
@@ -5316,12 +5356,12 @@ const generateMember = (scope, decl, _global, _name, _objectWasm = undefined) =>
5316
5356
  const propertyWasm = [ [ Opcodes.local_get, localTmp(scope, '#member_prop') ] ];
5317
5357
 
5318
5358
  const out = typeSwitch(scope, getNodeType(scope, object), {
5319
- [TYPES.array]: [
5359
+ [TYPES.array]: () => [
5320
5360
  ...loadArray(scope, objectWasm, propertyWasm),
5321
5361
  ...setLastType(scope)
5322
5362
  ],
5323
5363
 
5324
- [TYPES.string]: [
5364
+ [TYPES.string]: () => [
5325
5365
  // allocate out string
5326
5366
  [ Opcodes.call, includeBuiltin(scope, '__Porffor_allocate').index ],
5327
5367
  [ Opcodes.local_tee, localTmp(scope, '#member_allocd', Valtype.i32) ],
@@ -5355,7 +5395,7 @@ const generateMember = (scope, decl, _global, _name, _objectWasm = undefined) =>
5355
5395
  ...setLastType(scope, TYPES.string)
5356
5396
  ],
5357
5397
 
5358
- [TYPES.bytestring]: [
5398
+ [TYPES.bytestring]: () => [
5359
5399
  // allocate out string
5360
5400
  [ Opcodes.call, includeBuiltin(scope, '__Porffor_allocate').index ],
5361
5401
  [ Opcodes.local_tee, localTmp(scope, '#member_allocd', Valtype.i32) ],
@@ -5467,7 +5507,7 @@ const generateMember = (scope, decl, _global, _name, _objectWasm = undefined) =>
5467
5507
  [TYPES.undefined]: internalThrow(scope, 'TypeError', 'Cannot read property of undefined', true),
5468
5508
 
5469
5509
  // default: internalThrow(scope, 'TypeError', 'Unsupported member expression object', true)
5470
- default: [
5510
+ default: () => [
5471
5511
  ...objectWasm,
5472
5512
  Opcodes.i32_to_u,
5473
5513
  ...getNodeType(scope, object),
@@ -6186,6 +6226,7 @@ export default program => {
6186
6226
  data = [];
6187
6227
  currentFuncIndex = importedFuncs.length;
6188
6228
  typeswitchDepth = 0;
6229
+ usedTypes = new Set([ TYPES.empty, TYPES.undefined, TYPES.number, TYPES.boolean, TYPES.function ]);
6189
6230
 
6190
6231
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
6191
6232
 
@@ -6226,8 +6267,6 @@ export default program => {
6226
6267
 
6227
6268
  const [ main ] = generateFunc({}, program);
6228
6269
 
6229
- delete globals['#ind'];
6230
-
6231
6270
  // if wanted and blank main func and other exports, remove it
6232
6271
  if (Prefs.rmBlankMain && main.wasm.length === 0 && funcs.some(x => x.export)) funcs.splice(main.index - importedFuncs.length, 1);
6233
6272
 
@@ -6235,7 +6274,16 @@ export default program => {
6235
6274
  // todo: these should just be deleted once able
6236
6275
  for (let i = 0; i < funcs.length; i++) {
6237
6276
  const f = funcs[i];
6238
- if (f.internal || f.wasm) {
6277
+ if (f.wasm) {
6278
+ // run callbacks
6279
+ const wasm = f.wasm;
6280
+ for (let j = 0; j < wasm.length; j++) {
6281
+ const o = wasm[j];
6282
+ if (o[0] === null && typeof o[1] === 'function') {
6283
+ wasm.splice(j--, 1, ...o[1]());
6284
+ }
6285
+ }
6286
+
6239
6287
  continue;
6240
6288
  }
6241
6289
 
@@ -6282,5 +6330,9 @@ export default program => {
6282
6330
  // }
6283
6331
  // }
6284
6332
 
6333
+ delete globals['#ind'];
6334
+
6335
+ // console.log([...usedTypes].map(x => TYPE_NAMES[x]));
6336
+
6285
6337
  return { funcs, globals, tags, exceptions, pages, data };
6286
6338
  };