porffor 0.2.0-536e463 → 0.2.0-5ac7ea0

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