porffor 0.2.0-09999e8 → 0.2.0-181627c

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')) {
@@ -163,8 +169,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
163
169
  if (asm[0] === '') continue; // blank
164
170
 
165
171
  if (asm[0] === 'local') {
166
- const [ name, idx, type ] = asm.slice(1);
167
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
172
+ const [ name, type ] = asm.slice(1);
173
+ scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
168
174
  continue;
169
175
  }
170
176
 
@@ -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
  }
@@ -183,7 +189,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
183
189
  if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
184
190
 
185
191
  if (!Array.isArray(inst)) inst = [ inst ];
186
- const immediates = asm.slice(1).map(x => parseInt(x));
192
+ const immediates = asm.slice(1).map(x => parseInt(x) || scope.locals[x]?.idx);
187
193
 
188
194
  out.push([ ...inst, ...immediates ]);
189
195
  }
@@ -191,31 +197,41 @@ 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:
217
- if (decl.type.startsWith('TS')) {
218
- // ignore typescript nodes
232
+ // ignore typescript nodes
233
+ if (decl.type.startsWith('TS') ||
234
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
219
235
  return [];
220
236
  }
221
237
 
@@ -269,25 +285,25 @@ const generateIdent = (scope, decl) => {
269
285
  const name = mapName(rawName);
270
286
  let local = scope.locals[rawName];
271
287
 
272
- if (builtinVars[name]) {
288
+ if (Object.hasOwn(builtinVars, name)) {
273
289
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
274
290
  return builtinVars[name];
275
291
  }
276
292
 
277
- if (builtinFuncs[name] || internalConstrs[name]) {
293
+ if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
278
294
  // todo: return an actual something
279
295
  return number(1);
280
296
  }
281
297
 
282
- if (local === undefined) {
298
+ if (local?.idx === undefined) {
283
299
  // no local var with name
284
- if (importedFuncs.hasOwnProperty(name)) return number(importedFuncs[name]);
285
- 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]);
286
302
 
287
- if (globals[name] !== undefined) return [ [ Opcodes.global_get, globals[name].idx ] ];
303
+ if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
288
304
  }
289
305
 
290
- if (local === undefined && rawName.startsWith('__')) {
306
+ if (local?.idx === undefined && rawName.startsWith('__')) {
291
307
  // return undefined if unknown key in already known var
292
308
  let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
293
309
  if (parent.includes('_')) parent = '__' + parent;
@@ -296,7 +312,7 @@ const generateIdent = (scope, decl) => {
296
312
  if (!parentLookup[1]) return number(UNDEFINED);
297
313
  }
298
314
 
299
- 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);
300
316
 
301
317
  return [ [ Opcodes.local_get, local.idx ] ];
302
318
  };
@@ -404,9 +420,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
404
420
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
405
421
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
406
422
 
407
- const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
408
- if (aotWFA) addVarMeta(name, { wellFormed: undefined });
409
-
410
423
  if (assign) {
411
424
  const pointer = arrays.get(name ?? '$undeclared');
412
425
 
@@ -680,6 +693,15 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
680
693
  [ Opcodes.i32_eqz ], */
681
694
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
682
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
+ ],
683
705
  default: def
684
706
  }, intOut ? Valtype.i32 : valtypeBinary)
685
707
  ];
@@ -707,6 +729,17 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
707
729
  [ Opcodes.i32_eqz ],
708
730
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
709
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
+ ],
710
743
  default: [
711
744
  // if value == 0
712
745
  [ Opcodes.local_get, tmp ],
@@ -760,11 +793,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
760
793
  return performLogicOp(scope, op, left, right, leftType, rightType);
761
794
  }
762
795
 
796
+ const knownLeft = knownType(scope, leftType);
797
+ const knownRight = knownType(scope, rightType);
798
+
763
799
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
764
800
  const strictOp = op === '===' || op === '!==';
765
801
 
766
802
  const startOut = [], endOut = [];
767
- const finalise = out => startOut.concat(out, endOut);
803
+ const finalize = out => startOut.concat(out, endOut);
768
804
 
769
805
  // if strict (in)equal check types match
