porffor 0.2.0-47b4f2e → 0.2.0-4b72c49

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,11 @@ 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 => {
193
+ const int = parseInt(x);
194
+ if (Number.isNaN(int)) return scope.locals[x]?.idx;
195
+ return int;
196
+ });
187
197
 
188
198
  out.push([ ...inst, ...immediates ]);
189
199
  }
@@ -191,29 +201,43 @@ const generate = (scope, decl, global = false, name = undefined) => {
191
201
  return out;
192
202
  },
193
203
 
194
- __internal_print_type: str => {
195
- const type = getType(scope, str) - TYPES.number;
204
+ __Porffor_bs: str => [
205
+ ...makeString(scope, str, undefined, undefined, true),
196
206
 
197
- return [
198
- ...number(type),
199
- [ Opcodes.call, importedFuncs.print ],
207
+ ...number(TYPES._bytestring, Valtype.i32),
208
+ setLastType(scope)
209
+ ],
210
+ __Porffor_s: str => [
211
+ ...makeString(scope, str, undefined, undefined, false),
200
212
 
201
- // newline
202
- ...number(10),
203
- [ Opcodes.call, importedFuncs.printChar ]
204
- ];
205
- }
206
- }
213
+ ...number(TYPES.string, Valtype.i32),
214
+ setLastType(scope)
215
+ ],
216
+ };
207
217
 
208
218
  const name = decl.tag.name;
209
219
  // hack for inline asm
210
220
  if (!funcs[name]) return todo('tagged template expressions not implemented');
211
221
 
212
- const str = decl.quasi.quasis[0].value.raw;
222
+ const { quasis, expressions } = decl.quasi;
223
+ let str = quasis[0].value.raw;
224
+
225
+ for (let i = 0; i < expressions.length; i++) {
226
+ const e = expressions[i];
227
+ str += lookupName(scope, e.name)[0].idx;
228
+ str += quasis[i + 1].value.raw;
229
+ }
230
+
213
231
  return funcs[name](str);
214
232
  }
215
233
 
216
234
  default:
235
+ // ignore typescript nodes
236
+ if (decl.type.startsWith('TS') ||
237
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
238
+ return [];
239
+ }
240
+
217
241
  return todo(`no generation for ${decl.type}!`);
218
242
  }
219
243
  };
@@ -264,25 +288,25 @@ const generateIdent = (scope, decl) => {
264
288
  const name = mapName(rawName);
265
289
  let local = scope.locals[rawName];
266
290
 
267
- if (builtinVars[name]) {
291
+ if (Object.hasOwn(builtinVars, name)) {
268
292
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
269
293
  return builtinVars[name];
270
294
  }
271
295
 
272
- if (builtinFuncs[name] || internalConstrs[name]) {
296
+ if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
273
297
  // todo: return an actual something
274
298
  return number(1);
275
299
  }
276
300
 
277
- if (local === undefined) {
301
+ if (local?.idx === undefined) {
278
302
  // no local var with name
279
- if (importedFuncs.hasOwnProperty(name)) return number(importedFuncs[name]);
280
- if (funcIndex[name] !== undefined) return number(funcIndex[name]);
303
+ if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
304
+ if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
281
305
 
282
- if (globals[name] !== undefined) return [ [ Opcodes.global_get, globals[name].idx ] ];
306
+ if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
283
307
  }
284
308
 
285
- if (local === undefined && rawName.startsWith('__')) {
309
+ if (local?.idx === undefined && rawName.startsWith('__')) {
286
310
  // return undefined if unknown key in already known var
287
311
  let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
288
312
  if (parent.includes('_')) parent = '__' + parent;
@@ -291,7 +315,7 @@ const generateIdent = (scope, decl) => {
291
315
  if (!parentLookup[1]) return number(UNDEFINED);
292
316
  }
293
317
 
294
- if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
318
+ if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
295
319
 
296
320
  return [ [ Opcodes.local_get, local.idx ] ];
297
321
  };
@@ -360,12 +384,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
360
384
  ...right,
361
385
  // note type
362
386
  ...rightType,
363
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
387
+ setLastType(scope),
364
388
  [ Opcodes.else ],
365
389
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
366
390
  // note type
367
391
  ...leftType,
368
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
392
+ setLastType(scope),
369
393
  [ Opcodes.end ],
370
394
  Opcodes.i32_from
371
395
  ];
@@ -379,12 +403,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
379
403
  ...right,
380
404
  // note type
381
405
  ...rightType,
382
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
406
+ setLastType(scope),
383
407
  [ Opcodes.else ],
384
408
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
385
409
  // note type
386
410
  ...leftType,
387
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
411
+ setLastType(scope),
388
412
  [ Opcodes.end ]
389
413
  ];
390
414
  };
@@ -399,9 +423,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
399
423
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
400
424
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
401
425
 
402
- const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
403
- if (aotWFA) addVarMeta(name, { wellFormed: undefined });
404
-
405
426
  if (assign) {
406
427
  const pointer = arrays.get(name ?? '$undeclared');
407
428
 
@@ -639,11 +660,12 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
639
660
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
640
661
  ];
641
662
 
642
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
663
+ const useTmp = knownType(scope, type) == null;
664
+ const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
643
665
 
644
666
  const def = [
645
667
  // if value != 0
646
- [ Opcodes.local_get, tmp ],
668
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
647
669
 
648
670
  // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
649
671
  ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
@@ -655,7 +677,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
655
677
 
656
678
  return [
657
679
  ...wasm,
658
- [ Opcodes.local_set, tmp ],
680
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
659
681
 
660
682
  ...typeSwitch(scope, type, {
661
683
  // [TYPES.number]: def,
@@ -664,7 +686,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
664
686
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
665
687
  ],
666
688
  [TYPES.string]: [
667
- [ Opcodes.local_get, tmp ],
689
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
668
690
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
669
691
 
670
692
  // get length
@@ -675,16 +697,27 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
675
697
  [ Opcodes.i32_eqz ], */
676
698
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
677
699
  ],
700
+ [TYPES._bytestring]: [ // duplicate of string
701
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
702
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
703
+
704
+ // get length
705
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
706
+
707
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
708
+ ],
678
709
  default: def
679
710
  }, intOut ? Valtype.i32 : valtypeBinary)
680
711
  ];
681
712
  };
682
713
 
