porffor 0.2.0-c7b7423 → 0.2.0-dcc06c8

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.
@@ -1,5 +1,5 @@
1
1
  import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
2
- import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
2
+ import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from "./encoding.js";
3
3
  import { operatorOpcode } from "./expression.js";
4
4
  import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
5
5
  import { PrototypeFuncs } from "./prototype.js";
@@ -7,6 +7,7 @@ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enfor
7
7
  import { log } from "./log.js";
8
8
  import parse from "./parse.js";
9
9
  import * as Rhemyn from "../rhemyn/compile.js";
10
+ import Prefs from './prefs.js';
10
11
 
11
12
  let globals = {};
12
13
  let globalInd = 0;
@@ -55,7 +56,7 @@ const todo = msg => {
55
56
  };
56
57
 
57
58
  const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
58
- const generate = (scope, decl, global = false, name = undefined) => {
59
+ const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
59
60
  switch (decl.type) {
60
61
  case 'BinaryExpression':
61
62
  return generateBinaryExp(scope, decl, global, name);
@@ -68,7 +69,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
68
69
 
69
70
  case 'ArrowFunctionExpression':
70
71
  case 'FunctionDeclaration':
71
- generateFunc(scope, decl);
72
+ const func = generateFunc(scope, decl);
73
+
74
+ if (decl.type.endsWith('Expression')) {
75
+ return number(func.index);
76
+ }
77
+
72
78
  return [];
73
79
 
74
80
  case 'BlockStatement':
@@ -81,7 +87,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
81
87
  return generateExp(scope, decl);
82
88
 
83
89
  case 'CallExpression':
84
- return generateCall(scope, decl, global, name);
90
+ return generateCall(scope, decl, global, name, valueUnused);
85
91
 
86
92
  case 'NewExpression':
87
93
  return generateNew(scope, decl, global, name);
@@ -155,7 +161,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
155
161
 
156
162
  case 'TaggedTemplateExpression': {
157
163
  const funcs = {
158
- asm: str => {
164
+ __Porffor_wasm: str => {
159
165
  let out = [];
160
166
 
161
167
  for (const line of str.split('\n')) {
@@ -174,7 +180,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
174
180
  }
175
181
 
176
182
  if (asm[0] === 'memory') {
177
- allocPage('asm instrinsic');
183
+ allocPage(scope, 'asm instrinsic');
178
184
  // todo: add to store/load offset insts
179
185
  continue;
180
186
  }
@@ -191,29 +197,44 @@ const generate = (scope, decl, global = false, name = undefined) => {
191
197
  return out;
192
198
  },
193
199
 
194
- __internal_print_type: str => {
195
- const type = getType(scope, str) - TYPES.number;
200
+ __Porffor_bs: str => [
201
+ ...makeString(scope, str, undefined, undefined, true),
196
202
 
197
- return [
198
- ...number(type),
199
- [ Opcodes.call, importedFuncs.print ],
203
+ ...number(TYPES._bytestring, Valtype.i32),
204
+ setLastType(scope)
205
+ ],
206
+ __Porffor_s: str => [
207
+ ...makeString(scope, str, undefined, undefined, false),
200
208
 
201
- // newline
202
- ...number(10),
203
- [ Opcodes.call, importedFuncs.printChar ]
204
- ];
205
- }
206
- }
209
+ ...number(TYPES.string, Valtype.i32),
210
+ setLastType(scope)
211
+ ],
212
+ };
207
213
 
208
214
  const name = decl.tag.name;
209
215
  // hack for inline asm
210
216
  if (!funcs[name]) return todo('tagged template expressions not implemented');
211
217
 
212
- const str = decl.quasi.quasis[0].value.raw;
218
+ const { quasis, expressions } = decl.quasi;
219
+ let str = quasis[0].value.raw;
220
+
221
+ for (let i = 0; i < expressions.length; i++) {
222
+ const e = expressions[i];
223
+ str += lookupName(scope, e.name)[0];
224
+
225
+ str += quasis[i + 1].value.raw;
226
+ }
227
+
213
228
  return funcs[name](str);
214
229
  }
215
230
 
216
231
  default:
232
+ // ignore typescript nodes
233
+ if (decl.type.startsWith('TS') ||
234
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
235
+ return [];
236
+ }
237
+
217
238
  return todo(`no generation for ${decl.type}!`);
218
239
  }
219
240
  };
@@ -264,25 +285,25 @@ const generateIdent = (scope, decl) => {
264
285
  const name = mapName(rawName);
265
286
  let local = scope.locals[rawName];
266
287
 
267
- if (builtinVars[name]) {
288
+ if (Object.hasOwn(builtinVars, name)) {
268
289
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
269
290
  return builtinVars[name];
270
291
  }
271
292
 
272
- if (builtinFuncs[name] || internalConstrs[name]) {
293
+ if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
273
294
  // todo: return an actual something
274
295
  return number(1);
275
296
  }
276
297
 
277
- if (local === undefined) {
298
+ if (local?.idx === undefined) {
278
299
  // no local var with name
279
- if (importedFuncs.hasOwnProperty(name)) return number(importedFuncs[name]);
280
- if (funcIndex[name] !== undefined) return number(funcIndex[name]);
300
+ if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
301
+ if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
281
302
 
282
- if (globals[name] !== undefined) return [ [ Opcodes.global_get, globals[name].idx ] ];
303
+ if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
283
304
  }
284
305
 
285
- if (local === undefined && rawName.startsWith('__')) {
306
+ if (local?.idx === undefined && rawName.startsWith('__')) {
286
307
  // return undefined if unknown key in already known var
287
308
  let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
288
309
  if (parent.includes('_')) parent = '__' + parent;
@@ -291,7 +312,7 @@ const generateIdent = (scope, decl) => {
291
312
  if (!parentLookup[1]) return number(UNDEFINED);
292
313
  }
293
314
 
294
- if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
315
+ if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
295
316
 
296
317
  return [ [ Opcodes.local_get, local.idx ] ];
297
318
  };
@@ -360,12 +381,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
360
381
  ...right,
361
382
  // note type
362
383
  ...rightType,
363
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
384
+ setLastType(scope),
364
385
  [ Opcodes.else ],
365
386
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
366
387
  // note type
367
388
  ...leftType,
368
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
389
+ setLastType(scope),
369
390
  [ Opcodes.end ],
370
391
  Opcodes.i32_from
371
392
  ];
@@ -379,12 +400,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
379
400
  ...right,
380
401
  // note type
381
402
  ...rightType,
382
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
403
+ setLastType(scope),
383
404
  [ Opcodes.else ],
384
405
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
385
406
  // note type
386
407
  ...leftType,
387
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
408
+ setLastType(scope),
388
409
  [ Opcodes.end ]
389
410
  ];
390
411
  };
@@ -399,9 +420,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
399
420
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
400
421
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
401
422
 
402
- const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
403
- if (aotWFA) addVarMeta(name, { wellFormed: undefined });
404
-
405
423
  if (assign) {
406
424
  const pointer = arrays.get(name ?? '$undeclared');
407
425
 
@@ -675,6 +693,15 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
675
693
  [ Opcodes.i32_eqz ], */
676
694
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
677
695
  ],
696
+ [TYPES._bytestring]: [ // duplicate of string
697
+ [ Opcodes.local_get, tmp ],
698
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
699
+
700
+ // get length
701
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
702
+
703
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
704
+ ],
678
705
  default: def
679
706
  }, intOut ? Valtype.i32 : valtypeBinary)