770
806
  if (strictOp) {
@@ -809,31 +845,32 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
809
845
  // todo: if equality op and an operand is undefined, return false
810
846
  // todo: niche null hell with 0
811
847
 
812
- // if (leftType === TYPES.string || rightType === TYPES.string) {
813
- // if (op === '+') {
814
- // // string concat (a + b)
815
- // return finalise(concatStrings(scope, left, right, _global, _name, assign));
816
- // }
817
-
818
- // // not an equality op, NaN
819
- // if (!eqOp) return finalise(number(NaN));
820
-
821
- // // else leave bool ops
822
- // // todo: convert string to number if string and number/bool
823
- // // todo: string (>|>=|<|<=) string
824
-
825
- // // string comparison
826
- // if (op === '===' || op === '==') {
827
- // return finalise(compareStrings(scope, left, right));
828
- // }
829
-
830
- // if (op === '!==' || op === '!=') {
831
- // return finalise([
832
- // ...compareStrings(scope, left, right),
833
- // [ Opcodes.i32_eqz ]
834
- // ]);
835
- // }
836
- // }
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
+ }
837
874
 
838
875
  let ops = operatorOpcode[valtype][op];
839
876
 
@@ -843,7 +880,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
843
880
  includeBuiltin(scope, builtinName);
844
881
  const idx = funcIndex[builtinName];
845
882
 
