porffor 0.2.0-c7b7423 → 0.2.0-cb647c8

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";
@@ -55,7 +55,7 @@ const todo = msg => {
55
55
  };
56
56
 
57
57
  const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
58
- const generate = (scope, decl, global = false, name = undefined) => {
58
+ const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
59
59
  switch (decl.type) {
60
60
  case 'BinaryExpression':
61
61
  return generateBinaryExp(scope, decl, global, name);
@@ -68,7 +68,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
68
68
 
69
69
  case 'ArrowFunctionExpression':
70
70
  case 'FunctionDeclaration':
71
- generateFunc(scope, decl);
71
+ const func = generateFunc(scope, decl);
72
+
73
+ if (decl.type.endsWith('Expression')) {
74
+ return number(func.index);
75
+ }
76
+
72
77
  return [];
73
78
 
74
79
  case 'BlockStatement':
@@ -81,7 +86,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
81
86
  return generateExp(scope, decl);
82
87
 
83
88
  case 'CallExpression':
84
- return generateCall(scope, decl, global, name);
89
+ return generateCall(scope, decl, global, name, valueUnused);
85
90
 
86
91
  case 'NewExpression':
87
92
  return generateNew(scope, decl, global, name);
@@ -155,7 +160,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
155
160
 
156
161
  case 'TaggedTemplateExpression': {
157
162
  const funcs = {
158
- asm: str => {
163
+ __Porffor_asm: str => {
159
164
  let out = [];
160
165
 
161
166
  for (const line of str.split('\n')) {
@@ -191,19 +196,19 @@ const generate = (scope, decl, global = false, name = undefined) => {
191
196
  return out;
192
197
  },
193
198
 
194
- __internal_print_type: str => {
195
- const type = getType(scope, str) - TYPES.number;
199
+ __Porffor_bs: str => [
200
+ ...makeString(scope, str, undefined, undefined, true),
196
201
 
197
- return [
198
- ...number(type),
199
- [ Opcodes.call, importedFuncs.print ],
202
+ ...number(TYPES._bytestring, Valtype.i32),
203
+ setLastType(scope)
204
+ ],
205
+ __Porffor_s: str => [
206
+ ...makeString(scope, str, undefined, undefined, false),
200
207
 
201
- // newline
202
- ...number(10),
203
- [ Opcodes.call, importedFuncs.printChar ]
204
- ];
205
- }
206
- }
208
+ ...number(TYPES.string, Valtype.i32),
209
+ setLastType(scope)
210
+ ],
211
+ };
207
212
 
208
213
  const name = decl.tag.name;
209
214
  // hack for inline asm
@@ -214,6 +219,11 @@ const generate = (scope, decl, global = false, name = undefined) => {
214
219
  }
215
220
 
216
221
  default:
222
+ if (decl.type.startsWith('TS')) {
223
+ // ignore typescript nodes
224
+ return [];
225
+ }
226
+
217
227
  return todo(`no generation for ${decl.type}!`);
218
228
  }
219
229
  };
@@ -264,25 +274,25 @@ const generateIdent = (scope, decl) => {
264
274
  const name = mapName(rawName);
265
275
  let local = scope.locals[rawName];
266
276
 
267
- if (builtinVars[name]) {
277
+ if (Object.hasOwn(builtinVars, name)) {
268
278
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
269
279
  return builtinVars[name];
270
280
  }
271
281
 
272
- if (builtinFuncs[name] || internalConstrs[name]) {
282
+ if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
273
283
  // todo: return an actual something
274
284
  return number(1);
275
285
  }
276
286
 
277
- if (local === undefined) {
287
+ if (local?.idx === undefined) {
278
288
  // no local var with name
279
- if (importedFuncs.hasOwnProperty(name)) return number(importedFuncs[name]);
280
- if (funcIndex[name] !== undefined) return number(funcIndex[name]);
289
+ if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
290
+ if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
281
291
 
282
- if (globals[name] !== undefined) return [ [ Opcodes.global_get, globals[name].idx ] ];
292
+ if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
283
293
  }
284
294
 
285
- if (local === undefined && rawName.startsWith('__')) {
295
+ if (local?.idx === undefined && rawName.startsWith('__')) {
286
296
  // return undefined if unknown key in already known var
287
297
  let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
288
298
  if (parent.includes('_')) parent = '__' + parent;
@@ -291,7 +301,7 @@ const generateIdent = (scope, decl) => {
291
301
  if (!parentLookup[1]) return number(UNDEFINED);
292
302
  }
293
303
 
294
- if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
304
+ if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
295
305
 
296
306
  return [ [ Opcodes.local_get, local.idx ] ];
297
307
  };
@@ -360,12 +370,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
360
370
  ...right,
361
371
  // note type
362
372
  ...rightType,
363
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
373
+ setLastType(scope),
364
374
  [ Opcodes.else ],
365
375
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
366
376
  // note type
367
377
  ...leftType,
368
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
378
+ setLastType(scope),
369
379
  [ Opcodes.end ],
370
380
  Opcodes.i32_from
371
381
  ];
@@ -379,12 +389,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
379
389
  ...right,
380
390
  // note type
381
391
  ...rightType,
382
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
392
+ setLastType(scope),
383
393
  [ Opcodes.else ],
384
394
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
385
395
  // note type
386
396
  ...leftType,
387
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
397
+ setLastType(scope),
388
398
  [ Opcodes.end ]
389
399
  ];
390
400
  };
@@ -675,6 +685,15 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
675
685
  [ Opcodes.i32_eqz ], */
676
686
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
677
687
  ],
688
+ [TYPES._bytestring]: [ // duplicate of string
689
+ [ Opcodes.local_get, tmp ],
690
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
691
+
692
+ // get length
693
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
694
+
695
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
696
+ ],
678
697
  default: def
679
698
  }, intOut ? Valtype.i32 : valtypeBinary)
680
699
  ];