683
714
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
684
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
715
+ const useTmp = knownType(scope, type) == null;
716
+ const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
717
+
685
718
  return [
686
719
  ...wasm,
687
- [ Opcodes.local_set, tmp ],
720
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
688
721
 
689
722
  ...typeSwitch(scope, type, {
690
723
  [TYPES._array]: [
@@ -692,7 +725,18 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
692
725
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
693
726
  ],
694
727
  [TYPES.string]: [
695
- [ Opcodes.local_get, tmp ],
728
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
729
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
730
+
731
+ // get length
732
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
733
+
734
+ // if length == 0
735
+ [ Opcodes.i32_eqz ],
736
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
737
+ ],
738
+ [TYPES._bytestring]: [ // duplicate of string
739
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
696
740
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
697
741
 
698
742
  // get length
@@ -704,7 +748,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
704
748
  ],
705
749
  default: [
706
750
  // if value == 0
707
- [ Opcodes.local_get, tmp ],
751
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
708
752
 
709
753
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
710
754
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -714,10 +758,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
714
758
  };
715
759
 
716
760
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
717
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
761
+ const useTmp = knownType(scope, type) == null;
762
+ const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
763
+
718
764
  return [
719
765
  ...wasm,
720
- [ Opcodes.local_set, tmp ],
766
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
721
767
 
722
768
  ...typeSwitch(scope, type, {
723
769
  [TYPES.undefined]: [
@@ -726,7 +772,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
726
772
  ],
727
773
  [TYPES.object]: [
728
774
  // object, null if == 0
729
- [ Opcodes.local_get, tmp ],
775
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
730
776
 
731
777
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
732
778
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -755,11 +801,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
755
801
  return performLogicOp(scope, op, left, right, leftType, rightType);
756
802
  }
757
803
 
804
+ const knownLeft = knownType(scope, leftType);
805
+ const knownRight = knownType(scope, rightType);
806
+
758
807
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
759
808
  const strictOp = op === '===' || op === '!==';
760
809
 
761
810
  const startOut = [], endOut = [];
762
- const finalise = out => startOut.concat(out, endOut);
811
+ const finalize = out => startOut.concat(out, endOut);
763
812
 
764
813
  // if strict (in)equal check types match
765
814
  if (strictOp) {
@@ -804,31 +853,32 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
804
853
  // todo: if equality op and an operand is undefined, return false
805
854
  // todo: niche null hell with 0
806
855
 
807
- // if (leftType === TYPES.string || rightType === TYPES.string) {
808
- // if (op === '+') {
809
- // // string concat (a + b)
810
- // return finalise(concatStrings(scope, left, right, _global, _name, assign));
811
- // }
812
-
813
- // // not an equality op, NaN
814
- // if (!eqOp) return finalise(number(NaN));
815
-
816
- // // else leave bool ops
817
- // // todo: convert string to number if string and number/bool
818
- // // todo: string (>|>=|<|<=) string
819
-
820
- // // string comparison
821
- // if (op === '===' || op === '==') {
822
- // return finalise(compareStrings(scope, left, right));
823
- // }
824
-
825
- // if (op === '!==' || op === '!=') {
826
- // return finalise([
827
- // ...compareStrings(scope, left, right),
828
- // [ Opcodes.i32_eqz ]
829
- // ]);
830
- // }
831
- // }
856
+ // todo: this should be dynamic but for now only static
857
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) {
858
+ if (op === '+') {
859
+ // string concat (a + b)
860
+ return concatStrings(scope, left, right, _global, _name, assign);
861
+ }
862
+
863
+ // not an equality op, NaN
864
+ if (!eqOp) return number(NaN);
865
+
866
+ // else leave bool ops
867
+ // todo: convert string to number if string and number/bool
868
+ // todo: string (>|>=|<|<=) string
869
+
870
+ // string comparison
871
+ if (op === '===' || op === '==') {
872
+ return compareStrings(scope, left, right);
873
+ }
874
+
875
+ if (op === '!==' || op === '!=') {
876
+ return [
877
+ ...compareStrings(scope, left, right),
878
+ [ Opcodes.i32_eqz ]
879
+ ];
880
+ }
881
+ }
832
882
 
833
883
  let ops = operatorOpcode[valtype][op];
834
884
 
@@ -838,7 +888,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
838
888
  includeBuiltin(scope, builtinName);
839
889
  const idx = funcIndex[builtinName];
840
890
 
841
- return finalise([
891
+ return finalize([
842
892
  ...left,
843
893
  ...right,
844
894
  [ Opcodes.call, idx ]
@@ -852,7 +902,13 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
852
902
 
853
903
  let tmpLeft, tmpRight;
854
904
  // if equal op, check if strings for compareStrings
855
- if (op === '===' || op === '==' || op === '!==' || op === '!=') {
905
+ if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
906
+ // todo: intelligent partial skip later
907
+ // if neither known are string, stop this madness
908
+ if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
909
+ return;
910
+ }
911
+
856
912
  tmpLeft = localTmp(scope, '__tmpop_left');
857
913
  tmpRight = localTmp(scope, '__tmpop_right');
858
914
 
@@ -886,7 +942,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
886
942
  [ Opcodes.i32_or ],
887
943
  [ Opcodes.if, Blocktype.void ],
888
944
  ...number(0, Valtype.i32),
889
- [ Opcodes.br, 1 ],
945
+ [ Opcodes.br, 2 ],
890
946
  [ Opcodes.end ],
891
947
 
892
948
  ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
@@ -902,9 +958,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
902
958
  // endOut.push(stringOnly([ Opcodes.end ]));
903
959
  endOut.unshift(stringOnly([ Opcodes.end ]));
904
960
  // }
905
- }
961
+ })();
906
962
 
907
- return finalise([
963
+ return finalize([
908
964
  ...left,
909
965
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
910
966
  ...right,
@@ -921,7 +977,7 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
921
977
  return out;
922
978
  };
923
979
 
924
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
980
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
925
981
  const existing = funcs.find(x => x.name === name);
926
982
  if (existing) return existing;
927
983
 
@@ -933,6 +989,35 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
933
989
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
934
990
  }
935
991
 
992
+ for (const x of _data) {
993
+ const copy = { ...x };
994
+ copy.offset += pages.size * pageSize;
995
+ data.push(copy);
996
+ }
997
+
998
+ if (typeof wasm === 'function') {
999
+ const scope = {
1000
+ name,
1001
+ params,
1002
+ locals,
1003
+ returns,
1004
+ localInd: allLocals.length,
1005
+ };
1006
+
1007
+ wasm = wasm(scope, {
1008
+ TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
1009
+ builtin: name => {
1010
+ let idx = funcIndex[name] ?? importedFuncs[name];
1011
+ if (idx === undefined && builtinFuncs[name]) {
1012
+ includeBuiltin(scope, name);
1013
+ idx = funcIndex[name];
1014
+ }
1015
+
1016
+ return idx;
1017
+ }
1018
+ });
1019
+ }
1020
+
936
1021
  let baseGlobalIdx, i = 0;
937
1022
  for (const type of globalTypes) {
938
1023
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -1013,7 +1098,8 @@ const TYPES = {
1013
1098
 
1014
1099
  // these are not "typeof" types but tracked internally
1015
1100
  _array: 0x10,
1016
- _regexp: 0x11
1101
+ _regexp: 0x11,
1102
+ _bytestring: 0x12
1017
1103
  };
1018
1104
 
1019
1105
  const TYPE_NAMES = {
@@ -1027,13 +1113,17 @@ const TYPE_NAMES = {
1027
1113
  [TYPES.bigint]: 'BigInt',
1028
1114
 
1029
1115
  [TYPES._array]: 'Array',
1030
- [TYPES._regexp]: 'RegExp'
1116
+ [TYPES._regexp]: 'RegExp',
1117
+ [TYPES._bytestring]: 'ByteString'
1031
1118
  };
1032
1119
 
1033
1120
  const getType = (scope, _name) => {
1034
1121
  const name = mapName(_name);
1035
1122
 
1123
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1036
1124
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1125
+
1126
+ if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
1037
1127
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1038
1128
 
1039
1129
  let type = TYPES.undefined;
@@ -1051,11 +1141,13 @@ const setType = (scope, _name, type) => {
1051
1141
 
1052
1142
  const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
1053
1143
 
1144
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return [];
1054
1145
  if (scope.locals[name]) return [
1055
1146
  ...out,
1056
1147
  [ Opcodes.local_set, scope.locals[name + '#type'].idx ]
1057
1148
  ];
1058
1149
 
1150
+ if (typedInput && globals[name]?.metadata?.type != null) return [];
1059
1151
  if (globals[name]) return [
1060
1152
  ...out,
1061
1153
  [ Opcodes.global_set, globals[name + '#type'].idx ]
@@ -1064,11 +1156,22 @@ const setType = (scope, _name, type) => {
1064
1156
  // throw new Error('could not find var');
1065
1157
  };
1066
1158
 
1159
+ const getLastType = scope => {
1160
+ scope.gotLastType = true;
1161
+ return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1162
+ };
1163
+
1164
+ const setLastType = scope => {
1165
+ return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1166
+ };
1167
+
1067
1168
  const getNodeType = (scope, node) => {
1068
1169
  const inner = () => {
1069
1170
  if (node.type === 'Literal') {
1070
1171
  if (node.regex) return TYPES._regexp;
1071
1172
 
1173
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1174
+
1072
1175
  return TYPES[typeof node.value];
1073
1176
  }
1074
1177
 
@@ -1082,6 +1185,15 @@ const getNodeType = (scope, node) => {
1082
1185
 
1083
1186
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1084
1187
  const name = node.callee.name;
1188
+ if (!name) {
1189
+ // iife
1190
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1191
+
1192
+ // presume
1193
+ // todo: warn here?
1194
+ return TYPES.number;
1195
+ }
1196
+
1085
1197
  const func = funcs.find(x => x.name === name);
1086
1198
 
1087
1199
  if (func) {
@@ -1089,10 +1201,27 @@ const getNodeType = (scope, node) => {
1089
1201
  if (func.returnType) return func.returnType;
1090
1202
  }
1091
1203
 
1092
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1204
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1093
1205
  if (internalConstrs[name]) return internalConstrs[name].type;
1094
1206
 
1095
- if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1207
+ // check if this is a prototype function
1208
+ // if so and there is only one impl (eg charCodeAt)
1209
+ // use that return type as that is the only possibility
1210
+ // (if non-matching type it would error out)
1211
+ if (name.startsWith('__')) {
1212
+ const spl = name.slice(2).split('_');
1213
+
1214
+ const func = spl[spl.length - 1];
1215
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
1216
+ if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1217
+ }
1218
+
1219
+ if (name.startsWith('__Porffor_wasm_')) {
1220
+ // todo: return undefined for non-returning ops
1221
+ return TYPES.number;
1222
+ }
1223
+
1224
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1096
1225
 
1097
1226
  // presume
1098
1227
  // todo: warn here?
@@ -1140,6 +1269,14 @@ const getNodeType = (scope, node) => {
1140
1269
 
1141
1270
  if (node.type === 'BinaryExpression') {
1142
1271
  if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1272
+ if (node.operator !== '+') return TYPES.number;
1273
+
1274
+ const knownLeft = knownType(scope, getNodeType(scope, node.left));
1275
+ const knownRight = knownType(scope, getNodeType(scope, node.right));
1276
+
1277
+ // todo: this should be dynamic but for now only static
1278
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1279
+
1143
1280
  return TYPES.number;
1144
1281
 
1145
1282
  // todo: string concat types
@@ -1164,7 +1301,7 @@ const getNodeType = (scope, node) => {
1164
1301
  if (node.operator === '!') return TYPES.boolean;
1165
1302
  if (node.operator === 'void') return TYPES.undefined;
1166
1303
  if (node.operator === 'delete') return TYPES.boolean;
1167
- if (node.operator === 'typeof') return TYPES.string;
1304
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
1168
1305
 
1169
1306
  return TYPES.number;
1170
1307
  }
@@ -1173,11 +1310,17 @@ const getNodeType = (scope, node) => {
1173
1310
  // hack: if something.length, number type
1174
1311
  if (node.property.name === 'length') return TYPES.number;
1175
1312
 
1176
- // we cannot guess
1313
+ // ts hack
1314
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1315
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1316
+
1317
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1318
+
1319
+ // presume
1177
1320
  return TYPES.number;
1178
1321
  }
1179
1322
 
1180
- if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1323
+ if (scope.locals['#last_type']) return [ getLastType(scope) ];
1181
1324
 
1182
1325
  // presume
1183
1326
  // todo: warn here?
@@ -1193,8 +1336,8 @@ const getNodeType = (scope, node) => {
1193
1336
  const generateLiteral = (scope, decl, global, name) => {
1194
1337
  if (decl.value === null) return number(NULL);
1195
1338
 
1339
+ // hack: just return 1 for regex literals
1196
1340
  if (decl.regex) {
1197
- scope.regex[name] = decl.regex;
1198
1341
  return number(1);
1199
1342
  }
1200
1343
 
@@ -1207,16 +1350,7 @@ const generateLiteral = (scope, decl, global, name) => {
1207
1350
  return number(decl.value ? 1 : 0);
1208
1351
 
1209
1352
  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];
1353
+ return makeString(scope, decl.value, global, name);
1220
1354
 
1221
1355
  default:
1222
1356
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -1237,9 +1371,9 @@ const countLeftover = wasm => {
1237
1371
 
1238
1372
  if (depth === 0)
1239
1373
  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)) {}
1374
+ 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
1375
  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;
1376
+ else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1243
1377
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1244
1378
  else if (inst[0] === Opcodes.return) count = 0;
1245
1379
  else if (inst[0] === Opcodes.call) {
@@ -1265,7 +1399,7 @@ const disposeLeftover = wasm => {
1265
1399
  const generateExp = (scope, decl) => {
1266
1400
  const expression = decl.expression;
1267
1401
 
1268
- const out = generate(scope, expression);
1402
+ const out = generate(scope, expression, undefined, undefined, true);
1269
1403
  disposeLeftover(out);
1270
1404
 
1271
1405
  return out;
@@ -1323,7 +1457,7 @@ const RTArrayUtil = {
1323
1457
  ]
1324
1458
  };
1325
1459
 
1326
- const generateCall = (scope, decl, _global, _name) => {
1460
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1327
1461
  /* const callee = decl.callee;
1328
1462
  const args = decl.arguments;
1329
1463
 
@@ -1356,13 +1490,13 @@ const generateCall = (scope, decl, _global, _name) => {
1356
1490
  const finalStatement = parsed.body[parsed.body.length - 1];
1357
1491
  out.push(
1358
1492
  ...getNodeType(scope, finalStatement),
1359
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1493
+ setLastType(scope)
1360
1494
  );
1361
1495
  } else if (countLeftover(out) === 0) {
1362
1496
  out.push(...number(UNDEFINED));
1363
1497
  out.push(
1364
1498
  ...number(TYPES.undefined, Valtype.i32),
1365
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1499
+ setLastType(scope)
1366
1500
  );
1367
1501
  }
1368
1502
 
@@ -1380,8 +1514,7 @@ const generateCall = (scope, decl, _global, _name) => {
1380
1514
  if (name && name.startsWith('__')) {
1381
1515
  const spl = name.slice(2).split('_');
1382
1516
 
1383
- const func = spl[spl.length - 1];
1384
- protoName = func;
1517
+ protoName = spl[spl.length - 1];
1385
1518
 
1386
1519
  target = { ...decl.callee };
1387
1520
  target.name = spl.slice(0, -1).join('_');
@@ -1390,8 +1523,8 @@ const generateCall = (scope, decl, _global, _name) => {
1390
1523
  // literal.func()
1391
1524
  if (!name && decl.callee.type === 'MemberExpression') {
1392
1525
  // megahack for /regex/.func()
1393
- if (decl.callee.object.regex) {
1394
- const funcName = decl.callee.property.name;
1526
+ const funcName = decl.callee.property.name;
1527
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1395
1528
  const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1396
1529
 
1397
1530
  funcIndex[func.name] = func.index;
@@ -1407,12 +1540,11 @@ const generateCall = (scope, decl, _global, _name) => {
1407
1540
  Opcodes.i32_from_u,
1408
1541
 
1409
1542
  ...number(TYPES.boolean, Valtype.i32),
1410
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1543
+ setLastType(scope)
1411
1544
  ];
1412
1545
  }
1413
1546
 
1414
- const func = decl.callee.property.name;
1415
- protoName = func;
1547
+ protoName = decl.callee.property.name;
1416
1548
 
1417
1549
  target = decl.callee.object;
1418
1550
  }
@@ -1434,8 +1566,7 @@ const generateCall = (scope, decl, _global, _name) => {
1434
1566
 
1435
1567
  if (protoName) {
1436
1568
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1437
- const f = prototypeFuncs[x][protoName];
1438
- if (f) acc[x] = f;
1569
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1439
1570
  return acc;
1440
1571
  }, {});
1441
1572
 
@@ -1444,10 +1575,18 @@ const generateCall = (scope, decl, _global, _name) => {
1444
1575
  // use local for cached i32 length as commonly used
1445
1576
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1446
1577
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1447
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1448
1578
 
1449
1579
  // TODO: long-term, prototypes should be their individual separate funcs
1450
1580
 
1581
+ const rawPointer = [
1582
+ ...generate(scope, target),
1583
+ Opcodes.i32_to_u
1584
+ ];
1585
+
1586
+ const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
1587
+ const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
1588
+
1589
+ let allOptUnused = true;
1451
1590
  let lengthI32CacheUsed = false;
1452
1591
  const protoBC = {};
1453
1592
  for (const x in protoCands) {
@@ -1457,7 +1596,7 @@ const generateCall = (scope, decl, _global, _name) => {
1457
1596
  ...RTArrayUtil.getLength(getPointer),
1458
1597
 
1459
1598
  ...number(TYPES.number, Valtype.i32),
1460
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
1599
+ setLastType(scope)
1461
1600
  ];
1462
1601
  continue;
1463
1602
  }
@@ -1467,6 +1606,7 @@ const generateCall = (scope, decl, _global, _name) => {
1467
1606
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1468
1607
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1469
1608
 
1609
+ let optUnused = false;
1470
1610
  const protoOut = protoFunc(getPointer, {
1471
1611
  getCachedI32: () => {
1472
1612
  lengthI32CacheUsed = true;
@@ -1481,23 +1621,30 @@ const generateCall = (scope, decl, _global, _name) => {
1481
1621
  return makeArray(scope, {
1482
1622
  rawElements: new Array(length)
1483
1623
  }, _global, _name, true, itemType);
1624
+ }, () => {
1625
+ optUnused = true;
1626
+ return unusedValue;
1484
1627
  });
1485
1628
 
1629
+ if (!optUnused) allOptUnused = false;
1630
+
1486
1631
  protoBC[x] = [
1487
- [ Opcodes.block, valtypeBinary ],
1632
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1488
1633
  ...protoOut,
1489
1634
 
1490
1635
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1491
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
1636
+ setLastType(scope),
1492
1637
  [ Opcodes.end ]
1493
1638
  ];
1494
1639
  }
1495
1640
 
1496
- return [
1497
- ...generate(scope, target),
1641
+ // todo: if some cands use optUnused and some don't, we will probably crash
1498
1642
 
1499
- Opcodes.i32_to_u,
1500
- [ Opcodes.local_set, pointerLocal ],
1643
+ return [
1644
+ ...(usePointerCache ? [
1645
+ ...rawPointer,
1646
+ [ Opcodes.local_set, pointerLocal ],
1647
+ ] : []),
1501
1648
 
1502
1649
  ...(!lengthI32CacheUsed ? [] : [
1503
1650
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1509,7 +1656,7 @@ const generateCall = (scope, decl, _global, _name) => {
1509
1656
 
1510
1657
  // TODO: error better
1511
1658
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1512
- }, valtypeBinary),
1659
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1513
1660
  ];
1514
1661
  }
1515
1662
  }
@@ -1548,6 +1695,32 @@ const generateCall = (scope, decl, _global, _name) => {
1548
1695
  idx = -1;
1549
1696
  }
1550
1697
 
1698
+ if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1699
+ const wasmOps = {
1700
+ // pointer, align, offset
1701
+ i32_load8_u: { imms: 2, args: 1 },
1702
+ // pointer, value, align, offset
1703
+ i32_store8: { imms: 2, args: 2 },
1704
+ };
1705
+
1706
+ const opName = name.slice('__Porffor_wasm_'.length);
1707
+
1708
+ if (wasmOps[opName]) {
1709
+ const op = wasmOps[opName];
1710
+
1711
+ const argOut = [];
1712
+ for (let i = 0; i < op.args; i++) argOut.push(...generate(scope, decl.arguments[i]));
1713
+
1714
+ // literals only
1715
+ const imms = decl.arguments.slice(op.args).map(x => x.value);
1716
+
1717
+ return [
1718
+ ...argOut,
1719
+ [ Opcodes[opName], ...imms ]
1720
+ ];
1721
+ }
1722
+ }
1723
+
1551
1724
  if (idx === undefined) {
1552
1725
  if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1553
1726
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
@@ -1556,7 +1729,9 @@ const generateCall = (scope, decl, _global, _name) => {
1556
1729
  const func = funcs.find(x => x.index === idx);
1557
1730
 
1558
1731
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1559
- const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
1732
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1733
+ const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
1734
+ const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1560
1735
 
1561
1736
  let args = decl.arguments;
1562
1737
  if (func && args.length < paramCount) {
@@ -1572,14 +1747,20 @@ const generateCall = (scope, decl, _global, _name) => {
1572
1747
  if (func && func.throws) scope.throws = true;
1573
1748
 
1574
1749
  let out = [];
1575
- for (const arg of args) {
1750
+ for (let i = 0; i < args.length; i++) {
1751
+ const arg = args[i];
1576
1752
  out = out.concat(generate(scope, arg));
1577
- if (userFunc) out = out.concat(getNodeType(scope, arg));
1753
+
1754
+ if (builtinFuncs[name] && builtinFuncs[name].params[i] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1755
+ out.push(Opcodes.i32_to);
1756
+ }
1757
+
1758
+ if (typedParams) out = out.concat(getNodeType(scope, arg));
1578
1759
  }
1579
1760
 
1580
1761
  out.push([ Opcodes.call, idx ]);
1581
1762
 
1582
- if (!userFunc) {
1763
+ if (!typedReturns) {
1583
1764
  // let type;
1584
1765
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1585
1766
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1589,7 +1770,11 @@ const generateCall = (scope, decl, _global, _name) => {
1589
1770
  // ...number(type, Valtype.i32),
1590
1771
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1591
1772
  // );
1592
- } else out.push([ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]);
1773
+ } else out.push(setLastType(scope));
1774
+
1775
+ if (builtinFuncs[name] && builtinFuncs[name].returns[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1776
+ out.push(Opcodes.i32_from);
1777
+ }
1593
1778
 
1594
1779
  return out;
1595
1780
  };
@@ -1614,9 +1799,118 @@ const unhackName = name => {
1614
1799
  return name;
1615
1800
  };
1616
1801
 
1802
+ const knownType = (scope, type) => {
1803
+ if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
1804
+ return type[0][1];
1805
+ }
1806
+
1807
+ if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
1808
+ const idx = type[0][1];
1809
+
1810
+ // type idx = var idx + 1
1811
+ const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
1812
+ if (v.metadata?.type != null) return v.metadata.type;
1813
+ }
1814
+
1815
+ return null;
1816
+ };
1817
+
1818
+ const brTable = (input, bc, returns) => {
1819
+ const out = [];
1820
+ const keys = Object.keys(bc);
1821
+ const count = keys.length;
1822
+
1823
+ if (count === 1) {
1824
+ // return [
1825
+ // ...input,
1826
+ // ...bc[keys[0]]
1827
+ // ];
1828
+ return bc[keys[0]];
1829
+ }
1830
+
1831
+ if (count === 2) {
1832
+ // just use if else
1833
+ const other = keys.find(x => x !== 'default');
1834
+ return [
1835
+ ...input,
1836
+ ...number(other, Valtype.i32),
1837
+ [ Opcodes.i32_eq ],
1838
+ [ Opcodes.if, returns ],
1839
+ ...bc[other],
1840
+ [ Opcodes.else ],
1841
+ ...bc.default,
1842
+ [ Opcodes.end ]
1843
+ ];
1844
+ }
1845
+
1846
+ for (let i = 0; i < count; i++) {
1847
+ if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
1848
+ else out.push([ Opcodes.block, Blocktype.void ]);
1849
+ }
1850
+
1851
+ const nums = keys.filter(x => +x);
1852
+ const offset = Math.min(...nums);
1853
+ const max = Math.max(...nums);
1854
+
1855
+ const table = [];
1856
+ let br = 1;
1857
+
1858
+ for (let i = offset; i <= max; i++) {
1859
+ // if branch for this num, go to that block
1860
+ if (bc[i]) {
1861
+ table.push(br);
1862
+ br++;
1863
+ continue;
1864
+ }
1865
+
1866
+ // else default
1867
+ table.push(0);
1868
+ }
1869
+
1870
+ out.push(
1871
+ [ Opcodes.block, Blocktype.void ],
1872
+ ...input,
1873
+ ...(offset > 0 ? [
1874
+ ...number(offset, Valtype.i32),
1875
+ [ Opcodes.i32_sub ]
1876
+ ] : []),
1877
+ [ Opcodes.br_table, ...encodeVector(table), 0 ]
1878
+ );
1879
+
1880
+ // if you can guess why we sort the wrong way and then reverse
1881
+ // (instead of just sorting the correct way)
1882
+ // dm me and if you are correct and the first person
1883
+ // I will somehow shout you out or something
1884
+ const orderedBc = keys.sort((a, b) => b - a).reverse();
1885
+
1886
+ br = count - 1;
1887
+ for (const x of orderedBc) {
1888
+ out.push(
1889
+ [ Opcodes.end ],
1890
+ ...bc[x],
1891
+ ...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
1892
+ );
1893
+ br--;
1894
+ }
1895
+
1896
+ return [
1897
+ ...out,
1898
+ [ Opcodes.end, 'br table end' ]
1899
+ ];
1900
+ };
1901
+
1617
1902
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1618
- const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1903
+ if (!Prefs.bytestring) delete bc[TYPES._bytestring];
1619
1904
 
1905
+ const known = knownType(scope, type);
1906
+ if (known != null) {
1907
+ return bc[known] ?? bc.default;
1908
+ }
1909
+
1910
+ if (Prefs.typeswitchUseBrtable)
1911
+ return brTable(type, bc, returns);
1912
+
1913
+ const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1620
1914
  const out = [
1621
1915
  ...type,
1622
1916
  [ Opcodes.local_set, tmp ],
@@ -1648,7 +1942,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1648
1942
  return out;
1649
1943
  };
1650
1944
 
1651
- const allocVar = (scope, name, global = false) => {
1945
+ const allocVar = (scope, name, global = false, type = true) => {
1652
1946
  const target = global ? globals : scope.locals;
1653
1947
 
1654
1948
  // already declared
@@ -1662,12 +1956,60 @@ const allocVar = (scope, name, global = false) => {
1662
1956
  let idx = global ? globalInd++ : scope.localInd++;
1663
1957
  target[name] = { idx, type: valtypeBinary };
1664
1958
 
1665
- let typeIdx = global ? globalInd++ : scope.localInd++;
1666
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
1959
+ if (type) {
1960
+ let typeIdx = global ? globalInd++ : scope.localInd++;
1961
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
1962
+ }
1667
1963
 
1668
1964
  return idx;
1669
1965
  };
1670
1966
 
1967
+ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1968
+ const target = global ? globals : scope.locals;
1969
+
1970
+ target[name].metadata ??= {};
1971
+ for (const x in metadata) {
1972
+ if (metadata[x] != null) target[name].metadata[x] = metadata[x];
1973
+ }
1974
+ };
1975
+
1976
+ const typeAnnoToPorfType = x => {
1977
+ if (TYPES[x]) return TYPES[x];
1978
+ if (TYPES['_' + x]) return TYPES['_' + x];
1979
+
1980
+ switch (x) {
1981
+ case 'i32':
1982
+ case 'i64':
1983
+ return TYPES.number;
1984
+ }
1985
+
1986
+ return null;
1987
+ };
1988
+
1989
+ const extractTypeAnnotation = decl => {
1990
+ let a = decl;
1991
+ while (a.typeAnnotation) a = a.typeAnnotation;
1992
+
1993
+ let type, elementType;
1994
+ if (a.typeName) {
1995
+ type = a.typeName.name;
1996
+ } else if (a.type.endsWith('Keyword')) {
1997
+ type = a.type.slice(2, -7).toLowerCase();
1998
+ } else if (a.type === 'TSArrayType') {
1999
+ type = 'array';
2000
+ elementType = extractTypeAnnotation(a.elementType).type;
2001
+ }
2002
+
2003
+ const typeName = type;
2004
+ type = typeAnnoToPorfType(type);
2005
+
2006
+ if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
2007
+
2008
+ // if (decl.name) console.log(decl.name, { type, elementType });
2009
+
2010
+ return { type, typeName, elementType };
2011
+ };
2012
+
1671
2013
  const generateVar = (scope, decl) => {
1672
2014
  let out = [];
1673
2015
 
@@ -1679,6 +2021,8 @@ const generateVar = (scope, decl) => {
1679
2021
  for (const x of decl.declarations) {
1680
2022
  const name = mapName(x.id.name);
1681
2023
 
2024
+ if (!name) return todo('destructuring is not supported yet');
2025
+
1682
2026
  if (x.init && isFuncType(x.init.type)) {
1683
2027
  // hack for let a = function () { ... }
1684
2028
  x.init.id = { name };
@@ -1694,7 +2038,12 @@ const generateVar = (scope, decl) => {
1694
2038
  continue; // always ignore
1695
2039
  }
1696
2040
 
1697
- let idx = allocVar(scope, name, global);
2041
+ let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
2042
+
2043
+ if (typedInput && x.id.typeAnnotation) {
2044
+ addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
2045
+ }
2046
+
1698
2047
  if (x.init) {
1699
2048
  out = out.concat(generate(scope, x.init, global, name));
1700
2049
 
@@ -1812,6 +2161,8 @@ const generateAssign = (scope, decl) => {
1812
2161
  ];
1813
2162
  }
1814
2163
 
2164
+ if (!name) return todo('destructuring is not supported yet');
2165
+
1815
2166
  const [ local, isGlobal ] = lookupName(scope, name);
1816
2167
 
1817
2168
  if (local === undefined) {
@@ -1858,9 +2209,7 @@ const generateAssign = (scope, decl) => {
1858
2209
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1859
2210
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1860
2211
 
1861
- [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ],
1862
- // hack: type is idx+1
1863
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2212
+ ...setType(scope, name, getLastType(scope))
1864
2213
  ];
1865
2214
  }
1866
2215
 
@@ -1871,9 +2220,7 @@ const generateAssign = (scope, decl) => {
1871
2220
 
1872
2221
  // todo: string concat types
1873
2222
 
1874
- // hack: type is idx+1
1875
- ...number(TYPES.number, Valtype.i32),
1876
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2223
+ ...setType(scope, name, TYPES.number)
1877
2224
  ];
1878
2225
  };
1879
2226
 
@@ -1950,6 +2297,8 @@ const generateUnary = (scope, decl) => {
1950
2297
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
1951
2298
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
1952
2299
 
2300
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2301
+
1953
2302
  // object and internal types
1954
2303
  default: makeString(scope, 'object', false, '#typeof_result'),
1955
2304
  });
@@ -2025,7 +2374,7 @@ const generateConditional = (scope, decl) => {
2025
2374
  // note type
2026
2375
  out.push(
2027
2376
  ...getNodeType(scope, decl.consequent),
2028
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2377
+ setLastType(scope)
2029
2378
  );
2030
2379
 
2031
2380
  out.push([ Opcodes.else ]);
@@ -2034,7 +2383,7 @@ const generateConditional = (scope, decl) => {
2034
2383
  // note type
2035
2384
  out.push(
2036
2385
  ...getNodeType(scope, decl.alternate),
2037
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2386
+ setLastType(scope)
2038
2387
  );
2039
2388
 
2040
2389
  out.push([ Opcodes.end ]);
@@ -2055,8 +2404,10 @@ const generateFor = (scope, decl) => {
2055
2404
  out.push([ Opcodes.loop, Blocktype.void ]);
2056
2405
  depth.push('for');
2057
2406
 
2058
- out.push(...generate(scope, decl.test));
2059
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2407
+ if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
2408
+ else out.push(...number(1, Valtype.i32));
2409
+
2410
+ out.push([ Opcodes.if, Blocktype.void ]);
2060
2411
  depth.push('if');
2061
2412
 
2062
2413
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2064,8 +2415,7 @@ const generateFor = (scope, decl) => {
2064
2415
  out.push(...generate(scope, decl.body));
2065
2416
  out.push([ Opcodes.end ]);
2066
2417
 
2067
- out.push(...generate(scope, decl.update));
2068
- depth.pop();
2418
+ if (decl.update) out.push(...generate(scope, decl.update));
2069
2419
 
2070
2420
  out.push([ Opcodes.br, 1 ]);
2071
2421
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2122,7 +2472,13 @@ const generateForOf = (scope, decl) => {
2122
2472
  // setup local for left
2123
2473
  generate(scope, decl.left);
2124
2474
 
2125
- const leftName = decl.left.declarations[0].id.name;
2475
+ let leftName = decl.left.declarations?.[0]?.id?.name;
2476
+ if (!leftName && decl.left.name) {
2477
+ leftName = decl.left.name;
2478
+
2479
+ generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2480
+ }
2481
+
2126
2482
  const [ local, isGlobal ] = lookupName(scope, leftName);
2127
2483
 
2128
2484
  depth.push('block');
@@ -2131,13 +2487,14 @@ const generateForOf = (scope, decl) => {
2131
2487
  // // todo: we should only do this for strings but we don't know at compile-time :(
2132
2488
  // hack: this is naughty and will break things!
2133
2489
  let newOut = number(0, Valtype.f64), newPointer = -1;
2134
- if (pages.hasString) {
2490
+ if (pages.hasAnyString) {
2135
2491
  0, [ newOut, newPointer ] = makeArray(scope, {
2136
2492
  rawElements: new Array(1)
2137
2493
  }, isGlobal, leftName, true, 'i16');
2138
2494
  }
2139
2495
 
2140
2496
  // set type for local
2497
+ // todo: optimize away counter and use end pointer
2141
2498
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2142
2499
  [TYPES._array]: [
2143
2500
  ...setType(scope, leftName, TYPES.number),
@@ -2262,7 +2619,7 @@ const generateThrow = (scope, decl) => {
2262
2619
  // hack: throw new X("...") -> throw "..."
2263
2620
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2264
2621
  constructor = decl.argument.callee.name;
2265
- message = decl.argument.arguments[0].value;
2622
+ message = decl.argument.arguments[0]?.value ?? '';
2266
2623
  }
2267
2624
 
2268
2625
  if (tags.length === 0) tags.push({
@@ -2274,6 +2631,9 @@ const generateThrow = (scope, decl) => {
2274
2631
  let exceptId = exceptions.push({ constructor, message }) - 1;
2275
2632
  let tagIdx = tags[0].idx;
2276
2633
 
2634
+ scope.exceptions ??= [];
2635
+ scope.exceptions.push(exceptId);
2636
+
2277
2637
  // todo: write a description of how this works lol
2278
2638
 
2279
2639
  return [
@@ -2318,25 +2678,31 @@ const generateAssignPat = (scope, decl) => {
2318
2678
  };
2319
2679
 
2320
2680
  let pages = new Map();
2321
- const allocPage = (reason, type) => {
2681
+ const allocPage = (scope, reason, type) => {
2322
2682
  if (pages.has(reason)) return pages.get(reason).ind;
2323
2683
 
2324
2684
  if (reason.startsWith('array:')) pages.hasArray = true;
2325
2685
  if (reason.startsWith('string:')) pages.hasString = true;
2686
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
2687
+ if (reason.includes('string:')) pages.hasAnyString = true;
2326
2688
 
2327
2689
  const ind = pages.size;
2328
2690
  pages.set(reason, { ind, type });
2329
2691
 
2330
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2692
+ scope.pages ??= new Map();
2693
+ scope.pages.set(reason, { ind, type });
2694
+
2695
+ if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2331
2696
 
2332
2697
  return ind;
2333
2698
  };
2334
2699
 
2700
+ // todo: add scope.pages
2335
2701
  const freePage = reason => {
2336
2702
  const { ind } = pages.get(reason);
2337
2703
  pages.delete(reason);
2338
2704
 
2339
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2705
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2340
2706
 
2341
2707
  return ind;
2342
2708
  };
@@ -2356,25 +2722,34 @@ const StoreOps = {
2356
2722
  f64: Opcodes.f64_store,
2357
2723
 
2358
2724
  // expects i32 input!
2359
- i16: Opcodes.i32_store16
2725
+ i8: Opcodes.i32_store8,
2726
+ i16: Opcodes.i32_store16,
2360
2727
  };
2361
2728
 
2362
2729
  let data = [];
2363
2730
 
2364
- const compileBytes = (val, itemType, signed = true) => {
2731
+ const compileBytes = (val, itemType) => {
2365
2732
  // todo: this is a mess and needs confirming / ????
2366
2733
  switch (itemType) {
2367
2734
  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));
2735
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
2736
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
2737
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
2738
+ // todo: i64
2373
2739
 
2374
2740
  case 'f64': return ieee754_binary64(val);
2375
2741
  }
2376
2742
  };
2377
2743
 
2744
+ const getAllocType = itemType => {
2745
+ switch (itemType) {
2746
+ case 'i8': return 'bytestring';
2747
+ case 'i16': return 'string';
2748
+
2749
+ default: return 'array';
2750
+ }
2751
+ };
2752
+
2378
2753
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2379
2754
  const out = [];
2380
2755
 
@@ -2384,7 +2759,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2384
2759
 
2385
2760
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2386
2761
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2387
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
2762
+ arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2388
2763
  }
2389
2764
 
2390
2765
  const pointer = arrays.get(name);
@@ -2395,19 +2770,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2395
2770
  const valtype = itemTypeToValtype[itemType];
2396
2771
  const length = elements.length;
2397
2772
 
2398
- if (firstAssign && useRawElements) {
2399
- let bytes = compileBytes(length, 'i32');
2773
+ if (firstAssign && useRawElements && !Prefs.noData) {
2774
+ // if length is 0 memory/data will just be 0000... anyway
2775
+ if (length !== 0) {
2776
+ let bytes = compileBytes(length, 'i32');
2400
2777
 
2401
- if (!initEmpty) for (let i = 0; i < length; i++) {
2402
- if (elements[i] == null) continue;
2778
+ if (!initEmpty) for (let i = 0; i < length; i++) {
2779
+ if (elements[i] == null) continue;
2403
2780
 
2404
- bytes.push(...compileBytes(elements[i], itemType));
2405
- }
2781
+ bytes.push(...compileBytes(elements[i], itemType));
2782
+ }
2406
2783
 
2407
- data.push({
2408
- offset: pointer,
2409
- bytes
2410
- });
2784
+ const ind = data.push({
2785
+ offset: pointer,
2786
+ bytes
2787
+ }) - 1;
2788
+
2789
+ scope.data ??= [];
2790
+ scope.data.push(ind);
2791
+ }
2411
2792
 
2412
2793
  // local value as pointer
2413
2794
  out.push(...number(pointer));
@@ -2430,7 +2811,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2430
2811
  out.push(
2431
2812
  ...number(0, Valtype.i32),
2432
2813
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2433
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2814
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2434
2815
  );
2435
2816
  }
2436
2817
 
@@ -2440,15 +2821,31 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2440
2821
  return [ out, pointer ];
2441
2822
  };
2442
2823
 
2443
- const makeString = (scope, str, global = false, name = '$undeclared') => {
2824
+ const byteStringable = str => {
2825
+ if (!Prefs.bytestring) return false;
2826
+
2827
+ for (let i = 0; i < str.length; i++) {
2828
+ if (str.charCodeAt(i) > 0xFF) return false;
2829
+ }
2830
+
2831
+ return true;
2832
+ };
2833
+
2834
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2444
2835
  const rawElements = new Array(str.length);
2836
+ let byteStringable = Prefs.bytestring;
2445
2837
  for (let i = 0; i < str.length; i++) {
2446
- rawElements[i] = str.charCodeAt(i);
2838
+ const c = str.charCodeAt(i);
2839
+ rawElements[i] = c;
2840
+
2841
+ if (byteStringable && c > 0xFF) byteStringable = false;
2447
2842
  }
2448
2843
 
2844
+ if (byteStringable && forceBytestring === false) byteStringable = false;
2845
+
2449
2846
  return makeArray(scope, {
2450
2847
  rawElements
2451
- }, global, name, false, 'i16')[0];
2848
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2452
2849
  };
2453
2850
 
2454
2851
  let arrays = new Map();
@@ -2476,10 +2873,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2476
2873
  ];
2477
2874
  }
2478
2875
 
2876
+ const object = generate(scope, decl.object);
2877
+ const property = generate(scope, decl.property);
2878
+
2479
2879
  // // todo: we should only do this for strings but we don't know at compile-time :(
2480
2880
  // hack: this is naughty and will break things!
2481
- let newOut = number(0, Valtype.f64), newPointer = -1;
2482
- if (pages.hasString) {
2881
+ let newOut = number(0, valtypeBinary), newPointer = -1;
2882
+ if (pages.hasAnyString) {
2483
2883
  0, [ newOut, newPointer ] = makeArray(scope, {
2484
2884
  rawElements: new Array(1)
2485
2885
  }, _global, _name, true, 'i16');
@@ -2488,7 +2888,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2488
2888
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2489
2889
  [TYPES._array]: [
2490
2890
  // get index as valtype
2491
- ...generate(scope, decl.property),
2891
+ ...property,
2492
2892
 
2493
2893
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2494
2894
  Opcodes.i32_to_u,
@@ -2496,7 +2896,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2496
2896
  [ Opcodes.i32_mul ],
2497
2897
 
2498
2898
  ...(aotPointer ? [] : [
2499
- ...generate(scope, decl.object),
2899
+ ...object,
2500
2900
  Opcodes.i32_to_u,
2501
2901
  [ Opcodes.i32_add ]
2502
2902
  ]),
@@ -2505,7 +2905,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2505
2905
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2506
2906
 
2507
2907
  ...number(TYPES.number, Valtype.i32),
2508
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2908
+ setLastType(scope)
2509
2909
  ],
2510
2910
 
2511
2911
  [TYPES.string]: [
@@ -2515,14 +2915,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2515
2915
 
2516
2916
  ...number(0, Valtype.i32), // base 0 for store later
2517
2917
 
2518
- ...generate(scope, decl.property),
2519
-
2918
+ ...property,
2520
2919
  Opcodes.i32_to_u,
2920
+
2521
2921
  ...number(ValtypeSize.i16, Valtype.i32),
2522
2922
  [ Opcodes.i32_mul ],
2523
2923
 
2524
2924
  ...(aotPointer ? [] : [
2525
- ...generate(scope, decl.object),
2925
+ ...object,
2526
2926
  Opcodes.i32_to_u,
2527
2927
  [ Opcodes.i32_add ]
2528
2928
  ]),
@@ -2537,10 +2937,38 @@ export const generateMember = (scope, decl, _global, _name) => {
2537
2937
  ...number(newPointer),
2538
2938
 
2539
2939
  ...number(TYPES.string, Valtype.i32),
2540
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2940
+ setLastType(scope)
2941
+ ],
2942
+ [TYPES._bytestring]: [
2943
+ // setup new/out array
2944
+ ...newOut,
2945
+ [ Opcodes.drop ],
2946
+
2947
+ ...number(0, Valtype.i32), // base 0 for store later
2948
+
2949
+ ...property,
2950
+ Opcodes.i32_to_u,
2951
+
2952
+ ...(aotPointer ? [] : [
2953
+ ...object,
2954
+ Opcodes.i32_to_u,
2955
+ [ Opcodes.i32_add ]
2956
+ ]),
2957
+
2958
+ // load current string ind {arg}
2959
+ [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2960
+
2961
+ // store to new string ind 0
2962
+ [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2963
+
2964
+ // return new string (page)
2965
+ ...number(newPointer),
2966
+
2967
+ ...number(TYPES._bytestring, Valtype.i32),
2968
+ setLastType(scope)
2541
2969
  ],
2542
2970
 
2543
- default: [ [ Opcodes.unreachable ] ]
2971
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
2544
2972
  });
2545
2973
  };
2546
2974
 
@@ -2557,13 +2985,16 @@ const objectHack = node => {
2557
2985
  // if object is not identifier or another member exp, give up
2558
2986
  if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
2559
2987
 
2560
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
2988
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2561
2989
 
2562
2990
  // if .length, give up (hack within a hack!)
2563
2991
  if (node.property.name === 'length') return node;
2564
2992
 
2993
+ // no object name, give up
2994
+ if (!objectName) return node;
2995
+
2565
2996
  const name = '__' + objectName + '_' + node.property.name;
2566
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2997
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2567
2998
 
2568
2999
  return {
2569
3000
  type: 'Identifier',
@@ -2586,7 +3017,7 @@ const generateFunc = (scope, decl) => {
2586
3017
  if (decl.generator) return todo('generator functions are not supported');
2587
3018
 
2588
3019
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2589
- const params = decl.params?.map(x => x.name) ?? [];
3020
+ const params = decl.params ?? [];
2590
3021
 
2591
3022
  // const innerScope = { ...scope };
2592
3023
  // TODO: share scope/locals between !!!
@@ -2600,7 +3031,11 @@ const generateFunc = (scope, decl) => {
2600
3031
  };
2601
3032
 
2602
3033
  for (let i = 0; i < params.length; i++) {
2603
- allocVar(innerScope, params[i], false);
3034
+ allocVar(innerScope, params[i].name, false);
3035
+
3036
+ if (typedInput && params[i].typeAnnotation) {
3037
+ addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
3038
+ }
2604
3039
  }
2605
3040
 
2606
3041
  let body = objectHack(decl.body);
@@ -2616,13 +3051,13 @@ const generateFunc = (scope, decl) => {
2616
3051
  const func = {
2617
3052
  name,
2618
3053
  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++
3054
+ index: currentFuncIndex++,
3055
+ ...innerScope
2623
3056
  };
2624
3057
  funcIndex[name] = func.index;
2625
3058
 
3059
+ if (name === 'main') func.gotLastType = true;
3060
+
2626
3061
  // quick hack fixes
2627
3062
  for (const inst of wasm) {
2628
3063
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -2639,117 +3074,6 @@ const generateFunc = (scope, decl) => {
2639
3074
  );
2640
3075
  }
2641
3076
 
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
3077
  func.wasm = wasm;
2754
3078
 
2755
3079
  funcs.push(func);
@@ -2768,6 +3092,16 @@ const generateCode = (scope, decl) => {
2768
3092
  };
2769
3093
 
2770
3094
  const internalConstrs = {
3095
+ Boolean: {
3096
+ generate: (scope, decl) => {
3097
+ if (decl.arguments.length === 0) return number(0);
3098
+
3099
+ // should generate/run all args
3100
+ return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3101
+ },
3102
+ type: TYPES.boolean
3103
+ },
3104
+
2771
3105
  Array: {
2772
3106
  generate: (scope, decl, global, name) => {
2773
3107
  // new Array(i0, i1, ...)
@@ -2889,7 +3223,7 @@ export default program => {
2889
3223
  body: program.body
2890
3224
  };
2891
3225
 
2892
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3226
+ if (Prefs.astLog) console.log(program.body.body);
2893
3227
 
2894
3228
  generateFunc(scope, program);
2895
3229