846
- return finalise([
883
+ return finalize([
847
884
  ...left,
848
885
  ...right,
849
886
  [ Opcodes.call, idx ]
@@ -858,9 +895,6 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
858
895
  let tmpLeft, tmpRight;
859
896
  // if equal op, check if strings for compareStrings
860
897
  if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
861
- const knownLeft = knownType(scope, leftType);
862
- const knownRight = knownType(scope, rightType);
863
-
864
898
  // todo: intelligent partial skip later
865
899
  // if neither known are string, stop this madness
866
900
  if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
@@ -900,7 +934,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
900
934
  [ Opcodes.i32_or ],
901
935
  [ Opcodes.if, Blocktype.void ],
902
936
  ...number(0, Valtype.i32),
903
- [ Opcodes.br, 1 ],
937
+ [ Opcodes.br, 2 ],
904
938
  [ Opcodes.end ],
905
939
 
906
940
  ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
@@ -918,7 +952,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
918
952
  // }
919
953
  })();
920
954
 
921
- return finalise([
955
+ return finalize([
922
956
  ...left,
923
957
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
924
958
  ...right,
@@ -947,6 +981,18 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
947
981
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
948
982
  }
949
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
+
950
996
  let baseGlobalIdx, i = 0;
951
997
  for (const type of globalTypes) {
952
998
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -1027,7 +1073,8 @@ const TYPES = {
1027
1073
 
1028
1074
  // these are not "typeof" types but tracked internally
1029
1075
  _array: 0x10,
1030
- _regexp: 0x11
1076
+ _regexp: 0x11,
1077
+ _bytestring: 0x12
1031
1078
  };
1032
1079
 
1033
1080
  const TYPE_NAMES = {
@@ -1041,13 +1088,17 @@ const TYPE_NAMES = {
1041
1088
  [TYPES.bigint]: 'BigInt',
1042
1089
 
1043
1090
  [TYPES._array]: 'Array',
1044
- [TYPES._regexp]: 'RegExp'
1091
+ [TYPES._regexp]: 'RegExp',
1092
+ [TYPES._bytestring]: 'ByteString'
1045
1093
  };
1046
1094
 
1047
1095
  const getType = (scope, _name) => {
1048
1096
  const name = mapName(_name);
1049
1097
 
1098
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1050
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);
1051
1102
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1052
1103
 
1053
1104
  let type = TYPES.undefined;
@@ -1094,6 +1145,8 @@ const getNodeType = (scope, node) => {
1094
1145
  if (node.type === 'Literal') {
1095
1146
  if (node.regex) return TYPES._regexp;
1096
1147
 
1148
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1149
+
1097
1150
  return TYPES[typeof node.value];
1098
1151
  }
1099
1152
 
@@ -1107,6 +1160,15 @@ const getNodeType = (scope, node) => {
1107
1160
 
1108
1161
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1109
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
+
1110
1172
  const func = funcs.find(x => x.name === name);
1111
1173
 
1112
1174
  if (func) {
@@ -1125,7 +1187,7 @@ const getNodeType = (scope, node) => {
1125
1187
  const spl = name.slice(2).split('_');
1126
1188
 
1127
1189
  const func = spl[spl.length - 1];
1128
- const protoFuncs = Object.values(prototypeFuncs).filter(x => x[func] != null);
1190
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
1129
1191
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1130
1192
  }
1131
1193
 
@@ -1177,6 +1239,14 @@ const getNodeType = (scope, node) => {
1177
1239
 
1178
1240
  if (node.type === 'BinaryExpression') {
1179
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
+
1180
1250
  return TYPES.number;
1181
1251
 
1182
1252
  // todo: string concat types
@@ -1201,7 +1271,7 @@ const getNodeType = (scope, node) => {
1201
1271
  if (node.operator === '!') return TYPES.boolean;
1202
1272
  if (node.operator === 'void') return TYPES.undefined;
1203
1273
  if (node.operator === 'delete') return TYPES.boolean;
1204
- if (node.operator === 'typeof') return TYPES.string;
1274
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
1205
1275
 
1206
1276
  return TYPES.number;
1207
1277
  }
@@ -1210,7 +1280,13 @@ const getNodeType = (scope, node) => {
1210
1280
  // hack: if something.length, number type
1211
1281
  if (node.property.name === 'length') return TYPES.number;
1212
1282
 
1213
- // 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
1214
1290
  return TYPES.number;
1215
1291
  }
1216
1292
 
@@ -1230,8 +1306,8 @@ const getNodeType = (scope, node) => {
1230
1306
  const generateLiteral = (scope, decl, global, name) => {
1231
1307
  if (decl.value === null) return number(NULL);
1232
1308
 
1309
+ // hack: just return 1 for regex literals
1233
1310
  if (decl.regex) {
1234
- scope.regex[name] = decl.regex;
1235
1311
  return number(1);
1236
1312
  }
1237
1313
 
@@ -1244,16 +1320,7 @@ const generateLiteral = (scope, decl, global, name) => {
1244
1320
  return number(decl.value ? 1 : 0);
1245
1321
 
1246
1322
  case 'string':
1247
- const str = decl.value;
1248
- const rawElements = new Array(str.length);
1249
- let j = 0;
1250
- for (let i = 0; i < str.length; i++) {
1251
- rawElements[i] = str.charCodeAt(i);
1252
- }
1253
-
1254
- return makeArray(scope, {
1255
- rawElements
1256
- }, global, name, false, 'i16')[0];
1323
+ return makeString(scope, decl.value, global, name);
1257
1324
 
1258
1325
  default:
1259
1326
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -1274,9 +1341,9 @@ const countLeftover = wasm => {
1274
1341
 
1275
1342
  if (depth === 0)
1276
1343
  if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1277
- 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)) {}
1278
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++;
1279
- 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;
1280
1347
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1281
1348
  else if (inst[0] === Opcodes.return) count = 0;
1282
1349
  else if (inst[0] === Opcodes.call) {
@@ -1302,7 +1369,7 @@ const disposeLeftover = wasm => {
1302
1369
  const generateExp = (scope, decl) => {
1303
1370
  const expression = decl.expression;
1304
1371
 
1305
- const out = generate(scope, expression);
1372
+ const out = generate(scope, expression, undefined, undefined, true);
1306
1373
  disposeLeftover(out);
1307
1374
 
1308
1375
  return out;
@@ -1360,7 +1427,7 @@ const RTArrayUtil = {
1360
1427
  ]
1361
1428
  };
1362
1429
 
1363
- const generateCall = (scope, decl, _global, _name) => {
1430
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1364
1431
  /* const callee = decl.callee;
1365
1432
  const args = decl.arguments;
1366
1433
 
@@ -1426,8 +1493,8 @@ const generateCall = (scope, decl, _global, _name) => {
1426
1493
  // literal.func()
1427
1494
  if (!name && decl.callee.type === 'MemberExpression') {
1428
1495
  // megahack for /regex/.func()
1429
- if (decl.callee.object.regex) {
1430
- const funcName = decl.callee.property.name;
1496
+ const funcName = decl.callee.property.name;
1497
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1431
1498
  const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1432
1499
 
1433
1500
  funcIndex[func.name] = func.index;
@@ -1469,8 +1536,7 @@ const generateCall = (scope, decl, _global, _name) => {
1469
1536
 
1470
1537
  if (protoName) {
1471
1538
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1472
- const f = prototypeFuncs[x][protoName];
1473
- if (f) acc[x] = f;
1539
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1474
1540
  return acc;
1475
1541
  }, {});
1476
1542
 
@@ -1479,10 +1545,18 @@ const generateCall = (scope, decl, _global, _name) => {
1479
1545
  // use local for cached i32 length as commonly used
1480
1546
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1481
1547
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1482
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1483
1548
 
1484
1549
  // TODO: long-term, prototypes should be their individual separate funcs
1485
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;
1486
1560
  let lengthI32CacheUsed = false;
1487
1561
  const protoBC = {};
1488
1562
  for (const x in protoCands) {
@@ -1502,6 +1576,7 @@ const generateCall = (scope, decl, _global, _name) => {
1502
1576
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1503
1577
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1504
1578
 
1579
+ let optUnused = false;
1505
1580
  const protoOut = protoFunc(getPointer, {
1506
1581
  getCachedI32: () => {
1507
1582
  lengthI32CacheUsed = true;
@@ -1516,10 +1591,15 @@ const generateCall = (scope, decl, _global, _name) => {
1516
1591
  return makeArray(scope, {
1517
1592
  rawElements: new Array(length)
1518
1593
  }, _global, _name, true, itemType);
1594
+ }, () => {
1595
+ optUnused = true;
1596
+ return unusedValue;
1519
1597
  });
1520
1598
 
1599
+ if (!optUnused) allOptUnused = false;
1600
+
1521
1601
  protoBC[x] = [
1522
- [ Opcodes.block, valtypeBinary ],
1602
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1523
1603
  ...protoOut,
1524
1604
 
1525
1605
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
@@ -1528,11 +1608,13 @@ const generateCall = (scope, decl, _global, _name) => {
1528
1608
  ];
1529
1609
  }
1530
1610
 
1531
- return [
1532
- ...generate(scope, target),
1611
+ // todo: if some cands use optUnused and some don't, we will probably crash
1533
1612
 
1534
- Opcodes.i32_to_u,
1535
- [ Opcodes.local_set, pointerLocal ],
1613
+ return [
1614
+ ...(usePointerCache ? [
1615
+ ...rawPointer,
1616
+ [ Opcodes.local_set, pointerLocal ],
1617
+ ] : []),
1536
1618
 
1537
1619
  ...(!lengthI32CacheUsed ? [] : [
1538
1620
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1544,7 +1626,7 @@ const generateCall = (scope, decl, _global, _name) => {
1544
1626
 
1545
1627
  // TODO: error better
1546
1628
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1547
- }, valtypeBinary),
1629
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1548
1630
  ];
1549
1631
  }
1550
1632
  }
@@ -1591,7 +1673,9 @@ const generateCall = (scope, decl, _global, _name) => {
1591
1673
  const func = funcs.find(x => x.index === idx);
1592
1674
 
1593
1675
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1594
- 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);
1595
1679
 
1596
1680
  let args = decl.arguments;
1597
1681
  if (func && args.length < paramCount) {
@@ -1609,12 +1693,12 @@ const generateCall = (scope, decl, _global, _name) => {
1609
1693
  let out = [];
1610
1694
  for (const arg of args) {
1611
1695
  out = out.concat(generate(scope, arg));
1612
- if (userFunc) out = out.concat(getNodeType(scope, arg));
1696
+ if (typedParams) out = out.concat(getNodeType(scope, arg));
1613
1697
  }
1614
1698
 
1615
1699
  out.push([ Opcodes.call, idx ]);
1616
1700
 
1617
- if (!userFunc) {
1701
+ if (!typedReturns) {
1618
1702
  // let type;
1619
1703
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1620
1704
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1665,14 +1749,102 @@ const knownType = (scope, type) => {
1665
1749
  return null;
1666
1750
  };
1667
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
+
1668
1836
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1837
+ if (!Prefs.bytestring) delete bc[TYPES._bytestring];
1838
+
1669
1839
  const known = knownType(scope, type);
1670
1840
  if (known != null) {
1671
1841
  return bc[known] ?? bc.default;
1672
1842
  }
1673
1843
 
1674
- const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1844
+ if (Prefs.typeswitchUseBrtable)
1845
+ return brTable(type, bc, returns);
1675
1846
 
1847
+ const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1676
1848
  const out = [
1677
1849
  ...type,
1678
1850
  [ Opcodes.local_set, tmp ],
@@ -1704,7 +1876,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1704
1876
  return out;
1705
1877
  };
1706
1878
 
1707
- const allocVar = (scope, name, global = false) => {
1879
+ const allocVar = (scope, name, global = false, type = true) => {
1708
1880
  const target = global ? globals : scope.locals;
1709
1881
 
1710
1882
  // already declared
@@ -1718,8 +1890,10 @@ const allocVar = (scope, name, global = false) => {
1718
1890
  let idx = global ? globalInd++ : scope.localInd++;
1719
1891
  target[name] = { idx, type: valtypeBinary };
1720
1892
 
1721
- let typeIdx = global ? globalInd++ : scope.localInd++;
1722
- 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
+ }
1723
1897
 
1724
1898
  return idx;
1725
1899
  };
@@ -1759,11 +1933,14 @@ const extractTypeAnnotation = decl => {
1759
1933
  elementType = extractTypeAnnotation(a.elementType).type;
1760
1934
  }
1761
1935
 
1936
+ const typeName = type;
1762
1937
  type = typeAnnoToPorfType(type);
1763
1938
 
1939
+ if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
1940
+
1764
1941
  // if (decl.name) console.log(decl.name, { type, elementType });
1765
1942
 
1766
- return { type, elementType };
1943
+ return { type, typeName, elementType };
1767
1944
  };
1768
1945
 
1769
1946
  const generateVar = (scope, decl) => {
@@ -1777,6 +1954,8 @@ const generateVar = (scope, decl) => {
1777
1954
  for (const x of decl.declarations) {
1778
1955
  const name = mapName(x.id.name);
1779
1956
 
1957
+ if (!name) return todo('destructuring is not supported yet');
1958
+
1780
1959
  if (x.init && isFuncType(x.init.type)) {
1781
1960
  // hack for let a = function () { ... }
1782
1961
  x.init.id = { name };
@@ -1792,7 +1971,12 @@ const generateVar = (scope, decl) => {
1792
1971
  continue; // always ignore
1793
1972
  }
1794
1973
 
1795
- 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
+
1796
1980
  if (x.init) {
1797
1981
  out = out.concat(generate(scope, x.init, global, name));
1798
1982
 
@@ -1802,10 +1986,6 @@ const generateVar = (scope, decl) => {
1802
1986
 
1803
1987
  // hack: this follows spec properly but is mostly unneeded 😅
1804
1988
  // out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
1805
-
1806
- if (typedInput && x.id.typeAnnotation) {
1807
- addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1808
- }
1809
1989
  }
1810
1990
 
1811
1991
  return out;
@@ -1914,6 +2094,8 @@ const generateAssign = (scope, decl) => {
1914
2094
  ];
1915
2095
  }
1916
2096
 
2097
+ if (!name) return todo('destructuring is not supported yet');
2098
+
1917
2099
  const [ local, isGlobal ] = lookupName(scope, name);
1918
2100
 
1919
2101
  if (local === undefined) {
@@ -2052,6 +2234,8 @@ const generateUnary = (scope, decl) => {
2052
2234
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2053
2235
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2054
2236
 
2237
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2238
+
2055
2239
  // object and internal types
2056
2240
  default: makeString(scope, 'object', false, '#typeof_result'),
2057
2241
  });
@@ -2157,8 +2341,10 @@ const generateFor = (scope, decl) => {
2157
2341
  out.push([ Opcodes.loop, Blocktype.void ]);
2158
2342
  depth.push('for');
2159
2343
 
2160
- out.push(...generate(scope, decl.test));
2161
- 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 ]);
2162
2348
  depth.push('if');
2163
2349
 
2164
2350
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2166,8 +2352,7 @@ const generateFor = (scope, decl) => {
2166
2352
  out.push(...generate(scope, decl.body));
2167
2353
  out.push([ Opcodes.end ]);
2168
2354
 
2169
- out.push(...generate(scope, decl.update));
2170
- depth.pop();
2355
+ if (decl.update) out.push(...generate(scope, decl.update));
2171
2356
 
2172
2357
  out.push([ Opcodes.br, 1 ]);
2173
2358
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2224,7 +2409,13 @@ const generateForOf = (scope, decl) => {
2224
2409
  // setup local for left
2225
2410
  generate(scope, decl.left);
2226
2411
 
2227
- 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
+
2228
2419
  const [ local, isGlobal ] = lookupName(scope, leftName);
2229
2420
 
2230
2421
  depth.push('block');
@@ -2233,13 +2424,14 @@ const generateForOf = (scope, decl) => {
2233
2424
  // // todo: we should only do this for strings but we don't know at compile-time :(
2234
2425
  // hack: this is naughty and will break things!
2235
2426
  let newOut = number(0, Valtype.f64), newPointer = -1;
2236
- if (pages.hasString) {
2427
+ if (pages.hasAnyString) {
2237
2428
  0, [ newOut, newPointer ] = makeArray(scope, {
2238
2429
  rawElements: new Array(1)
2239
2430
  }, isGlobal, leftName, true, 'i16');
2240
2431
  }
2241
2432
 
2242
2433
  // set type for local
2434
+ // todo: optimize away counter and use end pointer
2243
2435
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2244
2436
  [TYPES._array]: [
2245
2437
  ...setType(scope, leftName, TYPES.number),
@@ -2364,7 +2556,7 @@ const generateThrow = (scope, decl) => {
2364
2556
  // hack: throw new X("...") -> throw "..."
2365
2557
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2366
2558
  constructor = decl.argument.callee.name;
2367
- message = decl.argument.arguments[0].value;
2559
+ message = decl.argument.arguments[0]?.value ?? '';
2368
2560
  }
2369
2561
 
2370
2562
  if (tags.length === 0) tags.push({
@@ -2376,6 +2568,9 @@ const generateThrow = (scope, decl) => {
2376
2568
  let exceptId = exceptions.push({ constructor, message }) - 1;
2377
2569
  let tagIdx = tags[0].idx;
2378
2570
 
2571
+ scope.exceptions ??= [];
2572
+ scope.exceptions.push(exceptId);
2573
+
2379
2574
  // todo: write a description of how this works lol
2380
2575
 
2381
2576
  return [
@@ -2420,25 +2615,31 @@ const generateAssignPat = (scope, decl) => {
2420
2615
  };
2421
2616
 
2422
2617
  let pages = new Map();
2423
- const allocPage = (reason, type) => {
2618
+ const allocPage = (scope, reason, type) => {
2424
2619
  if (pages.has(reason)) return pages.get(reason).ind;
2425
2620
 
2426
2621
  if (reason.startsWith('array:')) pages.hasArray = true;
2427
2622
  if (reason.startsWith('string:')) pages.hasString = true;
2623
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
2624
+ if (reason.includes('string:')) pages.hasAnyString = true;
2428
2625
 
2429
2626
  const ind = pages.size;
2430
2627
  pages.set(reason, { ind, type });
2431
2628
 
2432
- 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})`);
2433
2633
 
2434
2634
  return ind;
2435
2635
  };
2436
2636
 
2637
+ // todo: add scope.pages
2437
2638
  const freePage = reason => {
2438
2639
  const { ind } = pages.get(reason);
2439
2640
  pages.delete(reason);
2440
2641
 
2441
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2642
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2442
2643
 
2443
2644
  return ind;
2444
2645
  };
@@ -2458,25 +2659,34 @@ const StoreOps = {
2458
2659
  f64: Opcodes.f64_store,
2459
2660
 
2460
2661
  // expects i32 input!
2461
- i16: Opcodes.i32_store16
2662
+ i8: Opcodes.i32_store8,
2663
+ i16: Opcodes.i32_store16,
2462
2664
  };
2463
2665
 
2464
2666
  let data = [];
2465
2667
 
2466
- const compileBytes = (val, itemType, signed = true) => {
2668
+ const compileBytes = (val, itemType) => {
2467
2669
  // todo: this is a mess and needs confirming / ????
2468
2670
  switch (itemType) {
2469
2671
  case 'i8': return [ val % 256 ];
2470
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2471
-
2472
- case 'i32':
2473
- case 'i64':
2474
- 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
2475
2676
 
2476
2677
  case 'f64': return ieee754_binary64(val);
2477
2678
  }
2478
2679
  };
2479
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
+
2480
2690
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2481
2691
  const out = [];
2482
2692
 
@@ -2486,7 +2696,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2486
2696
 
2487
2697
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2488
2698
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2489
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
2699
+ arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2490
2700
  }
2491
2701
 
2492
2702
  const pointer = arrays.get(name);
@@ -2498,18 +2708,24 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2498
2708
  const length = elements.length;
2499
2709
 
2500
2710
  if (firstAssign && useRawElements) {
2501
- let bytes = compileBytes(length, 'i32');
2711
+ // if length is 0 memory/data will just be 0000... anyway
2712
+ if (length !== 0) {
2713
+ let bytes = compileBytes(length, 'i32');
2502
2714
 
2503
- if (!initEmpty) for (let i = 0; i < length; i++) {
2504
- if (elements[i] == null) continue;
2715
+ if (!initEmpty) for (let i = 0; i < length; i++) {
2716
+ if (elements[i] == null) continue;
2505
2717
 
2506
- bytes.push(...compileBytes(elements[i], itemType));
2507
- }
2718
+ bytes.push(...compileBytes(elements[i], itemType));
2719
+ }
2508
2720
 
2509
- data.push({
2510
- offset: pointer,
2511
- bytes
2512
- });
2721
+ const ind = data.push({
2722
+ offset: pointer,
2723
+ bytes
2724
+ }) - 1;
2725
+
2726
+ scope.data ??= [];
2727
+ scope.data.push(ind);
2728
+ }
2513
2729
 
2514
2730
  // local value as pointer
2515
2731
  out.push(...number(pointer));
@@ -2532,7 +2748,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2532
2748
  out.push(
2533
2749
  ...number(0, Valtype.i32),
2534
2750
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2535
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2751
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2536
2752
  );
2537
2753
  }
2538
2754
 
@@ -2542,15 +2758,31 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2542
2758
  return [ out, pointer ];
2543
2759
  };
2544
2760
 
2545
- const makeString = (scope, str, global = false, name = '$undeclared') => {
2761
+ const byteStringable = str => {
2762
+ if (!Prefs.bytestring) return false;
2763
+
2764
+ for (let i = 0; i < str.length; i++) {
2765
+ if (str.charCodeAt(i) > 0xFF) return false;
2766
+ }
2767
+
2768
+ return true;
2769
+ };
2770
+
2771
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2546
2772
  const rawElements = new Array(str.length);
2773
+ let byteStringable = Prefs.bytestring;
2547
2774
  for (let i = 0; i < str.length; i++) {
2548
- rawElements[i] = str.charCodeAt(i);
2775
+ const c = str.charCodeAt(i);
2776
+ rawElements[i] = c;
2777
+
2778
+ if (byteStringable && c > 0xFF) byteStringable = false;
2549
2779
  }
2550
2780
 
2781
+ if (byteStringable && forceBytestring === false) byteStringable = false;
2782
+
2551
2783
  return makeArray(scope, {
2552
2784
  rawElements
2553
- }, global, name, false, 'i16')[0];
2785
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2554
2786
  };
2555
2787
 
2556
2788
  let arrays = new Map();
@@ -2578,10 +2810,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2578
2810
  ];
2579
2811
  }
2580
2812
 
2813
+ const object = generate(scope, decl.object);
2814
+ const property = generate(scope, decl.property);
2815
+
2581
2816
  // // todo: we should only do this for strings but we don't know at compile-time :(
2582
2817
  // hack: this is naughty and will break things!
2583
- let newOut = number(0, Valtype.f64), newPointer = -1;
2584
- if (pages.hasString) {
2818
+ let newOut = number(0, valtypeBinary), newPointer = -1;
2819
+ if (pages.hasAnyString) {
2585
2820
  0, [ newOut, newPointer ] = makeArray(scope, {
2586
2821
  rawElements: new Array(1)
2587
2822
  }, _global, _name, true, 'i16');
@@ -2590,7 +2825,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2590
2825
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2591
2826
  [TYPES._array]: [
2592
2827
  // get index as valtype
2593
- ...generate(scope, decl.property),
2828
+ ...property,
2594
2829
 
2595
2830
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2596
2831
  Opcodes.i32_to_u,
@@ -2598,7 +2833,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2598
2833
  [ Opcodes.i32_mul ],
2599
2834
 
2600
2835
  ...(aotPointer ? [] : [
2601
- ...generate(scope, decl.object),
2836
+ ...object,
2602
2837
  Opcodes.i32_to_u,
2603
2838
  [ Opcodes.i32_add ]
2604
2839
  ]),
@@ -2617,14 +2852,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2617
2852
 
2618
2853
  ...number(0, Valtype.i32), // base 0 for store later
2619
2854
 
2620
- ...generate(scope, decl.property),
2621
-
2855
+ ...property,
2622
2856
  Opcodes.i32_to_u,
2857
+
2623
2858
  ...number(ValtypeSize.i16, Valtype.i32),
2624
2859
  [ Opcodes.i32_mul ],
2625
2860
 
2626
2861
  ...(aotPointer ? [] : [
2627
- ...generate(scope, decl.object),
2862
+ ...object,
2628
2863
  Opcodes.i32_to_u,
2629
2864
  [ Opcodes.i32_add ]
2630
2865
  ]),
@@ -2641,8 +2876,36 @@ export const generateMember = (scope, decl, _global, _name) => {
2641
2876
  ...number(TYPES.string, Valtype.i32),
2642
2877
  setLastType(scope)
2643
2878
  ],
2879
+ [TYPES._bytestring]: [
2880
+ // setup new/out array
2881
+ ...newOut,
2882
+ [ Opcodes.drop ],
2883
+
2884
+ ...number(0, Valtype.i32), // base 0 for store later
2644
2885
 
2645
- default: [ [ Opcodes.unreachable ] ]
2886
+ ...property,
2887
+ Opcodes.i32_to_u,
2888
+
2889
+ ...(aotPointer ? [] : [
2890
+ ...object,
2891
+ Opcodes.i32_to_u,
2892
+ [ Opcodes.i32_add ]
2893
+ ]),
2894
+
2895
+ // load current string ind {arg}
2896
+ [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2897
+
2898
+ // store to new string ind 0
2899
+ [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2900
+
2901
+ // return new string (page)
2902
+ ...number(newPointer),
2903
+
2904
+ ...number(TYPES._bytestring, Valtype.i32),
2905
+ setLastType(scope)
2906
+ ],
2907
+
2908
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
2646
2909
  });
2647
2910
  };
2648
2911
 
@@ -2659,13 +2922,16 @@ const objectHack = node => {
2659
2922
  // if object is not identifier or another member exp, give up
2660
2923
  if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
2661
2924
 
2662
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
2925
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2663
2926
 
2664
2927
  // if .length, give up (hack within a hack!)
2665
2928
  if (node.property.name === 'length') return node;
2666
2929
 
2930
+ // no object name, give up
2931
+ if (!objectName) return node;
2932
+
2667
2933
  const name = '__' + objectName + '_' + node.property.name;
2668
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2934
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2669
2935
 
2670
2936
  return {
2671
2937
  type: 'Identifier',
@@ -2722,10 +2988,8 @@ const generateFunc = (scope, decl) => {
2722
2988
  const func = {
2723
2989
  name,
2724
2990
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2725
- returns: innerScope.returns,
2726
- locals: innerScope.locals,
2727
- throws: innerScope.throws,
2728
- index: currentFuncIndex++
2991
+ index: currentFuncIndex++,
2992
+ ...innerScope
2729
2993
  };
2730
2994
  funcIndex[name] = func.index;
2731
2995
 
@@ -2763,6 +3027,16 @@ const generateCode = (scope, decl) => {
2763
3027
  };
2764
3028
 
2765
3029
  const internalConstrs = {
3030
+ Boolean: {
3031
+ generate: (scope, decl) => {
3032
+ if (decl.arguments.length === 0) return number(0);
3033
+
3034
+ // should generate/run all args
3035
+ return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3036
+ },
3037
+ type: TYPES.boolean
3038
+ },
3039
+
2766
3040
  Array: {
2767
3041
  generate: (scope, decl, global, name) => {
2768
3042
  // new Array(i0, i1, ...)
@@ -2884,7 +3158,7 @@ export default program => {
2884
3158
  body: program.body
2885
3159
  };
2886
3160
 
2887
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3161
+ if (Prefs.astLog) console.log(program.body.body);
2888
3162
 
2889
3163
  generateFunc(scope, program);
2890
3164