@@ -702,6 +721,17 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
702
721
  [ Opcodes.i32_eqz ],
703
722
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
704
723
  ],
724
+ [TYPES._bytestring]: [ // duplicate of string
725
+ [ Opcodes.local_get, tmp ],
726
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
727
+
728
+ // get length
729
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
730
+
731
+ // if length == 0
732
+ [ Opcodes.i32_eqz ],
733
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
734
+ ],
705
735
  default: [
706
736
  // if value == 0
707
737
  [ Opcodes.local_get, tmp ],
@@ -852,7 +882,16 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
852
882
 
853
883
  let tmpLeft, tmpRight;
854
884
  // if equal op, check if strings for compareStrings
855
- if (op === '===' || op === '==' || op === '!==' || op === '!=') {
885
+ if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
886
+ const knownLeft = knownType(scope, leftType);
887
+ const knownRight = knownType(scope, rightType);
888
+
889
+ // todo: intelligent partial skip later
890
+ // if neither known are string, stop this madness
891
+ if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
892
+ return;
893
+ }
894
+
856
895
  tmpLeft = localTmp(scope, '__tmpop_left');
857
896
  tmpRight = localTmp(scope, '__tmpop_right');
858
897
 
@@ -886,7 +925,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
886
925
  [ Opcodes.i32_or ],
887
926
  [ Opcodes.if, Blocktype.void ],
888
927
  ...number(0, Valtype.i32),
889
- [ Opcodes.br, 1 ],
928
+ [ Opcodes.br, 2 ],
890
929
  [ Opcodes.end ],
891
930
 
892
931
  ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
@@ -902,7 +941,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
902
941
  // endOut.push(stringOnly([ Opcodes.end ]));
903
942
  endOut.unshift(stringOnly([ Opcodes.end ]));
904
943
  // }
905
- }
944
+ })();
906
945
 