680
707
  ];
@@ -702,6 +729,17 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
702
729
  [ Opcodes.i32_eqz ],
703
730
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
704
731
  ],
732
+ [TYPES._bytestring]: [ // duplicate of string
733
+ [ Opcodes.local_get, tmp ],
734
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
735
+
736
+ // get length
737
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
738
+
739
+ // if length == 0
740
+ [ Opcodes.i32_eqz ],
741
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
742
+ ],
705
743
  default: [
706
744
  // if value == 0
707
745
  [ Opcodes.local_get, tmp ],
@@ -755,11 +793,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
755
793
  return performLogicOp(scope, op, left, right, leftType, rightType);
756
794
  }
757
795
 
796
+ const knownLeft = knownType(scope, leftType);
797
+ const knownRight = knownType(scope, rightType);
798
+
758
799
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
759
800
  const strictOp = op === '===' || op === '!==';
760
801
 
761
802
  const startOut = [], endOut = [];
762
- const finalise = out => startOut.concat(out, endOut);
803
+ const finalize = out => startOut.concat(out, endOut);
763
804
 
764
805
  // if strict (in)equal check types match
765
806
  if (strictOp) {
@@ -804,31 +845,32 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
804
845
  // todo: if equality op and an operand is undefined, return false
805
846
  // todo: niche null hell with 0
806
847
 
807
- // if (leftType === TYPES.string || rightType === TYPES.string) {
808
- // if (op === '+') {
809
- // // string concat (a + b)
810
- // return finalise(concatStrings(scope, left, right, _global, _name, assign));
811
- // }
812
-
813
- // // not an equality op, NaN
814
- // if (!eqOp) return finalise(number(NaN));
815
-
816
- // // else leave bool ops
817
- // // todo: convert string to number if string and number/bool
818
- // // todo: string (>|>=|<|<=) string
819
-
820
- // // string comparison
821
- // if (op === '===' || op === '==') {
822
- // return finalise(compareStrings(scope, left, right));
823
- // }
824
-
825
- // if (op === '!==' || op === '!=') {
826
- // return finalise([
827
- // ...compareStrings(scope, left, right),
828
- // [ Opcodes.i32_eqz ]
829
- // ]);
830
- // }
831
- // }
848
+ // todo: this should be dynamic but for now only static
849
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) {
850
+ if (op === '+') {
851
+ // string concat (a + b)
852
+ return concatStrings(scope, left, right, _global, _name, assign);
853
+ }
854
+
855
+ // not an equality op, NaN
856
+ if (!eqOp) return number(NaN);
857
+
858
+ // else leave bool ops
859
+ // todo: convert string to number if string and number/bool
860
+ // todo: string (>|>=|<|<=) string
861
+
862
+ // string comparison
863
+ if (op === '===' || op === '==') {
864
+ return compareStrings(scope, left, right);
865
+ }
866
+
867
+ if (op === '!==' || op === '!=') {
868
+ return [
869
+ ...compareStrings(scope, left, right),
870
+ [ Opcodes.i32_eqz ]
871
+ ];
872
+ }
873
+ }
832
874
 
833
875
  let ops = operatorOpcode[valtype][op];
834
876
 
@@ -838,7 +880,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
838
880
  includeBuiltin(scope, builtinName);
839
881
  const idx = funcIndex[builtinName];
840
882
 