907
946
  return finalise([
908
947
  ...left,
@@ -933,6 +972,18 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
933
972
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
934
973
  }
935
974
 
975
+ if (typeof wasm === 'function') {
976
+ const scope = {
977
+ name,
978
+ params,
979
+ locals,
980
+ returns,
981
+ localInd: allLocals.length,
982
+ };
983
+
984
+ wasm = wasm(scope, { TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString });
985
+ }
986
+
936
987
  let baseGlobalIdx, i = 0;
937
988
  for (const type of globalTypes) {
938
989
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -1013,7 +1064,8 @@ const TYPES = {
1013
1064
 
1014
1065
  // these are not "typeof" types but tracked internally
1015
1066
  _array: 0x10,
1016
- _regexp: 0x11
1067
+ _regexp: 0x11,
1068
+ _bytestring: 0x12
1017
1069
  };
1018
1070
 
1019
1071
  const TYPE_NAMES = {
@@ -1027,7 +1079,8 @@ const TYPE_NAMES = {
1027
1079
  [TYPES.bigint]: 'BigInt',
1028
1080
 
1029
1081
  [TYPES._array]: 'Array',
1030
- [TYPES._regexp]: 'RegExp'
1082
+ [TYPES._regexp]: 'RegExp',
1083
+ [TYPES._bytestring]: 'ByteString'
1031
1084
  };
1032
1085
 
1033
1086
  const getType = (scope, _name) => {
@@ -1051,11 +1104,13 @@ const setType = (scope, _name, type) => {
1051
1104
 
1052
1105
  const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
1053
1106
 
1107
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return [];
1054
1108
  if (scope.locals[name]) return [
1055
1109
  ...out,
1056
1110
  [ Opcodes.local_set, scope.locals[name + '#type'].idx ]
1057
1111
  ];
1058
1112
 
1113
+ if (typedInput && globals[name]?.metadata?.type != null) return [];
1059
1114
  if (globals[name]) return [
1060
1115
  ...out,
1061
1116
  [ Opcodes.global_set, globals[name + '#type'].idx ]
@@ -1064,11 +1119,22 @@ const setType = (scope, _name, type) => {
1064
1119
  // throw new Error('could not find var');
1065
1120
  };
1066
1121
 
1122
+ const getLastType = scope => {
1123
+ scope.gotLastType = true;
1124
+ return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1125
+ };
1126
+
1127
+ const setLastType = scope => {
1128
+ return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1129
+ };
1130
+
1067
1131
  const getNodeType = (scope, node) => {
1068
1132
  const inner = () => {
1069
1133
  if (node.type === 'Literal') {
1070
1134
  if (node.regex) return TYPES._regexp;
1071
1135
 
1136
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1137
+
1072
1138
  return TYPES[typeof node.value];
1073
1139
  }
1074
1140
 
@@ -1082,6 +1148,15 @@ const getNodeType = (scope, node) => {
1082
1148
 
1083
1149
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1084
1150
  const name = node.callee.name;
1151
+ if (!name) {
1152
+ // iife
1153
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1154
+
1155
+ // presume
1156
+ // todo: warn here?
1157
+ return TYPES.number;
1158
+ }
1159
+
1085
1160
  const func = funcs.find(x => x.name === name);
1086
1161
 
1087
1162
  if (func) {
@@ -1092,7 +1167,19 @@ const getNodeType = (scope, node) => {
1092
1167
  if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1093
1168
  if (internalConstrs[name]) return internalConstrs[name].type;
1094
1169
 
1095
- if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1170
+ // check if this is a prototype function
1171
+ // if so and there is only one impl (eg charCodeAt)
1172
+ // use that return type as that is the only possibility
1173
+ // (if non-matching type it would error out)
1174
+ if (name.startsWith('__')) {
1175
+ const spl = name.slice(2).split('_');
1176
+
1177
+ const func = spl[spl.length - 1];
1178
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
1179
+ if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1180
+ }
1181
+
1182
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1096
1183
 
1097
1184
  // presume
1098
1185
  // todo: warn here?
@@ -1164,7 +1251,7 @@ const getNodeType = (scope, node) => {
1164
1251
  if (node.operator === '!') return TYPES.boolean;
1165
1252
  if (node.operator === 'void') return TYPES.undefined;
1166
1253
  if (node.operator === 'delete') return TYPES.boolean;
1167
- if (node.operator === 'typeof') return TYPES.string;
1254
+ if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
1168
1255
 
1169
1256
  return TYPES.number;
1170
1257
  }
@@ -1173,11 +1260,17 @@ const getNodeType = (scope, node) => {
1173
1260
  // hack: if something.length, number type
1174
1261
  if (node.property.name === 'length') return TYPES.number;
1175
1262
 
1176
- // we cannot guess
1263
+ // ts hack
1264
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1265
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1266
+
1267
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1268
+
1269
+ // presume
1177
1270
  return TYPES.number;
1178
1271
  }
1179
1272
 
1180
- if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1273
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1181
1274
 
1182
1275
  // presume
1183
1276
  // todo: warn here?
@@ -1193,8 +1286,8 @@ const getNodeType = (scope, node) => {
1193
1286
  const generateLiteral = (scope, decl, global, name) => {
1194
1287
  if (decl.value === null) return number(NULL);
1195
1288
 
1289
+ // hack: just return 1 for regex literals
1196
1290
  if (decl.regex) {
1197
- scope.regex[name] = decl.regex;
1198
1291
  return number(1);
1199
1292
  }
1200
1293
 
@@ -1207,16 +1300,7 @@ const generateLiteral = (scope, decl, global, name) => {
1207
1300
  return number(decl.value ? 1 : 0);
1208
1301
 
1209
1302
  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];
1303
+ return makeString(scope, decl.value, global, name);
1220
1304
 
1221
1305
  default:
1222
1306
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -1237,9 +1321,9 @@ const countLeftover = wasm => {
1237
1321
 
1238
1322
  if (depth === 0)
1239
1323
  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)) {}
1324
+ 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
1325
  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;
1326
+ else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1243
1327
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1244
1328
  else if (inst[0] === Opcodes.return) count = 0;
1245
1329
  else if (inst[0] === Opcodes.call) {
@@ -1265,7 +1349,7 @@ const disposeLeftover = wasm => {
1265
1349
  const generateExp = (scope, decl) => {
1266
1350
  const expression = decl.expression;
1267
1351
 
1268
- const out = generate(scope, expression);
1352
+ const out = generate(scope, expression, undefined, undefined, true);
1269
1353
  disposeLeftover(out);
1270
1354
 
1271
1355
  return out;
@@ -1323,7 +1407,7 @@ const RTArrayUtil = {
1323
1407
  ]
1324
1408
  };
1325
1409
 
1326
- const generateCall = (scope, decl, _global, _name) => {
1410
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1327
1411
  /* const callee = decl.callee;
1328
1412
  const args = decl.arguments;
1329
1413
 
@@ -1356,13 +1440,13 @@ const generateCall = (scope, decl, _global, _name) => {
1356
1440
  const finalStatement = parsed.body[parsed.body.length - 1];
1357
1441
  out.push(
1358
1442
  ...getNodeType(scope, finalStatement),
1359
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1443
+ setLastType(scope)
1360
1444
  );
1361
1445
  } else if (countLeftover(out) === 0) {
1362
1446
  out.push(...number(UNDEFINED));
1363
1447
  out.push(
1364
1448
  ...number(TYPES.undefined, Valtype.i32),
1365
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1449
+ setLastType(scope)
1366
1450
  );
1367
1451
  }
1368
1452
 
@@ -1380,8 +1464,7 @@ const generateCall = (scope, decl, _global, _name) => {
1380
1464
  if (name && name.startsWith('__')) {
1381
1465
  const spl = name.slice(2).split('_');
1382
1466
 
1383
- const func = spl[spl.length - 1];
1384
- protoName = func;
1467
+ protoName = spl[spl.length - 1];
1385
1468
 
1386
1469
  target = { ...decl.callee };
1387
1470
  target.name = spl.slice(0, -1).join('_');
@@ -1390,8 +1473,8 @@ const generateCall = (scope, decl, _global, _name) => {
1390
1473
  // literal.func()
1391
1474
  if (!name && decl.callee.type === 'MemberExpression') {
1392
1475
  // megahack for /regex/.func()
1393
- if (decl.callee.object.regex) {
1394
- const funcName = decl.callee.property.name;
1476
+ const funcName = decl.callee.property.name;
1477
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1395
1478
  const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1396
1479
 
1397
1480
  funcIndex[func.name] = func.index;
@@ -1407,12 +1490,11 @@ const generateCall = (scope, decl, _global, _name) => {
1407
1490
  Opcodes.i32_from_u,
1408
1491
 
1409
1492
  ...number(TYPES.boolean, Valtype.i32),
1410
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1493
+ setLastType(scope)
1411
1494
  ];
1412
1495
  }
1413
1496
 
1414
- const func = decl.callee.property.name;
1415
- protoName = func;
1497
+ protoName = decl.callee.property.name;
1416
1498
 
1417
1499
  target = decl.callee.object;
1418
1500
  }
@@ -1434,8 +1516,7 @@ const generateCall = (scope, decl, _global, _name) => {
1434
1516
 
1435
1517
  if (protoName) {
1436
1518
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1437
- const f = prototypeFuncs[x][protoName];
1438
- if (f) acc[x] = f;
1519
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1439
1520
  return acc;
1440
1521
  }, {});
1441
1522
 
@@ -1444,10 +1525,18 @@ const generateCall = (scope, decl, _global, _name) => {
1444
1525
  // use local for cached i32 length as commonly used
1445
1526
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1446
1527
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1447
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1448
1528
 
1449
1529
  // TODO: long-term, prototypes should be their individual separate funcs
1450
1530
 
1531
+ const rawPointer = [
1532
+ ...generate(scope, target),
1533
+ Opcodes.i32_to_u
1534
+ ];
1535
+
1536
+ const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
1537
+ const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
1538
+
1539
+ let allOptUnused = true;
1451
1540
  let lengthI32CacheUsed = false;
1452
1541
  const protoBC = {};
1453
1542
  for (const x in protoCands) {
@@ -1457,7 +1546,7 @@ const generateCall = (scope, decl, _global, _name) => {
1457
1546
  ...RTArrayUtil.getLength(getPointer),
1458
1547
 
1459
1548
  ...number(TYPES.number, Valtype.i32),
1460
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
1549
+ setLastType(scope)
1461
1550
  ];
1462
1551
  continue;
1463
1552
  }
@@ -1467,6 +1556,7 @@ const generateCall = (scope, decl, _global, _name) => {
1467
1556
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1468
1557
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1469
1558
 
1559
+ let optUnused = false;
1470
1560
  const protoOut = protoFunc(getPointer, {
1471
1561
  getCachedI32: () => {
1472
1562
  lengthI32CacheUsed = true;
@@ -1481,23 +1571,30 @@ const generateCall = (scope, decl, _global, _name) => {
1481
1571
  return makeArray(scope, {
1482
1572
  rawElements: new Array(length)
1483
1573
  }, _global, _name, true, itemType);
1574
+ }, () => {
1575
+ optUnused = true;
1576
+ return unusedValue;
1484
1577
  });
1485
1578
 
1579
+ if (!optUnused) allOptUnused = false;
1580
+
1486
1581
  protoBC[x] = [
1487
- [ Opcodes.block, valtypeBinary ],
1582
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1488
1583
  ...protoOut,
1489
1584
 
1490
1585
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1491
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
1586
+ setLastType(scope),
1492
1587
  [ Opcodes.end ]
1493
1588
  ];
1494
1589
  }
1495
1590
 
1496
- return [
1497
- ...generate(scope, target),
1591
+ // todo: if some cands use optUnused and some don't, we will probably crash
1498
1592
 
1499
- Opcodes.i32_to_u,
1500
- [ Opcodes.local_set, pointerLocal ],
1593
+ return [
1594
+ ...(usePointerCache ? [
1595
+ ...rawPointer,
1596
+ [ Opcodes.local_set, pointerLocal ],
1597
+ ] : []),
1501
1598
 
1502
1599
  ...(!lengthI32CacheUsed ? [] : [
1503
1600
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1509,7 +1606,7 @@ const generateCall = (scope, decl, _global, _name) => {
1509
1606
 
1510
1607
  // TODO: error better
1511
1608
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1512
- }, valtypeBinary),
1609
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1513
1610
  ];
1514
1611
  }
1515
1612
  }
@@ -1556,7 +1653,9 @@ const generateCall = (scope, decl, _global, _name) => {
1556
1653
  const func = funcs.find(x => x.index === idx);
1557
1654
 
1558
1655
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1559
- const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
1656
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1657
+ const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
1658
+ const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1560
1659
 
1561
1660
  let args = decl.arguments;
1562
1661
  if (func && args.length < paramCount) {
@@ -1574,12 +1673,12 @@ const generateCall = (scope, decl, _global, _name) => {
1574
1673
  let out = [];
1575
1674
  for (const arg of args) {
1576
1675
  out = out.concat(generate(scope, arg));
1577
- if (userFunc) out = out.concat(getNodeType(scope, arg));
1676
+ if (typedParams) out = out.concat(getNodeType(scope, arg));
1578
1677
  }
1579
1678
 
1580
1679
  out.push([ Opcodes.call, idx ]);
1581
1680
 
1582
- if (!userFunc) {
1681
+ if (!typedReturn) {
1583
1682
  // let type;
1584
1683
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1585
1684
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1589,7 +1688,7 @@ const generateCall = (scope, decl, _global, _name) => {
1589
1688
  // ...number(type, Valtype.i32),
1590
1689
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1591
1690
  // );
1592
- } else out.push([ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]);
1691
+ } else out.push(setLastType(scope));
1593
1692
 
1594
1693
  return out;
1595
1694
  };
@@ -1614,9 +1713,118 @@ const unhackName = name => {
1614
1713
  return name;
1615
1714
  };
1616
1715
 
1716
+ const knownType = (scope, type) => {
1717
+ if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
1718
+ return type[0][1];
1719
+ }
1720
+
1721
+ if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
1722
+ const idx = type[0][1];
1723
+
1724
+ // type idx = var idx + 1
1725
+ const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
1726
+ if (v.metadata?.type != null) return v.metadata.type;
1727
+ }
1728
+
1729
+ return null;
1730
+ };
1731
+
1732
+ const brTable = (input, bc, returns) => {
1733
+ const out = [];
1734
+ const keys = Object.keys(bc);
1735
+ const count = keys.length;
1736
+
1737
+ if (count === 1) {
1738
+ // return [
1739
+ // ...input,
1740
+ // ...bc[keys[0]]
1741
+ // ];
1742
+ return bc[keys[0]];
1743
+ }
1744
+
1745
+ if (count === 2) {
1746
+ // just use if else
1747
+ const other = keys.find(x => x !== 'default');
1748
+ return [
1749
+ ...input,
1750
+ ...number(other, Valtype.i32),
1751
+ [ Opcodes.i32_eq ],
1752
+ [ Opcodes.if, returns ],
1753
+ ...bc[other],
1754
+ [ Opcodes.else ],
1755
+ ...bc.default,
1756
+ [ Opcodes.end ]
1757
+ ];
1758
+ }
1759
+
1760
+ for (let i = 0; i < count; i++) {
1761
+ if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
1762
+ else out.push([ Opcodes.block, Blocktype.void ]);
1763
+ }
1764
+
1765
+ const nums = keys.filter(x => +x);
1766
+ const offset = Math.min(...nums);
1767
+ const max = Math.max(...nums);
1768
+
1769
+ const table = [];
1770
+ let br = 1;
1771
+
1772
+ for (let i = offset; i <= max; i++) {
1773
+ // if branch for this num, go to that block
1774
+ if (bc[i]) {
1775
+ table.push(br);
1776
+ br++;
1777
+ continue;
1778
+ }
1779
+
1780
+ // else default
1781
+ table.push(0);
1782
+ }
1783
+
1784
+ out.push(
1785
+ [ Opcodes.block, Blocktype.void ],
1786
+ ...input,
1787
+ ...(offset > 0 ? [
1788
+ ...number(offset, Valtype.i32),
1789
+ [ Opcodes.i32_sub ]
1790
+ ] : []),
1791
+ [ Opcodes.br_table, ...encodeVector(table), 0 ]
1792
+ );
1793
+
1794
+ // if you can guess why we sort the wrong way and then reverse
1795
+ // (instead of just sorting the correct way)
1796
+ // dm me and if you are correct and the first person
1797
+ // I will somehow shout you out or something
1798
+ const orderedBc = keys.sort((a, b) => b - a).reverse();
1799
+
1800
+ br = count - 1;
1801
+ for (const x of orderedBc) {
1802
+ out.push(
1803
+ [ Opcodes.end ],
1804
+ ...bc[x],
1805
+ ...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
1806
+ );
1807
+ br--;
1808
+ }
1809
+
1810
+ return [
1811
+ ...out,
1812
+ [ Opcodes.end, 'br table end' ]
1813
+ ];
1814
+ };
1815
+
1617
1816
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1618
- const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1817
+ if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
1818
+
1819
+ const known = knownType(scope, type);
1820
+ if (known != null) {
1821
+ return bc[known] ?? bc.default;
1822
+ }
1619
1823
 
1824
+ if (process.argv.includes('-typeswitch-use-brtable'))
1825
+ return brTable(type, bc, returns);
1826
+
1827
+ const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1620
1828
  const out = [
1621
1829
  ...type,
1622
1830
  [ Opcodes.local_set, tmp ],
@@ -1668,6 +1876,51 @@ const allocVar = (scope, name, global = false) => {
1668
1876
  return idx;
1669
1877
  };
1670
1878
 
1879
+ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1880
+ const target = global ? globals : scope.locals;
1881
+
1882
+ target[name].metadata ??= {};
1883
+ for (const x in metadata) {
1884
+ if (metadata[x] != null) target[name].metadata[x] = metadata[x];
1885
+ }
1886
+ };
1887
+
1888
+ const typeAnnoToPorfType = x => {
1889
+ if (TYPES[x]) return TYPES[x];
1890
+ if (TYPES['_' + x]) return TYPES['_' + x];
1891
+
1892
+ switch (x) {
1893
+ case 'i32':
1894
+ return TYPES.number;
1895
+ }
1896
+
1897
+ return null;
1898
+ };
1899
+
1900
+ const extractTypeAnnotation = decl => {
1901
+ let a = decl;
1902
+ while (a.typeAnnotation) a = a.typeAnnotation;
1903
+
1904
+ let type, elementType;
1905
+ if (a.typeName) {
1906
+ type = a.typeName.name;
1907
+ } else if (a.type.endsWith('Keyword')) {
1908
+ type = a.type.slice(2, -7).toLowerCase();
1909
+ } else if (a.type === 'TSArrayType') {
1910
+ type = 'array';
1911
+ elementType = extractTypeAnnotation(a.elementType).type;
1912
+ }
1913
+
1914
+ const typeName = type;
1915
+ type = typeAnnoToPorfType(type);
1916
+
1917
+ if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
1918
+
1919
+ // if (decl.name) console.log(decl.name, { type, elementType });
1920
+
1921
+ return { type, typeName, elementType };
1922
+ };
1923
+
1671
1924
  const generateVar = (scope, decl) => {
1672
1925
  let out = [];
1673
1926
 
@@ -1679,6 +1932,8 @@ const generateVar = (scope, decl) => {
1679
1932
  for (const x of decl.declarations) {
1680
1933
  const name = mapName(x.id.name);
1681
1934
 
1935
+ if (!name) return todo('destructuring is not supported yet');
1936
+
1682
1937
  if (x.init && isFuncType(x.init.type)) {
1683
1938
  // hack for let a = function () { ... }
1684
1939
  x.init.id = { name };
@@ -1695,6 +1950,11 @@ const generateVar = (scope, decl) => {
1695
1950
  }
1696
1951
 
1697
1952
  let idx = allocVar(scope, name, global);
1953
+
1954
+ if (typedInput && x.id.typeAnnotation) {
1955
+ addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1956
+ }
1957
+
1698
1958
  if (x.init) {
1699
1959
  out = out.concat(generate(scope, x.init, global, name));
1700
1960
 
@@ -1812,6 +2072,8 @@ const generateAssign = (scope, decl) => {
1812
2072
  ];
1813
2073
  }
1814
2074
 
2075
+ if (!name) return todo('destructuring is not supported yet');
2076
+
1815
2077
  const [ local, isGlobal ] = lookupName(scope, name);
1816
2078
 
1817
2079
  if (local === undefined) {
@@ -1858,7 +2120,7 @@ const generateAssign = (scope, decl) => {
1858
2120
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1859
2121
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1860
2122
 
1861
- [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ],
2123
+ getLastType(scope),
1862
2124
  // hack: type is idx+1
1863
2125
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
1864
2126
  ];
@@ -1950,6 +2212,8 @@ const generateUnary = (scope, decl) => {
1950
2212
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
1951
2213
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
1952
2214
 
2215
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2216
+
1953
2217
  // object and internal types
1954
2218
  default: makeString(scope, 'object', false, '#typeof_result'),
1955
2219
  });
@@ -2025,7 +2289,7 @@ const generateConditional = (scope, decl) => {
2025
2289
  // note type
2026
2290
  out.push(
2027
2291
  ...getNodeType(scope, decl.consequent),
2028
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2292
+ setLastType(scope)
2029
2293
  );
2030
2294
 
2031
2295
  out.push([ Opcodes.else ]);
@@ -2034,7 +2298,7 @@ const generateConditional = (scope, decl) => {
2034
2298
  // note type
2035
2299
  out.push(
2036
2300
  ...getNodeType(scope, decl.alternate),
2037
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2301
+ setLastType(scope)
2038
2302
  );
2039
2303
 
2040
2304
  out.push([ Opcodes.end ]);
@@ -2055,8 +2319,10 @@ const generateFor = (scope, decl) => {
2055
2319
  out.push([ Opcodes.loop, Blocktype.void ]);
2056
2320
  depth.push('for');
2057
2321
 
2058
- out.push(...generate(scope, decl.test));
2059
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2322
+ if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
2323
+ else out.push(...number(1, Valtype.i32));
2324
+
2325
+ out.push([ Opcodes.if, Blocktype.void ]);
2060
2326
  depth.push('if');
2061
2327
 
2062
2328
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2064,8 +2330,7 @@ const generateFor = (scope, decl) => {
2064
2330
  out.push(...generate(scope, decl.body));
2065
2331
  out.push([ Opcodes.end ]);
2066
2332
 
2067
- out.push(...generate(scope, decl.update));
2068
- depth.pop();
2333
+ if (decl.update) out.push(...generate(scope, decl.update));
2069
2334
 
2070
2335
  out.push([ Opcodes.br, 1 ]);
2071
2336
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2122,7 +2387,13 @@ const generateForOf = (scope, decl) => {
2122
2387
  // setup local for left
2123
2388
  generate(scope, decl.left);
2124
2389
 
2125
- const leftName = decl.left.declarations[0].id.name;
2390
+ let leftName = decl.left.declarations?.[0]?.id?.name;
2391
+ if (!leftName && decl.left.name) {
2392
+ leftName = decl.left.name;
2393
+
2394
+ generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2395
+ }
2396
+
2126
2397
  const [ local, isGlobal ] = lookupName(scope, leftName);
2127
2398
 
2128
2399
  depth.push('block');
@@ -2131,13 +2402,14 @@ const generateForOf = (scope, decl) => {
2131
2402
  // // todo: we should only do this for strings but we don't know at compile-time :(
2132
2403
  // hack: this is naughty and will break things!
2133
2404
  let newOut = number(0, Valtype.f64), newPointer = -1;
2134
- if (pages.hasString) {
2405
+ if (pages.hasAnyString) {
2135
2406
  0, [ newOut, newPointer ] = makeArray(scope, {
2136
2407
  rawElements: new Array(1)
2137
2408
  }, isGlobal, leftName, true, 'i16');
2138
2409
  }
2139
2410
 
2140
2411
  // set type for local
2412
+ // todo: optimize away counter and use end pointer
2141
2413
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2142
2414
  [TYPES._array]: [
2143
2415
  ...setType(scope, leftName, TYPES.number),
@@ -2262,7 +2534,7 @@ const generateThrow = (scope, decl) => {
2262
2534
  // hack: throw new X("...") -> throw "..."
2263
2535
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2264
2536
  constructor = decl.argument.callee.name;
2265
- message = decl.argument.arguments[0].value;
2537
+ message = decl.argument.arguments[0]?.value ?? '';
2266
2538
  }
2267
2539
 
2268
2540
  if (tags.length === 0) tags.push({
@@ -2323,6 +2595,8 @@ const allocPage = (reason, type) => {
2323
2595
 
2324
2596
  if (reason.startsWith('array:')) pages.hasArray = true;
2325
2597
  if (reason.startsWith('string:')) pages.hasString = true;
2598
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
2599
+ if (reason.includes('string:')) pages.hasAnyString = true;
2326
2600
 
2327
2601
  const ind = pages.size;
2328
2602
  pages.set(reason, { ind, type });
@@ -2356,25 +2630,34 @@ const StoreOps = {
2356
2630
  f64: Opcodes.f64_store,
2357
2631
 
2358
2632
  // expects i32 input!
2359
- i16: Opcodes.i32_store16
2633
+ i8: Opcodes.i32_store8,
2634
+ i16: Opcodes.i32_store16,
2360
2635
  };
2361
2636
 
2362
2637
  let data = [];
2363
2638
 
2364
- const compileBytes = (val, itemType, signed = true) => {
2639
+ const compileBytes = (val, itemType) => {
2365
2640
  // todo: this is a mess and needs confirming / ????
2366
2641
  switch (itemType) {
2367
2642
  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));
2643
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
2644
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
2645
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
2646
+ // todo: i64
2373
2647
 
2374
2648
  case 'f64': return ieee754_binary64(val);
2375
2649
  }
2376
2650
  };
2377
2651
 
2652
+ const getAllocType = itemType => {
2653
+ switch (itemType) {
2654
+ case 'i8': return 'bytestring';
2655
+ case 'i16': return 'string';
2656
+
2657
+ default: return 'array';
2658
+ }
2659
+ };
2660
+
2378
2661
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2379
2662
  const out = [];
2380
2663
 
@@ -2384,7 +2667,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2384
2667
 
2385
2668
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2386
2669
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2387
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
2670
+ arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2388
2671
  }
2389
2672
 
2390
2673
  const pointer = arrays.get(name);
@@ -2430,7 +2713,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2430
2713
  out.push(
2431
2714
  ...number(0, Valtype.i32),
2432
2715
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2433
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2716
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2434
2717
  );
2435
2718
  }
2436
2719
 
@@ -2440,15 +2723,31 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2440
2723
  return [ out, pointer ];
2441
2724
  };
2442
2725
 
2443
- const makeString = (scope, str, global = false, name = '$undeclared') => {
2726
+ const byteStringable = str => {
2727
+ if (!process.argv.includes('-bytestring')) return false;
2728
+
2729
+ for (let i = 0; i < str.length; i++) {
2730
+ if (str.charCodeAt(i) > 0xFF) return false;
2731
+ }
2732
+
2733
+ return true;
2734
+ };
2735
+
2736
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2444
2737
  const rawElements = new Array(str.length);
2738
+ let byteStringable = process.argv.includes('-bytestring');
2445
2739
  for (let i = 0; i < str.length; i++) {
2446
- rawElements[i] = str.charCodeAt(i);
2740
+ const c = str.charCodeAt(i);
2741
+ rawElements[i] = c;
2742
+
2743
+ if (byteStringable && c > 0xFF) byteStringable = false;
2447
2744
  }
2448
2745
 
2746
+ if (byteStringable && forceBytestring === false) byteStringable = false;
2747
+
2449
2748
  return makeArray(scope, {
2450
2749
  rawElements
2451
- }, global, name, false, 'i16')[0];
2750
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2452
2751
  };
2453
2752
 
2454
2753
  let arrays = new Map();
@@ -2476,10 +2775,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2476
2775
  ];
2477
2776
  }
2478
2777
 
2778
+ const object = generate(scope, decl.object);
2779
+ const property = generate(scope, decl.property);
2780
+
2479
2781
  // // todo: we should only do this for strings but we don't know at compile-time :(
2480
2782
  // hack: this is naughty and will break things!
2481
- let newOut = number(0, Valtype.f64), newPointer = -1;
2482
- if (pages.hasString) {
2783
+ let newOut = number(0, valtypeBinary), newPointer = -1;
2784
+ if (pages.hasAnyString) {
2483
2785
  0, [ newOut, newPointer ] = makeArray(scope, {
2484
2786
  rawElements: new Array(1)
2485
2787
  }, _global, _name, true, 'i16');
@@ -2488,7 +2790,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2488
2790
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2489
2791
  [TYPES._array]: [
2490
2792
  // get index as valtype
2491
- ...generate(scope, decl.property),
2793
+ ...property,
2492
2794
 
2493
2795
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2494
2796
  Opcodes.i32_to_u,
@@ -2496,7 +2798,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2496
2798
  [ Opcodes.i32_mul ],
2497
2799
 
2498
2800
  ...(aotPointer ? [] : [
2499
- ...generate(scope, decl.object),
2801
+ ...object,
2500
2802
  Opcodes.i32_to_u,
2501
2803
  [ Opcodes.i32_add ]
2502
2804
  ]),
@@ -2505,7 +2807,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2505
2807
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2506
2808
 
2507
2809
  ...number(TYPES.number, Valtype.i32),
2508
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2810
+ setLastType(scope)
2509
2811
  ],
2510
2812
 
2511
2813
  [TYPES.string]: [
@@ -2515,14 +2817,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2515
2817
 
2516
2818
  ...number(0, Valtype.i32), // base 0 for store later
2517
2819
 
2518
- ...generate(scope, decl.property),
2519
-
2820
+ ...property,
2520
2821
  Opcodes.i32_to_u,
2822
+
2521
2823
  ...number(ValtypeSize.i16, Valtype.i32),
2522
2824
  [ Opcodes.i32_mul ],
2523
2825
 
2524
2826
  ...(aotPointer ? [] : [
2525
- ...generate(scope, decl.object),
2827
+ ...object,
2526
2828
  Opcodes.i32_to_u,
2527
2829
  [ Opcodes.i32_add ]
2528
2830
  ]),
@@ -2537,10 +2839,38 @@ export const generateMember = (scope, decl, _global, _name) => {
2537
2839
  ...number(newPointer),
2538
2840
 
2539
2841
  ...number(TYPES.string, Valtype.i32),
2540
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2842
+ setLastType(scope)
2843
+ ],
2844
+ [TYPES._bytestring]: [
2845
+ // setup new/out array
2846
+ ...newOut,
2847
+ [ Opcodes.drop ],
2848
+
2849
+ ...number(0, Valtype.i32), // base 0 for store later
2850
+
2851
+ ...property,
2852
+ Opcodes.i32_to_u,
2853
+
2854
+ ...(aotPointer ? [] : [
2855
+ ...object,
2856
+ Opcodes.i32_to_u,
2857
+ [ Opcodes.i32_add ]
2858
+ ]),
2859
+
2860
+ // load current string ind {arg}
2861
+ [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2862
+
2863
+ // store to new string ind 0
2864
+ [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2865
+
2866
+ // return new string (page)
2867
+ ...number(newPointer),
2868
+
2869
+ ...number(TYPES._bytestring, Valtype.i32),
2870
+ setLastType(scope)
2541
2871
  ],
2542
2872
 
2543
- default: [ [ Opcodes.unreachable ] ]
2873
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
2544
2874
  });
2545
2875
  };
2546
2876
 
@@ -2557,11 +2887,14 @@ const objectHack = node => {
2557
2887
  // if object is not identifier or another member exp, give up
2558
2888
  if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
2559
2889
 
2560
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
2890
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2561
2891
 
2562
2892
  // if .length, give up (hack within a hack!)
2563
2893
  if (node.property.name === 'length') return node;
2564
2894
 
2895
+ // no object name, give up
2896
+ if (!objectName) return node;
2897
+
2565
2898
  const name = '__' + objectName + '_' + node.property.name;
2566
2899
  if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2567
2900
 
@@ -2586,7 +2919,7 @@ const generateFunc = (scope, decl) => {
2586
2919
  if (decl.generator) return todo('generator functions are not supported');
2587
2920
 
2588
2921
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2589
- const params = decl.params?.map(x => x.name) ?? [];
2922
+ const params = decl.params ?? [];
2590
2923
 
2591
2924
  // const innerScope = { ...scope };
2592
2925
  // TODO: share scope/locals between !!!
@@ -2600,7 +2933,11 @@ const generateFunc = (scope, decl) => {
2600
2933
  };
2601
2934
 
2602
2935
  for (let i = 0; i < params.length; i++) {
2603
- allocVar(innerScope, params[i], false);
2936
+ allocVar(innerScope, params[i].name, false);
2937
+
2938
+ if (typedInput && params[i].typeAnnotation) {
2939
+ addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
2940
+ }
2604
2941
  }
2605
2942
 
2606
2943
  let body = objectHack(decl.body);
@@ -2616,10 +2953,8 @@ const generateFunc = (scope, decl) => {
2616
2953
  const func = {
2617
2954
  name,
2618
2955
  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++
2956
+ index: currentFuncIndex++,
2957
+ ...innerScope
2623
2958
  };
2624
2959
  funcIndex[name] = func.index;
2625
2960
 
@@ -2639,117 +2974,6 @@ const generateFunc = (scope, decl) => {
2639
2974
  );
2640
2975
  }
2641
2976
 
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
2977
  func.wasm = wasm;
2754
2978
 
2755
2979
  funcs.push(func);
@@ -2768,6 +2992,16 @@ const generateCode = (scope, decl) => {
2768
2992
  };
2769
2993
 
2770
2994
  const internalConstrs = {
2995
+ Boolean: {
2996
+ generate: (scope, decl) => {
2997
+ if (decl.arguments.length === 0) return number(0);
2998
+
2999
+ // should generate/run all args
3000
+ return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3001
+ },
3002
+ type: TYPES.boolean
3003
+ },
3004
+
2771
3005
  Array: {
2772
3006
  generate: (scope, decl, global, name) => {
2773
3007
  // new Array(i0, i1, ...)