841
- return finalise([
883
+ return finalize([
842
884
  ...left,
843
885
  ...right,
844
886
  [ Opcodes.call, idx ]
@@ -852,7 +894,13 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
852
894
 
853
895
  let tmpLeft, tmpRight;
854
896
  // if equal op, check if strings for compareStrings
855
- if (op === '===' || op === '==' || op === '!==' || op === '!=') {
897
+ if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
898
+ // todo: intelligent partial skip later
899
+ // if neither known are string, stop this madness
900
+ if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
901
+ return;
902
+ }
903
+
856
904
  tmpLeft = localTmp(scope, '__tmpop_left');
857
905
  tmpRight = localTmp(scope, '__tmpop_right');
858
906
 
@@ -886,7 +934,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
886
934
  [ Opcodes.i32_or ],
887
935
  [ Opcodes.if, Blocktype.void ],
888
936
  ...number(0, Valtype.i32),
889
- [ Opcodes.br, 1 ],
937
+ [ Opcodes.br, 2 ],
890
938
  [ Opcodes.end ],
891
939
 
892
940
  ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
@@ -902,9 +950,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
902
950
  // endOut.push(stringOnly([ Opcodes.end ]));
903
951
  endOut.unshift(stringOnly([ Opcodes.end ]));
904
952
  // }
905
- }
953
+ })();
906
954
 
907
- return finalise([
955
+ return finalize([
908
956
  ...left,
909
957
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
910
958
  ...right,
@@ -933,6 +981,18 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
933
981
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
934
982
  }
935
983
 
984
+ if (typeof wasm === 'function') {
985
+ const scope = {
986
+ name,
987
+ params,
988
+ locals,
989
+ returns,
990
+ localInd: allLocals.length,
991
+ };
992
+
993
+ wasm = wasm(scope, { TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString });
994
+ }
995
+
936
996
  let baseGlobalIdx, i = 0;
937
997
  for (const type of globalTypes) {
938
998
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -1013,7 +1073,8 @@ const TYPES = {
1013
1073
 
1014
1074
  // these are not "typeof" types but tracked internally
1015
1075
  _array: 0x10,
1016
- _regexp: 0x11
1076
+ _regexp: 0x11,
1077
+ _bytestring: 0x12
1017
1078
  };
1018
1079
 
1019
1080
  const TYPE_NAMES = {
@@ -1027,13 +1088,17 @@ const TYPE_NAMES = {
1027
1088
  [TYPES.bigint]: 'BigInt',
1028
1089
 
1029
1090
  [TYPES._array]: 'Array',
1030
- [TYPES._regexp]: 'RegExp'
1091
+ [TYPES._regexp]: 'RegExp',
1092
+ [TYPES._bytestring]: 'ByteString'
1031
1093
  };
1032
1094
 
1033
1095
  const getType = (scope, _name) => {
1034
1096
  const name = mapName(_name);
1035
1097
 
1098
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1036
1099
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1100
+
1101
+ if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
1037
1102
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1038
1103
 
1039
1104
  let type = TYPES.undefined;
@@ -1051,11 +1116,13 @@ const setType = (scope, _name, type) => {
1051
1116
 
1052
1117
  const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
1053
1118
 
1119
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return [];
1054
1120
  if (scope.locals[name]) return [
1055
1121
  ...out,
1056
1122
  [ Opcodes.local_set, scope.locals[name + '#type'].idx ]
1057
1123
  ];
1058
1124
 
1125
+ if (typedInput && globals[name]?.metadata?.type != null) return [];
1059
1126
  if (globals[name]) return [
1060
1127
  ...out,
1061
1128
  [ Opcodes.global_set, globals[name + '#type'].idx ]
@@ -1064,11 +1131,22 @@ const setType = (scope, _name, type) => {
1064
1131
  // throw new Error('could not find var');
1065
1132
  };
1066
1133
 
1134
+ const getLastType = scope => {
1135
+ scope.gotLastType = true;
1136
+ return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1137
+ };
1138
+
1139
+ const setLastType = scope => {
1140
+ return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1141
+ };
1142
+
1067
1143
  const getNodeType = (scope, node) => {
1068
1144
  const inner = () => {
1069
1145
  if (node.type === 'Literal') {
1070
1146
  if (node.regex) return TYPES._regexp;
1071
1147
 
1148
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1149
+
1072
1150
  return TYPES[typeof node.value];
1073
1151
  }
1074
1152
 
@@ -1082,6 +1160,15 @@ const getNodeType = (scope, node) => {
1082
1160
 
1083
1161
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1084
1162
  const name = node.callee.name;
1163
+ if (!name) {
1164
+ // iife
1165
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1166
+
1167
+ // presume
1168
+ // todo: warn here?
1169
+ return TYPES.number;
1170
+ }
1171
+
1085
1172
  const func = funcs.find(x => x.name === name);
1086
1173
 
1087
1174
  if (func) {
@@ -1092,7 +1179,19 @@ const getNodeType = (scope, node) => {
1092
1179
  if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1093
1180
  if (internalConstrs[name]) return internalConstrs[name].type;
1094
1181
 
1095
- if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1182
+ // check if this is a prototype function
1183
+ // if so and there is only one impl (eg charCodeAt)
1184
+ // use that return type as that is the only possibility
1185
+ // (if non-matching type it would error out)
1186
+ if (name.startsWith('__')) {
1187
+ const spl = name.slice(2).split('_');
1188
+
1189
+ const func = spl[spl.length - 1];
1190
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
1191
+ if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1192
+ }
1193
+
1194
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1096
1195
 
1097
1196
  // presume
1098
1197
  // todo: warn here?
@@ -1140,6 +1239,14 @@ const getNodeType = (scope, node) => {
1140
1239
 
1141
1240
  if (node.type === 'BinaryExpression') {
1142
1241
  if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1242
+ if (node.operator !== '+') return TYPES.number;
1243
+
1244
+ const knownLeft = knownType(scope, getNodeType(scope, node.left));
1245
+ const knownRight = knownType(scope, getNodeType(scope, node.right));
1246
+
1247
+ // todo: this should be dynamic but for now only static
1248
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1249
+
1143
1250
  return TYPES.number;
1144
1251
 
1145
1252
  // todo: string concat types
@@ -1164,7 +1271,7 @@ const getNodeType = (scope, node) => {
1164
1271
  if (node.operator === '!') return TYPES.boolean;
1165
1272
  if (node.operator === 'void') return TYPES.undefined;
1166
1273
  if (node.operator === 'delete') return TYPES.boolean;
1167
- if (node.operator === 'typeof') return TYPES.string;
1274
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
1168
1275
 
1169
1276
  return TYPES.number;
1170
1277
  }
@@ -1173,11 +1280,17 @@ const getNodeType = (scope, node) => {
1173
1280
  // hack: if something.length, number type
1174
1281
  if (node.property.name === 'length') return TYPES.number;
1175
1282
 
1176
- // we cannot guess
1283
+ // ts hack
1284
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1285
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1286
+
1287
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1288
+
1289
+ // presume
1177
1290
  return TYPES.number;
1178
1291
  }
1179
1292
 
1180
- if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1293
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1181
1294
 
1182
1295
  // presume
1183
1296
  // todo: warn here?
@@ -1193,8 +1306,8 @@ const getNodeType = (scope, node) => {
1193
1306
  const generateLiteral = (scope, decl, global, name) => {
1194
1307
  if (decl.value === null) return number(NULL);
1195
1308
 
1309
+ // hack: just return 1 for regex literals
1196
1310
  if (decl.regex) {
1197
- scope.regex[name] = decl.regex;
1198
1311
  return number(1);
1199
1312
  }
1200
1313
 
@@ -1207,16 +1320,7 @@ const generateLiteral = (scope, decl, global, name) => {
1207
1320
  return number(decl.value ? 1 : 0);
1208
1321
 
1209
1322
  case 'string':
1210
- const str = decl.value;
1211
- const rawElements = new Array(str.length);
1212
- let j = 0;
1213
- for (let i = 0; i < str.length; i++) {
1214
- rawElements[i] = str.charCodeAt(i);
1215
- }
1216
-
1217
- return makeArray(scope, {
1218
- rawElements
1219
- }, global, name, false, 'i16')[0];
1323
+ return makeString(scope, decl.value, global, name);
1220
1324
 
1221
1325
  default:
1222
1326
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -1237,9 +1341,9 @@ const countLeftover = wasm => {
1237
1341
 
1238
1342
  if (depth === 0)
1239
1343
  if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1240
- 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.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
1344
+ 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.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] < 0x0a)) {}
1241
1345
  else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1242
- else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
1346
+ else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1243
1347
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1244
1348
  else if (inst[0] === Opcodes.return) count = 0;
1245
1349
  else if (inst[0] === Opcodes.call) {
@@ -1265,7 +1369,7 @@ const disposeLeftover = wasm => {
1265
1369
  const generateExp = (scope, decl) => {
1266
1370
  const expression = decl.expression;
1267
1371
 
1268
- const out = generate(scope, expression);
1372
+ const out = generate(scope, expression, undefined, undefined, true);
1269
1373
  disposeLeftover(out);
1270
1374
 
1271
1375
  return out;
@@ -1323,7 +1427,7 @@ const RTArrayUtil = {
1323
1427
  ]
1324
1428
  };
1325
1429
 
1326
- const generateCall = (scope, decl, _global, _name) => {
1430
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1327
1431
  /* const callee = decl.callee;
1328
1432
  const args = decl.arguments;
1329
1433
 
@@ -1356,13 +1460,13 @@ const generateCall = (scope, decl, _global, _name) => {
1356
1460
  const finalStatement = parsed.body[parsed.body.length - 1];
1357
1461
  out.push(
1358
1462
  ...getNodeType(scope, finalStatement),
1359
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1463
+ setLastType(scope)
1360
1464
  );
1361
1465
  } else if (countLeftover(out) === 0) {
1362
1466
  out.push(...number(UNDEFINED));
1363
1467
  out.push(
1364
1468
  ...number(TYPES.undefined, Valtype.i32),
1365
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1469
+ setLastType(scope)
1366
1470
  );
1367
1471
  }
1368
1472
 
@@ -1380,8 +1484,7 @@ const generateCall = (scope, decl, _global, _name) => {
1380
1484
  if (name && name.startsWith('__')) {
1381
1485
  const spl = name.slice(2).split('_');
1382
1486
 
1383
- const func = spl[spl.length - 1];
1384
- protoName = func;
1487
+ protoName = spl[spl.length - 1];
1385
1488
 
1386
1489
  target = { ...decl.callee };
1387
1490
  target.name = spl.slice(0, -1).join('_');
@@ -1390,8 +1493,8 @@ const generateCall = (scope, decl, _global, _name) => {
1390
1493
  // literal.func()
1391
1494
  if (!name && decl.callee.type === 'MemberExpression') {
1392
1495
  // megahack for /regex/.func()
1393
- if (decl.callee.object.regex) {
1394
- const funcName = decl.callee.property.name;
1496
+ const funcName = decl.callee.property.name;
1497
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1395
1498
  const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1396
1499
 
1397
1500
  funcIndex[func.name] = func.index;
@@ -1407,12 +1510,11 @@ const generateCall = (scope, decl, _global, _name) => {
1407
1510
  Opcodes.i32_from_u,
1408
1511
 
1409
1512
  ...number(TYPES.boolean, Valtype.i32),
1410
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1513
+ setLastType(scope)
1411
1514
  ];
1412
1515
  }
1413
1516
 
1414
- const func = decl.callee.property.name;
1415
- protoName = func;
1517
+ protoName = decl.callee.property.name;
1416
1518
 
1417
1519
  target = decl.callee.object;
1418
1520
  }
@@ -1434,8 +1536,7 @@ const generateCall = (scope, decl, _global, _name) => {
1434
1536
 
1435
1537
  if (protoName) {
1436
1538
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1437
- const f = prototypeFuncs[x][protoName];
1438
- if (f) acc[x] = f;
1539
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1439
1540
  return acc;
1440
1541
  }, {});
1441
1542
 
@@ -1444,10 +1545,18 @@ const generateCall = (scope, decl, _global, _name) => {
1444
1545
  // use local for cached i32 length as commonly used
1445
1546
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1446
1547
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1447
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1448
1548
 
1449
1549
  // TODO: long-term, prototypes should be their individual separate funcs
1450
1550
 
1551
+ const rawPointer = [
1552
+ ...generate(scope, target),
1553
+ Opcodes.i32_to_u
1554
+ ];
1555
+
1556
+ const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
1557
+ const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
1558
+
1559
+ let allOptUnused = true;
1451
1560
  let lengthI32CacheUsed = false;
1452
1561
  const protoBC = {};
1453
1562
  for (const x in protoCands) {
@@ -1457,7 +1566,7 @@ const generateCall = (scope, decl, _global, _name) => {
1457
1566
  ...RTArrayUtil.getLength(getPointer),
1458
1567
 
1459
1568
  ...number(TYPES.number, Valtype.i32),
1460
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
1569
+ setLastType(scope)
1461
1570
  ];
1462
1571
  continue;
1463
1572
  }
@@ -1467,6 +1576,7 @@ const generateCall = (scope, decl, _global, _name) => {
1467
1576
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1468
1577
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1469
1578
 
1579
+ let optUnused = false;
1470
1580
  const protoOut = protoFunc(getPointer, {
1471
1581
  getCachedI32: () => {
1472
1582
  lengthI32CacheUsed = true;
@@ -1481,23 +1591,30 @@ const generateCall = (scope, decl, _global, _name) => {
1481
1591
  return makeArray(scope, {
1482
1592
  rawElements: new Array(length)
1483
1593
  }, _global, _name, true, itemType);
1594
+ }, () => {
1595
+ optUnused = true;
1596
+ return unusedValue;
1484
1597
  });
1485
1598
 
1599
+ if (!optUnused) allOptUnused = false;
1600
+
1486
1601
  protoBC[x] = [
1487
- [ Opcodes.block, valtypeBinary ],
1602
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1488
1603
  ...protoOut,
1489
1604
 
1490
1605
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1491
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
1606
+ setLastType(scope),
1492
1607
  [ Opcodes.end ]
1493
1608
  ];
1494
1609
  }
1495
1610
 
1496
- return [
1497
- ...generate(scope, target),
1611
+ // todo: if some cands use optUnused and some don't, we will probably crash
1498
1612
 
1499
- Opcodes.i32_to_u,
1500
- [ Opcodes.local_set, pointerLocal ],
1613
+ return [
1614
+ ...(usePointerCache ? [
1615
+ ...rawPointer,
1616
+ [ Opcodes.local_set, pointerLocal ],
1617
+ ] : []),
1501
1618
 
1502
1619
  ...(!lengthI32CacheUsed ? [] : [
1503
1620
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1509,7 +1626,7 @@ const generateCall = (scope, decl, _global, _name) => {
1509
1626
 
1510
1627
  // TODO: error better
1511
1628
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1512
- }, valtypeBinary),
1629
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1513
1630
  ];
1514
1631
  }
1515
1632
  }
@@ -1556,7 +1673,9 @@ const generateCall = (scope, decl, _global, _name) => {
1556
1673
  const func = funcs.find(x => x.index === idx);
1557
1674
 
1558
1675
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1559
- const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
1676
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1677
+ const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
1678
+ const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1560
1679
 
1561
1680
  let args = decl.arguments;
1562
1681
  if (func && args.length < paramCount) {
@@ -1574,12 +1693,12 @@ const generateCall = (scope, decl, _global, _name) => {
1574
1693
  let out = [];
1575
1694
  for (const arg of args) {
1576
1695
  out = out.concat(generate(scope, arg));
1577
- if (userFunc) out = out.concat(getNodeType(scope, arg));
1696
+ if (typedParams) out = out.concat(getNodeType(scope, arg));
1578
1697
  }
1579
1698
 
1580
1699
  out.push([ Opcodes.call, idx ]);
1581
1700
 
1582
- if (!userFunc) {
1701
+ if (!typedReturns) {
1583
1702
  // let type;
1584
1703
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1585
1704
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1589,7 +1708,7 @@ const generateCall = (scope, decl, _global, _name) => {
1589
1708
  // ...number(type, Valtype.i32),
1590
1709
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1591
1710
  // );
1592
- } else out.push([ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]);
1711
+ } else out.push(setLastType(scope));
1593
1712
 
1594
1713
  return out;
1595
1714
  };
@@ -1614,9 +1733,118 @@ const unhackName = name => {
1614
1733
  return name;
1615
1734
  };
1616
1735
 
1736
+ const knownType = (scope, type) => {
1737
+ if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
1738
+ return type[0][1];
1739
+ }
1740
+
1741
+ if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
1742
+ const idx = type[0][1];
1743
+
1744
+ // type idx = var idx + 1
1745
+ const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
1746
+ if (v.metadata?.type != null) return v.metadata.type;
1747
+ }
1748
+
1749
+ return null;
1750
+ };
1751
+
1752
+ const brTable = (input, bc, returns) => {
1753
+ const out = [];
1754
+ const keys = Object.keys(bc);
1755
+ const count = keys.length;
1756
+
1757
+ if (count === 1) {
1758
+ // return [
1759
+ // ...input,
1760
+ // ...bc[keys[0]]
1761
+ // ];
1762
+ return bc[keys[0]];
1763
+ }
1764
+
1765
+ if (count === 2) {
1766
+ // just use if else
1767
+ const other = keys.find(x => x !== 'default');
1768
+ return [
1769
+ ...input,
1770
+ ...number(other, Valtype.i32),
1771
+ [ Opcodes.i32_eq ],
1772
+ [ Opcodes.if, returns ],
1773
+ ...bc[other],
1774
+ [ Opcodes.else ],
1775
+ ...bc.default,
1776
+ [ Opcodes.end ]
1777
+ ];
1778
+ }
1779
+
1780
+ for (let i = 0; i < count; i++) {
1781
+ if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
1782
+ else out.push([ Opcodes.block, Blocktype.void ]);
1783
+ }
1784
+
1785
+ const nums = keys.filter(x => +x);
1786
+ const offset = Math.min(...nums);
1787
+ const max = Math.max(...nums);
1788
+
1789
+ const table = [];
1790
+ let br = 1;
1791
+
1792
+ for (let i = offset; i <= max; i++) {
1793
+ // if branch for this num, go to that block
1794
+ if (bc[i]) {
1795
+ table.push(br);
1796
+ br++;
1797
+ continue;
1798
+ }
1799
+
1800
+ // else default
1801
+ table.push(0);
1802
+ }
1803
+
1804
+ out.push(
1805
+ [ Opcodes.block, Blocktype.void ],
1806
+ ...input,
1807
+ ...(offset > 0 ? [
1808
+ ...number(offset, Valtype.i32),
1809
+ [ Opcodes.i32_sub ]
1810
+ ] : []),
1811
+ [ Opcodes.br_table, ...encodeVector(table), 0 ]
1812
+ );
1813
+
1814
+ // if you can guess why we sort the wrong way and then reverse
1815
+ // (instead of just sorting the correct way)
1816
+ // dm me and if you are correct and the first person
1817
+ // I will somehow shout you out or something
1818
+ const orderedBc = keys.sort((a, b) => b - a).reverse();
1819
+
1820
+ br = count - 1;
1821
+ for (const x of orderedBc) {
1822
+ out.push(
1823
+ [ Opcodes.end ],
1824
+ ...bc[x],
1825
+ ...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
1826
+ );
1827
+ br--;
1828
+ }
1829
+
1830
+ return [
1831
+ ...out,
1832
+ [ Opcodes.end, 'br table end' ]
1833
+ ];
1834
+ };
1835
+
1617
1836
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1618
- const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1837
+ if (!Prefs.bytestring) delete bc[TYPES._bytestring];
1838
+
1839
+ const known = knownType(scope, type);
1840
+ if (known != null) {
1841
+ return bc[known] ?? bc.default;
1842
+ }
1843
+
1844
+ if (Prefs.typeswitchUseBrtable)
1845
+ return brTable(type, bc, returns);
1619
1846
 
1847
+ const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1620
1848
  const out = [
1621
1849
  ...type,
1622
1850
  [ Opcodes.local_set, tmp ],
@@ -1648,7 +1876,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1648
1876
  return out;
1649
1877
  };
1650
1878
 
1651
- const allocVar = (scope, name, global = false) => {
1879
+ const allocVar = (scope, name, global = false, type = true) => {
1652
1880
  const target = global ? globals : scope.locals;
1653
1881
 
1654
1882
  // already declared
@@ -1662,12 +1890,59 @@ const allocVar = (scope, name, global = false) => {
1662
1890
  let idx = global ? globalInd++ : scope.localInd++;
1663
1891
  target[name] = { idx, type: valtypeBinary };
1664
1892
 
1665
- let typeIdx = global ? globalInd++ : scope.localInd++;
1666
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
1893
+ if (type) {
1894
+ let typeIdx = global ? globalInd++ : scope.localInd++;
1895
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
1896
+ }
1667
1897
 
1668
1898
  return idx;
1669
1899
  };
1670
1900
 
1901
+ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1902
+ const target = global ? globals : scope.locals;
1903
+
1904
+ target[name].metadata ??= {};
1905
+ for (const x in metadata) {
1906
+ if (metadata[x] != null) target[name].metadata[x] = metadata[x];
1907
+ }
1908
+ };
1909
+
1910
+ const typeAnnoToPorfType = x => {
1911
+ if (TYPES[x]) return TYPES[x];
1912
+ if (TYPES['_' + x]) return TYPES['_' + x];
1913
+
1914
+ switch (x) {
1915
+ case 'i32':
1916
+ return TYPES.number;
1917
+ }
1918
+
1919
+ return null;
1920
+ };
1921
+
1922
+ const extractTypeAnnotation = decl => {
1923
+ let a = decl;
1924
+ while (a.typeAnnotation) a = a.typeAnnotation;
1925
+
1926
+ let type, elementType;
1927
+ if (a.typeName) {
1928
+ type = a.typeName.name;
1929
+ } else if (a.type.endsWith('Keyword')) {
1930
+ type = a.type.slice(2, -7).toLowerCase();
1931
+ } else if (a.type === 'TSArrayType') {
1932
+ type = 'array';
1933
+ elementType = extractTypeAnnotation(a.elementType).type;
1934
+ }
1935
+
1936
+ const typeName = type;
1937
+ type = typeAnnoToPorfType(type);
1938
+
1939
+ if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
1940
+
1941
+ // if (decl.name) console.log(decl.name, { type, elementType });
1942
+
1943
+ return { type, typeName, elementType };
1944
+ };
1945
+
1671
1946
  const generateVar = (scope, decl) => {
1672
1947
  let out = [];
1673
1948
 
@@ -1679,6 +1954,8 @@ const generateVar = (scope, decl) => {
1679
1954
  for (const x of decl.declarations) {
1680
1955
  const name = mapName(x.id.name);
1681
1956
 
1957
+ if (!name) return todo('destructuring is not supported yet');
1958
+
1682
1959
  if (x.init && isFuncType(x.init.type)) {
1683
1960
  // hack for let a = function () { ... }
1684
1961
  x.init.id = { name };
@@ -1694,7 +1971,12 @@ const generateVar = (scope, decl) => {
1694
1971
  continue; // always ignore
1695
1972
  }
1696
1973
 
1697
- let idx = allocVar(scope, name, global);
1974
+ let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
1975
+
1976
+ if (typedInput && x.id.typeAnnotation) {
1977
+ addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1978
+ }
1979
+
1698
1980
  if (x.init) {
1699
1981
  out = out.concat(generate(scope, x.init, global, name));
1700
1982
 
@@ -1812,6 +2094,8 @@ const generateAssign = (scope, decl) => {
1812
2094
  ];
1813
2095
  }
1814
2096
 
2097
+ if (!name) return todo('destructuring is not supported yet');
2098
+
1815
2099
  const [ local, isGlobal ] = lookupName(scope, name);
1816
2100
 
1817
2101
  if (local === undefined) {
@@ -1858,7 +2142,7 @@ const generateAssign = (scope, decl) => {
1858
2142
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1859
2143
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1860
2144
 
1861
- [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ],
2145
+ getLastType(scope),
1862
2146
  // hack: type is idx+1
1863
2147
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
1864
2148
  ];
@@ -1950,6 +2234,8 @@ const generateUnary = (scope, decl) => {
1950
2234
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
1951
2235
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
1952
2236
 
2237
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2238
+
1953
2239
  // object and internal types
1954
2240
  default: makeString(scope, 'object', false, '#typeof_result'),
1955
2241
  });
@@ -2025,7 +2311,7 @@ const generateConditional = (scope, decl) => {
2025
2311
  // note type
2026
2312
  out.push(
2027
2313
  ...getNodeType(scope, decl.consequent),
2028
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2314
+ setLastType(scope)
2029
2315
  );
2030
2316
 
2031
2317
  out.push([ Opcodes.else ]);
@@ -2034,7 +2320,7 @@ const generateConditional = (scope, decl) => {
2034
2320
  // note type
2035
2321
  out.push(
2036
2322
  ...getNodeType(scope, decl.alternate),
2037
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2323
+ setLastType(scope)
2038
2324
  );
2039
2325
 
2040
2326
  out.push([ Opcodes.end ]);
@@ -2055,8 +2341,10 @@ const generateFor = (scope, decl) => {
2055
2341
  out.push([ Opcodes.loop, Blocktype.void ]);
2056
2342
  depth.push('for');
2057
2343
 
2058
- out.push(...generate(scope, decl.test));
2059
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2344
+ if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
2345
+ else out.push(...number(1, Valtype.i32));
2346
+
2347
+ out.push([ Opcodes.if, Blocktype.void ]);
2060
2348
  depth.push('if');
2061
2349
 
2062
2350
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2064,8 +2352,7 @@ const generateFor = (scope, decl) => {
2064
2352
  out.push(...generate(scope, decl.body));
2065
2353
  out.push([ Opcodes.end ]);
2066
2354
 
2067
- out.push(...generate(scope, decl.update));
2068
- depth.pop();
2355
+ if (decl.update) out.push(...generate(scope, decl.update));
2069
2356
 
2070
2357
  out.push([ Opcodes.br, 1 ]);
2071
2358
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2122,7 +2409,13 @@ const generateForOf = (scope, decl) => {
2122
2409
  // setup local for left
2123
2410
  generate(scope, decl.left);
2124
2411
 
2125
- const leftName = decl.left.declarations[0].id.name;
2412
+ let leftName = decl.left.declarations?.[0]?.id?.name;
2413
+ if (!leftName && decl.left.name) {
2414
+ leftName = decl.left.name;
2415
+
2416
+ generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2417
+ }
2418
+
2126
2419
  const [ local, isGlobal ] = lookupName(scope, leftName);
2127
2420
 
2128
2421
  depth.push('block');
@@ -2131,13 +2424,14 @@ const generateForOf = (scope, decl) => {
2131
2424
  // // todo: we should only do this for strings but we don't know at compile-time :(
2132
2425
  // hack: this is naughty and will break things!
2133
2426
  let newOut = number(0, Valtype.f64), newPointer = -1;
2134
- if (pages.hasString) {
2427
+ if (pages.hasAnyString) {
2135
2428
  0, [ newOut, newPointer ] = makeArray(scope, {
2136
2429
  rawElements: new Array(1)
2137
2430
  }, isGlobal, leftName, true, 'i16');
2138
2431
  }
2139
2432
 
2140
2433
  // set type for local
2434
+ // todo: optimize away counter and use end pointer
2141
2435
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2142
2436
  [TYPES._array]: [
2143
2437
  ...setType(scope, leftName, TYPES.number),
@@ -2262,7 +2556,7 @@ const generateThrow = (scope, decl) => {
2262
2556
  // hack: throw new X("...") -> throw "..."
2263
2557
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2264
2558
  constructor = decl.argument.callee.name;
2265
- message = decl.argument.arguments[0].value;
2559
+ message = decl.argument.arguments[0]?.value ?? '';
2266
2560
  }
2267
2561
 
2268
2562
  if (tags.length === 0) tags.push({
@@ -2274,6 +2568,9 @@ const generateThrow = (scope, decl) => {
2274
2568
  let exceptId = exceptions.push({ constructor, message }) - 1;
2275
2569
  let tagIdx = tags[0].idx;
2276
2570
 
2571
+ scope.exceptions ??= [];
2572
+ scope.exceptions.push(exceptId);
2573
+
2277
2574
  // todo: write a description of how this works lol
2278
2575
 
2279
2576
  return [
@@ -2318,25 +2615,31 @@ const generateAssignPat = (scope, decl) => {
2318
2615
  };
2319
2616
 
2320
2617
  let pages = new Map();
2321
- const allocPage = (reason, type) => {
2618
+ const allocPage = (scope, reason, type) => {
2322
2619
  if (pages.has(reason)) return pages.get(reason).ind;
2323
2620
 
2324
2621
  if (reason.startsWith('array:')) pages.hasArray = true;
2325
2622
  if (reason.startsWith('string:')) pages.hasString = true;
2623
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
2624
+ if (reason.includes('string:')) pages.hasAnyString = true;
2326
2625
 
2327
2626
  const ind = pages.size;
2328
2627
  pages.set(reason, { ind, type });
2329
2628
 
2330
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2629
+ scope.pages ??= new Map();
2630
+ scope.pages.set(reason, { ind, type });
2631
+
2632
+ if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2331
2633
 
2332
2634
  return ind;
2333
2635
  };
2334
2636
 
2637
+ // todo: add scope.pages
2335
2638
  const freePage = reason => {
2336
2639
  const { ind } = pages.get(reason);
2337
2640
  pages.delete(reason);
2338
2641
 
2339
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2642
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2340
2643
 
2341
2644
  return ind;
2342
2645
  };
@@ -2356,25 +2659,34 @@ const StoreOps = {
2356
2659
  f64: Opcodes.f64_store,
2357
2660
 
2358
2661
  // expects i32 input!
2359
- i16: Opcodes.i32_store16
2662
+ i8: Opcodes.i32_store8,
2663
+ i16: Opcodes.i32_store16,
2360
2664
  };
2361
2665
 
2362
2666
  let data = [];
2363
2667
 
2364
- const compileBytes = (val, itemType, signed = true) => {
2668
+ const compileBytes = (val, itemType) => {
2365
2669
  // todo: this is a mess and needs confirming / ????
2366
2670
  switch (itemType) {
2367
2671
  case 'i8': return [ val % 256 ];
2368
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2369
-
2370
- case 'i32':
2371
- case 'i64':
2372
- return enforceFourBytes(signedLEB128(val));
2672
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
2673
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
2674
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
2675
+ // todo: i64
2373
2676
 
2374
2677
  case 'f64': return ieee754_binary64(val);
2375
2678
  }
2376
2679
  };
2377
2680
 
2681
+ const getAllocType = itemType => {
2682
+ switch (itemType) {
2683
+ case 'i8': return 'bytestring';
2684
+ case 'i16': return 'string';
2685
+
2686
+ default: return 'array';
2687
+ }
2688
+ };
2689
+
2378
2690
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2379
2691
  const out = [];
2380
2692
 
@@ -2384,7 +2696,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2384
2696
 
2385
2697
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2386
2698
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2387
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
2699
+ arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2388
2700
  }
2389
2701
 
2390
2702
  const pointer = arrays.get(name);
@@ -2404,10 +2716,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2404
2716
  bytes.push(...compileBytes(elements[i], itemType));
2405
2717
  }
2406
2718
 
2407
- data.push({
2719
+ const ind = data.push({
2408
2720
  offset: pointer,
2409
2721
  bytes
2410
- });
2722
+ }) - 1;
2723
+
2724
+ scope.data ??= [];
2725
+ scope.data.push(ind);
2411
2726
 
2412
2727
  // local value as pointer
2413
2728
  out.push(...number(pointer));
@@ -2430,7 +2745,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2430
2745
  out.push(
2431
2746
  ...number(0, Valtype.i32),
2432
2747
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2433
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2748
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2434
2749
  );
2435
2750
  }
2436
2751
 
@@ -2440,15 +2755,31 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2440
2755
  return [ out, pointer ];
2441
2756
  };
2442
2757
 
2443
- const makeString = (scope, str, global = false, name = '$undeclared') => {
2758
+ const byteStringable = str => {
2759
+ if (!Prefs.bytestring) return false;
2760
+
2761
+ for (let i = 0; i < str.length; i++) {
2762
+ if (str.charCodeAt(i) > 0xFF) return false;
2763
+ }
2764
+
2765
+ return true;
2766
+ };
2767
+
2768
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2444
2769
  const rawElements = new Array(str.length);
2770
+ let byteStringable = Prefs.bytestring;
2445
2771
  for (let i = 0; i < str.length; i++) {
2446
- rawElements[i] = str.charCodeAt(i);
2772
+ const c = str.charCodeAt(i);
2773
+ rawElements[i] = c;
2774
+
2775
+ if (byteStringable && c > 0xFF) byteStringable = false;
2447
2776
  }
2448
2777
 
2778
+ if (byteStringable && forceBytestring === false) byteStringable = false;
2779
+
2449
2780
  return makeArray(scope, {
2450
2781
  rawElements
2451
- }, global, name, false, 'i16')[0];
2782
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2452
2783
  };
2453
2784
 
2454
2785
  let arrays = new Map();
@@ -2476,10 +2807,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2476
2807
  ];
2477
2808
  }
2478
2809
 
2810
+ const object = generate(scope, decl.object);
2811
+ const property = generate(scope, decl.property);
2812
+
2479
2813
  // // todo: we should only do this for strings but we don't know at compile-time :(
2480
2814
  // hack: this is naughty and will break things!
2481
- let newOut = number(0, Valtype.f64), newPointer = -1;
2482
- if (pages.hasString) {
2815
+ let newOut = number(0, valtypeBinary), newPointer = -1;
2816
+ if (pages.hasAnyString) {
2483
2817
  0, [ newOut, newPointer ] = makeArray(scope, {
2484
2818
  rawElements: new Array(1)
2485
2819
  }, _global, _name, true, 'i16');
@@ -2488,7 +2822,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2488
2822
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2489
2823
  [TYPES._array]: [
2490
2824
  // get index as valtype
2491
- ...generate(scope, decl.property),
2825
+ ...property,
2492
2826
 
2493
2827
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2494
2828
  Opcodes.i32_to_u,
@@ -2496,7 +2830,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2496
2830
  [ Opcodes.i32_mul ],
2497
2831
 
2498
2832
  ...(aotPointer ? [] : [
2499
- ...generate(scope, decl.object),
2833
+ ...object,
2500
2834
  Opcodes.i32_to_u,
2501
2835
  [ Opcodes.i32_add ]
2502
2836
  ]),
@@ -2505,7 +2839,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2505
2839
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2506
2840
 
2507
2841
  ...number(TYPES.number, Valtype.i32),
2508
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2842
+ setLastType(scope)
2509
2843
  ],
2510
2844
 
2511
2845
  [TYPES.string]: [
@@ -2515,14 +2849,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2515
2849
 
2516
2850
  ...number(0, Valtype.i32), // base 0 for store later
2517
2851
 
2518
- ...generate(scope, decl.property),
2519
-
2852
+ ...property,
2520
2853
  Opcodes.i32_to_u,
2854
+
2521
2855
  ...number(ValtypeSize.i16, Valtype.i32),
2522
2856
  [ Opcodes.i32_mul ],
2523
2857
 
2524
2858
  ...(aotPointer ? [] : [
2525
- ...generate(scope, decl.object),
2859
+ ...object,
2526
2860
  Opcodes.i32_to_u,
2527
2861
  [ Opcodes.i32_add ]
2528
2862
  ]),
@@ -2537,10 +2871,38 @@ export const generateMember = (scope, decl, _global, _name) => {
2537
2871
  ...number(newPointer),
2538
2872
 
2539
2873
  ...number(TYPES.string, Valtype.i32),
2540
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2874
+ setLastType(scope)
2875
+ ],
2876
+ [TYPES._bytestring]: [
2877
+ // setup new/out array
2878
+ ...newOut,
2879
+ [ Opcodes.drop ],
2880
+
2881
+ ...number(0, Valtype.i32), // base 0 for store later
2882
+
2883
+ ...property,
2884
+ Opcodes.i32_to_u,
2885
+
2886
+ ...(aotPointer ? [] : [
2887
+ ...object,
2888
+ Opcodes.i32_to_u,
2889
+ [ Opcodes.i32_add ]
2890
+ ]),
2891
+
2892
+ // load current string ind {arg}
2893
+ [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2894
+
2895
+ // store to new string ind 0
2896
+ [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2897
+
2898
+ // return new string (page)
2899
+ ...number(newPointer),
2900
+
2901
+ ...number(TYPES._bytestring, Valtype.i32),
2902
+ setLastType(scope)
2541
2903
  ],
2542
2904
 
2543
- default: [ [ Opcodes.unreachable ] ]
2905
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
2544
2906
  });
2545
2907
  };
2546
2908
 
@@ -2557,13 +2919,16 @@ const objectHack = node => {
2557
2919
  // if object is not identifier or another member exp, give up
2558
2920
  if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
2559
2921
 
2560
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
2922
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2561
2923
 
2562
2924
  // if .length, give up (hack within a hack!)
2563
2925
  if (node.property.name === 'length') return node;
2564
2926
 
2927
+ // no object name, give up
2928
+ if (!objectName) return node;
2929
+
2565
2930
  const name = '__' + objectName + '_' + node.property.name;
2566
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2931
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2567
2932
 
2568
2933
  return {
2569
2934
  type: 'Identifier',
@@ -2586,7 +2951,7 @@ const generateFunc = (scope, decl) => {
2586
2951
  if (decl.generator) return todo('generator functions are not supported');
2587
2952
 
2588
2953
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2589
- const params = decl.params?.map(x => x.name) ?? [];
2954
+ const params = decl.params ?? [];
2590
2955
 
2591
2956
  // const innerScope = { ...scope };
2592
2957
  // TODO: share scope/locals between !!!
@@ -2600,7 +2965,11 @@ const generateFunc = (scope, decl) => {
2600
2965
  };
2601
2966
 
2602
2967
  for (let i = 0; i < params.length; i++) {
2603
- allocVar(innerScope, params[i], false);
2968
+ allocVar(innerScope, params[i].name, false);
2969
+
2970
+ if (typedInput && params[i].typeAnnotation) {
2971
+ addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
2972
+ }
2604
2973
  }
2605
2974
 
2606
2975
  let body = objectHack(decl.body);
@@ -2616,10 +2985,8 @@ const generateFunc = (scope, decl) => {
2616
2985
  const func = {
2617
2986
  name,
2618
2987
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2619
- returns: innerScope.returns,
2620
- locals: innerScope.locals,
2621
- throws: innerScope.throws,
2622
- index: currentFuncIndex++
2988
+ index: currentFuncIndex++,
2989
+ ...innerScope
2623
2990
  };
2624
2991
  funcIndex[name] = func.index;
2625
2992
 
@@ -2639,117 +3006,6 @@ const generateFunc = (scope, decl) => {
2639
3006
  );
2640
3007
  }
2641
3008
 
2642
- // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
2643
- let offset = 0, vecParams = 0;
2644
- for (let i = 0; i < params.length; i++) {
2645
- const name = params[i];
2646
- const local = func.locals[name];
2647
- if (local.type === Valtype.v128) {
2648
- vecParams++;
2649
-
2650
- /* wasm.unshift( // add v128 load for param
2651
- [ Opcodes.i32_const, 0 ],
2652
- [ ...Opcodes.v128_load, 0, i * 16 ],
2653
- [ Opcodes.local_set, local.idx ]
2654
- ); */
2655
-
2656
- // using params and replace_lane is noticably faster than just loading from memory (above) somehow
2657
-
2658
- // extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
2659
- const { vecType } = local;
2660
- let [ type, lanes ] = vecType.split('x');
2661
- if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
2662
-
2663
- lanes = parseInt(lanes);
2664
- type = Valtype[type];
2665
-
2666
- const name = params[i]; // get original param name
2667
-
2668
- func.params.splice(offset, 1, ...new Array(lanes).fill(type)); // add new params of {type}, {lanes} times
2669
-
2670
- // update index of original local
2671
- // delete func.locals[name];
2672
-
2673
- // add new locals for params
2674
- for (let j = 0; j < lanes; j++) {
2675
- func.locals[name + j] = { idx: offset + j, type, vecParamAutogen: true };
2676
- }
2677
-
2678
- // prepend wasm to generate expected v128 locals
2679
- wasm.splice(i * 2 + offset * 2, 0,
2680
- ...i32x4(0, 0, 0, 0),
2681
- ...new Array(lanes).fill(0).flatMap((_, j) => [
2682
- [ Opcodes.local_get, offset + j ],
2683
- [ ...Opcodes[vecType + '_replace_lane'], j ]
2684
- ]),
2685
- [ Opcodes.local_set, i ]
2686
- );
2687
-
2688
- offset += lanes;
2689
-
2690
- // note: wrapping is disabled for now due to perf/dx concerns (so this will never run)
2691
- /* if (!func.name.startsWith('#')) func.name = '##' + func.name;
2692
-
2693
- // add vec type index to hash name prefix for wrapper to know how to wrap
2694
- const vecTypeIdx = [ 'i8x16', 'i16x8', 'i32x4', 'i64x2', 'f32x4', 'f64x2' ].indexOf(local.vecType);
2695
- const secondHash = func.name.slice(1).indexOf('#');
2696
- func.name = '#' + func.name.slice(1, secondHash) + vecTypeIdx + func.name.slice(secondHash); */
2697
- }
2698
- }
2699
-
2700
- if (offset !== 0) {
2701
- // bump local indexes for all other locals after
2702
- for (const x in func.locals) {
2703
- const local = func.locals[x];
2704
- if (!local.vecParamAutogen) local.idx += offset;
2705
- }
2706
-
2707
- // bump local indexes in wasm local.get/set
2708
- for (let j = 0; j < wasm.length; j++) {
2709
- const inst = wasm[j];
2710
- if (j < offset * 2 + vecParams * 2) {
2711
- if (inst[0] === Opcodes.local_set) inst[1] += offset;
2712
- continue;
2713
- }
2714
-
2715
- if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set) inst[1] += offset;
2716
- }
2717
- }
2718
-
2719
- // change v128 return into many <type> instead as unsupported return valtype
2720
- const lastReturnLocal = wasm.length > 2 && wasm[wasm.length - 1][0] === Opcodes.return && Object.values(func.locals).find(x => x.idx === wasm[wasm.length - 2][1]);
2721
- if (lastReturnLocal && lastReturnLocal.type === Valtype.v128) {
2722
- const name = Object.keys(func.locals)[Object.values(func.locals).indexOf(lastReturnLocal)];
2723
- // extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
2724
- const { vecType } = lastReturnLocal;
2725
- let [ type, lanes ] = vecType.split('x');
2726
- if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
2727
-
2728
- lanes = parseInt(lanes);
2729
- type = Valtype[type];
2730
-
2731
- const vecIdx = lastReturnLocal.idx;
2732
-
2733
- const lastIdx = Math.max(0, ...Object.values(func.locals).map(x => x.idx));
2734
- const tmpIdx = [];
2735
- for (let i = 0; i < lanes; i++) {
2736
- const idx = lastIdx + i + 1;
2737
- tmpIdx.push(idx);
2738
- func.locals[name + i] = { idx, type, vecReturnAutogen: true };
2739
- }
2740
-
2741
- wasm.splice(wasm.length - 1, 1,
2742
- ...new Array(lanes).fill(0).flatMap((_, i) => [
2743
- i === 0 ? null : [ Opcodes.local_get, vecIdx ],
2744
- [ ...Opcodes[vecType + '_extract_lane'], i ],
2745
- [ Opcodes.local_set, tmpIdx[i] ],
2746
- ].filter(x => x !== null)),
2747
- ...new Array(lanes).fill(0).map((_, i) => [ Opcodes.local_get, tmpIdx[i]])
2748
- );
2749
-
2750
- func.returns = new Array(lanes).fill(type);
2751
- }
2752
-
2753
3009
  func.wasm = wasm;
2754
3010
 
2755
3011
  funcs.push(func);
@@ -2768,6 +3024,16 @@ const generateCode = (scope, decl) => {
2768
3024
  };
2769
3025
 
2770
3026
  const internalConstrs = {
3027
+ Boolean: {
3028
+ generate: (scope, decl) => {
3029
+ if (decl.arguments.length === 0) return number(0);
3030
+
3031
+ // should generate/run all args
3032
+ return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3033
+ },
3034
+ type: TYPES.boolean
3035
+ },
3036
+
2771
3037
  Array: {
2772
3038
  generate: (scope, decl, global, name) => {
2773
3039
  // new Array(i0, i1, ...)
@@ -2889,7 +3155,7 @@ export default program => {
2889
3155
  body: program.body
2890
3156
  };
2891
3157
 
2892
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3158
+ if (Prefs.astLog) console.log(program.body.body);
2893
3159
 
2894
3160
  generateFunc(scope, program);
2895
3161