porffor 0.2.0-5e33105 → 0.2.0-6aff0fa

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.
@@ -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,31 +201,40 @@ 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:
217
- if (decl.type.startsWith('TS')) {
218
- // ignore typescript nodes
235
+ // ignore typescript nodes
236
+ if (decl.type.startsWith('TS') ||
237
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
219
238
  return [];
220
239
  }
221
240
 
@@ -269,25 +288,28 @@ const generateIdent = (scope, decl) => {
269
288
  const name = mapName(rawName);
270
289
  let local = scope.locals[rawName];
271
290
 
272
- if (builtinVars[name]) {
291
+ if (Object.hasOwn(builtinVars, name)) {
273
292
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
274
- return builtinVars[name];
293
+
294
+ let wasm = builtinVars[name];
295
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
296
+ return wasm;
275
297
  }
276
298
 
277
- if (builtinFuncs[name] || internalConstrs[name]) {
299
+ if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
278
300
  // todo: return an actual something
279
301
  return number(1);
280
302
  }
281
303
 
282
- if (local === undefined) {
304
+ if (local?.idx === undefined) {
283
305
  // no local var with name
284
- if (importedFuncs.hasOwnProperty(name)) return number(importedFuncs[name]);
285
- 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]);
286
308
 
287
- if (globals[name] !== undefined) return [ [ Opcodes.global_get, globals[name].idx ] ];
309
+ if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
288
310
  }
289
311
 
290
- if (local === undefined && rawName.startsWith('__')) {
312
+ if (local?.idx === undefined && rawName.startsWith('__')) {
291
313
  // return undefined if unknown key in already known var
292
314
  let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
293
315
  if (parent.includes('_')) parent = '__' + parent;
@@ -296,7 +318,7 @@ const generateIdent = (scope, decl) => {
296
318
  if (!parentLookup[1]) return number(UNDEFINED);
297
319
  }
298
320
 
299
- 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);
300
322
 
301
323
  return [ [ Opcodes.local_get, local.idx ] ];
302
324
  };
@@ -404,9 +426,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
404
426
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
405
427
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
406
428
 
407
- const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
408
- if (aotWFA) addVarMeta(name, { wellFormed: undefined });
409
-
410
429
  if (assign) {
411
430
  const pointer = arrays.get(name ?? '$undeclared');
412
431
 
@@ -644,11 +663,12 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
644
663
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
645
664
  ];
646
665
 
647
- 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);
648
668
 
649
669
  const def = [
650
670
  // if value != 0
651
- [ Opcodes.local_get, tmp ],
671
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
652
672
 
653
673
  // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
654
674
  ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
@@ -660,7 +680,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
660
680
 
661
681
  return [
662
682
  ...wasm,
663
- [ Opcodes.local_set, tmp ],
683
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
664
684
 
665
685
  ...typeSwitch(scope, type, {
666
686
  // [TYPES.number]: def,
@@ -669,7 +689,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
669
689
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
670
690
  ],
671
691
  [TYPES.string]: [
672
- [ Opcodes.local_get, tmp ],
692
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
673
693
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
674
694
 
675
695
  // get length
@@ -680,16 +700,27 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
680
700
  [ Opcodes.i32_eqz ], */
681
701
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
682
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
+ ],
683
712
  default: def
684
713
  }, intOut ? Valtype.i32 : valtypeBinary)
685
714
  ];
686
715
  };
687
716
 
688
717
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
689
- 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
+
690
721
  return [
691
722
  ...wasm,
692
- [ Opcodes.local_set, tmp ],
723
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
693
724
 
694
725
  ...typeSwitch(scope, type, {
695
726
  [TYPES._array]: [
@@ -697,7 +728,18 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
697
728
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
698
729
  ],
699
730
  [TYPES.string]: [
700
- [ 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 ] ]),
701
743
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
702
744
 
703
745
  // get length
@@ -709,7 +751,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
709
751
  ],
710
752
  default: [
711
753
  // if value == 0
712
- [ Opcodes.local_get, tmp ],
754
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
713
755
 
714
756
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
715
757
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -719,10 +761,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
719
761
  };
720
762
 
721
763
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
722
- 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
+
723
767
  return [
724
768
  ...wasm,
725
- [ Opcodes.local_set, tmp ],
769
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
726
770
 
727
771
  ...typeSwitch(scope, type, {
728
772
  [TYPES.undefined]: [
@@ -731,7 +775,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
731
775
  ],
732
776
  [TYPES.object]: [
733
777
  // object, null if == 0
734
- [ Opcodes.local_get, tmp ],
778
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
735
779
 
736
780
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
737
781
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -760,11 +804,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
760
804
  return performLogicOp(scope, op, left, right, leftType, rightType);
761
805
  }
762
806
 
807
+ const knownLeft = knownType(scope, leftType);
808
+ const knownRight = knownType(scope, rightType);
809
+
763
810
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
764
811
  const strictOp = op === '===' || op === '!==';
765
812
 
766
813
  const startOut = [], endOut = [];
767
- const finalise = out => startOut.concat(out, endOut);
814
+ const finalize = out => startOut.concat(out, endOut);
768
815
 
769
816
  // if strict (in)equal check types match
770
817
  if (strictOp) {
@@ -809,31 +856,32 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
809
856
  // todo: if equality op and an operand is undefined, return false
810
857
  // todo: niche null hell with 0
811
858
 
812
- // if (leftType === TYPES.string || rightType === TYPES.string) {
813
- // if (op === '+') {
814
- // // string concat (a + b)
815
- // return finalise(concatStrings(scope, left, right, _global, _name, assign));
816
- // }
817
-
818
- // // not an equality op, NaN
819
- // if (!eqOp) return finalise(number(NaN));
820
-
821
- // // else leave bool ops
822
- // // todo: convert string to number if string and number/bool
823
- // // todo: string (>|>=|<|<=) string
824
-
825
- // // string comparison
826
- // if (op === '===' || op === '==') {
827
- // return finalise(compareStrings(scope, left, right));
828
- // }
829
-
830
- // if (op === '!==' || op === '!=') {
831
- // return finalise([
832
- // ...compareStrings(scope, left, right),
833
- // [ Opcodes.i32_eqz ]
834
- // ]);
835
- // }
836
- // }
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
+ }
837
885
 
838
886
  let ops = operatorOpcode[valtype][op];
839
887
 
@@ -843,7 +891,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
843
891
  includeBuiltin(scope, builtinName);
844
892
  const idx = funcIndex[builtinName];
845
893
 
846
- return finalise([
894
+ return finalize([
847
895
  ...left,
848
896
  ...right,
849
897
  [ Opcodes.call, idx ]
@@ -858,9 +906,6 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
858
906
  let tmpLeft, tmpRight;
859
907
  // if equal op, check if strings for compareStrings
860
908
  if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
861
- const knownLeft = knownType(scope, leftType);
862
- const knownRight = knownType(scope, rightType);
863
-
864
909
  // todo: intelligent partial skip later
865
910
  // if neither known are string, stop this madness
866
911
  if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
@@ -900,7 +945,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
900
945
  [ Opcodes.i32_or ],
901
946
  [ Opcodes.if, Blocktype.void ],
902
947
  ...number(0, Valtype.i32),
903
- [ Opcodes.br, 1 ],
948
+ [ Opcodes.br, 2 ],
904
949
  [ Opcodes.end ],
905
950
 
906
951
  ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
@@ -918,7 +963,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
918
963
  // }
919
964
  })();
920
965
 
921
- return finalise([
966
+ return finalize([
922
967
  ...left,
923
968
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
924
969
  ...right,
@@ -935,7 +980,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
935
980
  return out;
936
981
  };
937
982
 
938
- 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 = [] }) => {
939
999
  const existing = funcs.find(x => x.name === name);
940
1000
  if (existing) return existing;
941
1001
 
@@ -947,6 +1007,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
947
1007
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
948
1008
  }
949
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
+
950
1018
  let baseGlobalIdx, i = 0;
951
1019
  for (const type of globalTypes) {
952
1020
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -1027,7 +1095,8 @@ const TYPES = {
1027
1095
 
1028
1096
  // these are not "typeof" types but tracked internally
1029
1097
  _array: 0x10,
1030
- _regexp: 0x11
1098
+ _regexp: 0x11,
1099
+ _bytestring: 0x12
1031
1100
  };
1032
1101
 
1033
1102
  const TYPE_NAMES = {
@@ -1041,13 +1110,17 @@ const TYPE_NAMES = {
1041
1110
  [TYPES.bigint]: 'BigInt',
1042
1111
 
1043
1112
  [TYPES._array]: 'Array',
1044
- [TYPES._regexp]: 'RegExp'
1113
+ [TYPES._regexp]: 'RegExp',
1114
+ [TYPES._bytestring]: 'ByteString'
1045
1115
  };
1046
1116
 
1047
1117
  const getType = (scope, _name) => {
1048
1118
  const name = mapName(_name);
1049
1119
 
1120
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1050
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);
1051
1124
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1052
1125
 
1053
1126
  let type = TYPES.undefined;
@@ -1094,6 +1167,8 @@ const getNodeType = (scope, node) => {
1094
1167
  if (node.type === 'Literal') {
1095
1168
  if (node.regex) return TYPES._regexp;
1096
1169
 
1170
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1171
+
1097
1172
  return TYPES[typeof node.value];
1098
1173
  }
1099
1174
 
@@ -1107,6 +1182,15 @@ const getNodeType = (scope, node) => {
1107
1182
 
1108
1183
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1109
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
+
1110
1194
  const func = funcs.find(x => x.name === name);
1111
1195
 
1112
1196
  if (func) {
@@ -1114,7 +1198,7 @@ const getNodeType = (scope, node) => {
1114
1198
  if (func.returnType) return func.returnType;
1115
1199
  }
1116
1200
 
1117
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1201
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1118
1202
  if (internalConstrs[name]) return internalConstrs[name].type;
1119
1203
 
1120
1204
  // check if this is a prototype function
@@ -1125,10 +1209,15 @@ const getNodeType = (scope, node) => {
1125
1209
  const spl = name.slice(2).split('_');
1126
1210
 
1127
1211
  const func = spl[spl.length - 1];
1128
- const protoFuncs = Object.values(prototypeFuncs).filter(x => x[func] != null);
1212
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
1129
1213
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1130
1214
  }
1131
1215
 
1216
+ if (name.startsWith('__Porffor_wasm_')) {
1217
+ // todo: return undefined for non-returning ops
1218
+ return TYPES.number;
1219
+ }
1220
+
1132
1221
  if (scope.locals['#last_type']) return [ getLastType(scope) ];
1133
1222
 
1134
1223
  // presume
@@ -1177,6 +1266,14 @@ const getNodeType = (scope, node) => {
1177
1266
 
1178
1267
  if (node.type === 'BinaryExpression') {
1179
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
+
1180
1277
  return TYPES.number;
1181
1278
 
1182
1279
  // todo: string concat types
@@ -1201,7 +1298,7 @@ const getNodeType = (scope, node) => {
1201
1298
  if (node.operator === '!') return TYPES.boolean;
1202
1299
  if (node.operator === 'void') return TYPES.undefined;
1203
1300
  if (node.operator === 'delete') return TYPES.boolean;
1204
- if (node.operator === 'typeof') return TYPES.string;
1301
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
1205
1302
 
1206
1303
  return TYPES.number;
1207
1304
  }
@@ -1210,7 +1307,13 @@ const getNodeType = (scope, node) => {
1210
1307
  // hack: if something.length, number type
1211
1308
  if (node.property.name === 'length') return TYPES.number;
1212
1309
 
1213
- // 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
1214
1317
  return TYPES.number;
1215
1318
  }
1216
1319
 
@@ -1227,28 +1330,11 @@ const getNodeType = (scope, node) => {
1227
1330
  return ret;
1228
1331
  };
1229
1332
 
1230
- const toString = (scope, wasm, type) => {
1231
- const tmp = localTmp(scope, '#tostring_tmp');
1232
- return [
1233
- ...wasm,
1234
- [ Opcodes.local_set, tmp ],
1235
-
1236
- ...typeSwitch(scope, type, {
1237
- [TYPES.string]: [
1238
- [ Opcodes.local_get, tmp ]
1239
- ],
1240
- [TYPES.undefined]: [
1241
- // [ Opcodes.]
1242
- ]
1243
- })
1244
- ]
1245
- };
1246
-
1247
1333
  const generateLiteral = (scope, decl, global, name) => {
1248
1334
  if (decl.value === null) return number(NULL);
1249
1335
 
1336
+ // hack: just return 1 for regex literals
1250
1337
  if (decl.regex) {
1251
- scope.regex[name] = decl.regex;
1252
1338
  return number(1);
1253
1339
  }
1254
1340
 
@@ -1261,16 +1347,7 @@ const generateLiteral = (scope, decl, global, name) => {
1261
1347
  return number(decl.value ? 1 : 0);
1262
1348
 
1263
1349
  case 'string':
1264
- const str = decl.value;
1265
- const rawElements = new Array(str.length);
1266
- let j = 0;
1267
- for (let i = 0; i < str.length; i++) {
1268
- rawElements[i] = str.charCodeAt(i);
1269
- }
1270
-
1271
- return makeArray(scope, {
1272
- rawElements
1273
- }, global, name, false, 'i16')[0];
1350
+ return makeString(scope, decl.value, global, name);
1274
1351
 
1275
1352
  default:
1276
1353
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -1290,10 +1367,10 @@ const countLeftover = wasm => {
1290
1367
  if (inst[0] === Opcodes.end) depth--;
1291
1368
 
1292
1369
  if (depth === 0)
1293
- if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1294
- 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)) {}
1295
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++;
1296
- 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;
1297
1374
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1298
1375
  else if (inst[0] === Opcodes.return) count = 0;
1299
1376
  else if (inst[0] === Opcodes.call) {
@@ -1319,7 +1396,7 @@ const disposeLeftover = wasm => {
1319
1396
  const generateExp = (scope, decl) => {
1320
1397
  const expression = decl.expression;
1321
1398
 
1322
- const out = generate(scope, expression);
1399
+ const out = generate(scope, expression, undefined, undefined, true);
1323
1400
  disposeLeftover(out);
1324
1401
 
1325
1402
  return out;
@@ -1377,7 +1454,7 @@ const RTArrayUtil = {
1377
1454
  ]
1378
1455
  };
1379
1456
 
1380
- const generateCall = (scope, decl, _global, _name) => {
1457
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1381
1458
  /* const callee = decl.callee;
1382
1459
  const args = decl.arguments;
1383
1460
 
@@ -1443,8 +1520,8 @@ const generateCall = (scope, decl, _global, _name) => {
1443
1520
  // literal.func()
1444
1521
  if (!name && decl.callee.type === 'MemberExpression') {
1445
1522
  // megahack for /regex/.func()
1446
- if (decl.callee.object.regex) {
1447
- const funcName = decl.callee.property.name;
1523
+ const funcName = decl.callee.property.name;
1524
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1448
1525
  const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1449
1526
 
1450
1527
  funcIndex[func.name] = func.index;
@@ -1486,8 +1563,7 @@ const generateCall = (scope, decl, _global, _name) => {
1486
1563
 
1487
1564
  if (protoName) {
1488
1565
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1489
- const f = prototypeFuncs[x][protoName];
1490
- if (f) acc[x] = f;
1566
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1491
1567
  return acc;
1492
1568
  }, {});
1493
1569
 
@@ -1496,10 +1572,18 @@ const generateCall = (scope, decl, _global, _name) => {
1496
1572
  // use local for cached i32 length as commonly used
1497
1573
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1498
1574
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1499
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1500
1575
 
1501
1576
  // TODO: long-term, prototypes should be their individual separate funcs
1502
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;
1503
1587
  let lengthI32CacheUsed = false;
1504
1588
  const protoBC = {};
1505
1589
  for (const x in protoCands) {
@@ -1519,6 +1603,7 @@ const generateCall = (scope, decl, _global, _name) => {
1519
1603
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1520
1604
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1521
1605
 
1606
+ let optUnused = false;
1522
1607
  const protoOut = protoFunc(getPointer, {
1523
1608
  getCachedI32: () => {
1524
1609
  lengthI32CacheUsed = true;
@@ -1533,10 +1618,15 @@ const generateCall = (scope, decl, _global, _name) => {
1533
1618
  return makeArray(scope, {
1534
1619
  rawElements: new Array(length)
1535
1620
  }, _global, _name, true, itemType);
1621
+ }, () => {
1622
+ optUnused = true;
1623
+ return unusedValue;
1536
1624
  });
1537
1625
 
1626
+ if (!optUnused) allOptUnused = false;
1627
+
1538
1628
  protoBC[x] = [
1539
- [ Opcodes.block, valtypeBinary ],
1629
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1540
1630
  ...protoOut,
1541
1631
 
1542
1632
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
@@ -1545,11 +1635,13 @@ const generateCall = (scope, decl, _global, _name) => {
1545
1635
  ];
1546
1636
  }
1547
1637
 
1548
- return [
1549
- ...generate(scope, target),
1638
+ // todo: if some cands use optUnused and some don't, we will probably crash
1550
1639
 
1551
- Opcodes.i32_to_u,
1552
- [ Opcodes.local_set, pointerLocal ],
1640
+ return [
1641
+ ...(usePointerCache ? [
1642
+ ...rawPointer,
1643
+ [ Opcodes.local_set, pointerLocal ],
1644
+ ] : []),
1553
1645
 
1554
1646
  ...(!lengthI32CacheUsed ? [] : [
1555
1647
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1561,7 +1653,7 @@ const generateCall = (scope, decl, _global, _name) => {
1561
1653
 
1562
1654
  // TODO: error better
1563
1655
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1564
- }, valtypeBinary),
1656
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1565
1657
  ];
1566
1658
  }
1567
1659
  }
@@ -1600,6 +1692,32 @@ const generateCall = (scope, decl, _global, _name) => {
1600
1692
  idx = -1;
1601
1693
  }
1602
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
+
1603
1721
  if (idx === undefined) {
1604
1722
  if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1605
1723
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
@@ -1608,7 +1726,9 @@ const generateCall = (scope, decl, _global, _name) => {
1608
1726
  const func = funcs.find(x => x.index === idx);
1609
1727
 
1610
1728
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1611
- 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);
1612
1732
 
1613
1733
  let args = decl.arguments;
1614
1734
  if (func && args.length < paramCount) {
@@ -1624,14 +1744,20 @@ const generateCall = (scope, decl, _global, _name) => {
1624
1744
  if (func && func.throws) scope.throws = true;
1625
1745
 
1626
1746
  let out = [];
1627
- for (const arg of args) {
1747
+ for (let i = 0; i < args.length; i++) {
1748
+ const arg = args[i];
1628
1749
  out = out.concat(generate(scope, arg));
1629
- 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));
1630
1756
  }
1631
1757
 
1632
1758
  out.push([ Opcodes.call, idx ]);
1633
1759
 
1634
- if (!userFunc) {
1760
+ if (!typedReturns) {
1635
1761
  // let type;
1636
1762
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1637
1763
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1643,6 +1769,10 @@ const generateCall = (scope, decl, _global, _name) => {
1643
1769
  // );
1644
1770
  } else out.push(setLastType(scope));
1645
1771
 
1772
+ if (builtinFuncs[name] && builtinFuncs[name].returns[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1773
+ out.push(Opcodes.i32_from);
1774
+ }
1775
+
1646
1776
  return out;
1647
1777
  };
1648
1778
 
@@ -1767,12 +1897,14 @@ const brTable = (input, bc, returns) => {
1767
1897
  };
1768
1898
 
1769
1899
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1900
+ if (!Prefs.bytestring) delete bc[TYPES._bytestring];
1901
+
1770
1902
  const known = knownType(scope, type);
1771
1903
  if (known != null) {
1772
1904
  return bc[known] ?? bc.default;
1773
1905
  }
1774
1906
 
1775
- if (process.argv.includes('-typeswitch-use-brtable'))
1907
+ if (Prefs.typeswitchUseBrtable)
1776
1908
  return brTable(type, bc, returns);
1777
1909
 
1778
1910
  const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
@@ -1807,7 +1939,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1807
1939
  return out;
1808
1940
  };
1809
1941
 
1810
- const allocVar = (scope, name, global = false) => {
1942
+ const allocVar = (scope, name, global = false, type = true) => {
1811
1943
  const target = global ? globals : scope.locals;
1812
1944
 
1813
1945
  // already declared
@@ -1821,8 +1953,10 @@ const allocVar = (scope, name, global = false) => {
1821
1953
  let idx = global ? globalInd++ : scope.localInd++;
1822
1954
  target[name] = { idx, type: valtypeBinary };
1823
1955
 
1824
- let typeIdx = global ? globalInd++ : scope.localInd++;
1825
- 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
+ }
1826
1960
 
1827
1961
  return idx;
1828
1962
  };
@@ -1842,6 +1976,7 @@ const typeAnnoToPorfType = x => {
1842
1976
 
1843
1977
  switch (x) {
1844
1978
  case 'i32':
1979
+ case 'i64':
1845
1980
  return TYPES.number;
1846
1981
  }
1847
1982
 
@@ -1865,6 +2000,8 @@ const extractTypeAnnotation = decl => {
1865
2000
  const typeName = type;
1866
2001
  type = typeAnnoToPorfType(type);
1867
2002
 
2003
+ if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
2004
+
1868
2005
  // if (decl.name) console.log(decl.name, { type, elementType });
1869
2006
 
1870
2007
  return { type, typeName, elementType };
@@ -1881,6 +2018,8 @@ const generateVar = (scope, decl) => {
1881
2018
  for (const x of decl.declarations) {
1882
2019
  const name = mapName(x.id.name);
1883
2020
 
2021
+ if (!name) return todo('destructuring is not supported yet');
2022
+
1884
2023
  if (x.init && isFuncType(x.init.type)) {
1885
2024
  // hack for let a = function () { ... }
1886
2025
  x.init.id = { name };
@@ -1896,7 +2035,7 @@ const generateVar = (scope, decl) => {
1896
2035
  continue; // always ignore
1897
2036
  }
1898
2037
 
1899
- let idx = allocVar(scope, name, global);
2038
+ let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
1900
2039
 
1901
2040
  if (typedInput && x.id.typeAnnotation) {
1902
2041
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
@@ -2019,6 +2158,8 @@ const generateAssign = (scope, decl) => {
2019
2158
  ];
2020
2159
  }
2021
2160
 
2161
+ if (!name) return todo('destructuring is not supported yet');
2162
+
2022
2163
  const [ local, isGlobal ] = lookupName(scope, name);
2023
2164
 
2024
2165
  if (local === undefined) {
@@ -2065,9 +2206,7 @@ const generateAssign = (scope, decl) => {
2065
2206
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
2066
2207
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2067
2208
 
2068
- getLastType(scope),
2069
- // hack: type is idx+1
2070
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2209
+ ...setType(scope, name, getLastType(scope))
2071
2210
  ];
2072
2211
  }
2073
2212
 
@@ -2078,9 +2217,7 @@ const generateAssign = (scope, decl) => {
2078
2217
 
2079
2218
  // todo: string concat types
2080
2219
 
2081
- // hack: type is idx+1
2082
- ...number(TYPES.number, Valtype.i32),
2083
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2220
+ ...setType(scope, name, TYPES.number)
2084
2221
  ];
2085
2222
  };
2086
2223
 
@@ -2157,6 +2294,8 @@ const generateUnary = (scope, decl) => {
2157
2294
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2158
2295
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2159
2296
 
2297
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2298
+
2160
2299
  // object and internal types
2161
2300
  default: makeString(scope, 'object', false, '#typeof_result'),
2162
2301
  });
@@ -2262,8 +2401,10 @@ const generateFor = (scope, decl) => {
2262
2401
  out.push([ Opcodes.loop, Blocktype.void ]);
2263
2402
  depth.push('for');
2264
2403
 
2265
- out.push(...generate(scope, decl.test));
2266
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2404
+ if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
2405
+ else out.push(...number(1, Valtype.i32));
2406
+
2407
+ out.push([ Opcodes.if, Blocktype.void ]);
2267
2408
  depth.push('if');
2268
2409
 
2269
2410
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2271,8 +2412,7 @@ const generateFor = (scope, decl) => {
2271
2412
  out.push(...generate(scope, decl.body));
2272
2413
  out.push([ Opcodes.end ]);
2273
2414
 
2274
- out.push(...generate(scope, decl.update));
2275
- depth.pop();
2415
+ if (decl.update) out.push(...generate(scope, decl.update));
2276
2416
 
2277
2417
  out.push([ Opcodes.br, 1 ]);
2278
2418
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2329,7 +2469,13 @@ const generateForOf = (scope, decl) => {
2329
2469
  // setup local for left
2330
2470
  generate(scope, decl.left);
2331
2471
 
2332
- const leftName = decl.left.declarations[0].id.name;
2472
+ let leftName = decl.left.declarations?.[0]?.id?.name;
2473
+ if (!leftName && decl.left.name) {
2474
+ leftName = decl.left.name;
2475
+
2476
+ generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2477
+ }
2478
+
2333
2479
  const [ local, isGlobal ] = lookupName(scope, leftName);
2334
2480
 
2335
2481
  depth.push('block');
@@ -2338,13 +2484,14 @@ const generateForOf = (scope, decl) => {
2338
2484
  // // todo: we should only do this for strings but we don't know at compile-time :(
2339
2485
  // hack: this is naughty and will break things!
2340
2486
  let newOut = number(0, Valtype.f64), newPointer = -1;
2341
- if (pages.hasString) {
2487
+ if (pages.hasAnyString) {
2342
2488
  0, [ newOut, newPointer ] = makeArray(scope, {
2343
2489
  rawElements: new Array(1)
2344
2490
  }, isGlobal, leftName, true, 'i16');
2345
2491
  }
2346
2492
 
2347
2493
  // set type for local
2494
+ // todo: optimize away counter and use end pointer
2348
2495
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2349
2496
  [TYPES._array]: [
2350
2497
  ...setType(scope, leftName, TYPES.number),
@@ -2469,7 +2616,7 @@ const generateThrow = (scope, decl) => {
2469
2616
  // hack: throw new X("...") -> throw "..."
2470
2617
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2471
2618
  constructor = decl.argument.callee.name;
2472
- message = decl.argument.arguments[0].value;
2619
+ message = decl.argument.arguments[0]?.value ?? '';
2473
2620
  }
2474
2621
 
2475
2622
  if (tags.length === 0) tags.push({
@@ -2481,6 +2628,9 @@ const generateThrow = (scope, decl) => {
2481
2628
  let exceptId = exceptions.push({ constructor, message }) - 1;
2482
2629
  let tagIdx = tags[0].idx;
2483
2630
 
2631
+ scope.exceptions ??= [];
2632
+ scope.exceptions.push(exceptId);
2633
+
2484
2634
  // todo: write a description of how this works lol
2485
2635
 
2486
2636
  return [
@@ -2525,25 +2675,31 @@ const generateAssignPat = (scope, decl) => {
2525
2675
  };
2526
2676
 
2527
2677
  let pages = new Map();
2528
- const allocPage = (reason, type) => {
2678
+ const allocPage = (scope, reason, type) => {
2529
2679
  if (pages.has(reason)) return pages.get(reason).ind;
2530
2680
 
2531
2681
  if (reason.startsWith('array:')) pages.hasArray = true;
2532
2682
  if (reason.startsWith('string:')) pages.hasString = true;
2683
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
2684
+ if (reason.includes('string:')) pages.hasAnyString = true;
2533
2685
 
2534
2686
  const ind = pages.size;
2535
2687
  pages.set(reason, { ind, type });
2536
2688
 
2537
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2689
+ scope.pages ??= new Map();
2690
+ scope.pages.set(reason, { ind, type });
2691
+
2692
+ if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2538
2693
 
2539
2694
  return ind;
2540
2695
  };
2541
2696
 
2697
+ // todo: add scope.pages
2542
2698
  const freePage = reason => {
2543
2699
  const { ind } = pages.get(reason);
2544
2700
  pages.delete(reason);
2545
2701
 
2546
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2702
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2547
2703
 
2548
2704
  return ind;
2549
2705
  };
@@ -2563,25 +2719,34 @@ const StoreOps = {
2563
2719
  f64: Opcodes.f64_store,
2564
2720
 
2565
2721
  // expects i32 input!
2566
- i16: Opcodes.i32_store16
2722
+ i8: Opcodes.i32_store8,
2723
+ i16: Opcodes.i32_store16,
2567
2724
  };
2568
2725
 
2569
2726
  let data = [];
2570
2727
 
2571
- const compileBytes = (val, itemType, signed = true) => {
2728
+ const compileBytes = (val, itemType) => {
2572
2729
  // todo: this is a mess and needs confirming / ????
2573
2730
  switch (itemType) {
2574
2731
  case 'i8': return [ val % 256 ];
2575
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2576
-
2577
- case 'i32':
2578
- case 'i64':
2579
- return enforceFourBytes(signedLEB128(val));
2732
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
2733
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
2734
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
2735
+ // todo: i64
2580
2736
 
2581
2737
  case 'f64': return ieee754_binary64(val);
2582
2738
  }
2583
2739
  };
2584
2740
 
2741
+ const getAllocType = itemType => {
2742
+ switch (itemType) {
2743
+ case 'i8': return 'bytestring';
2744
+ case 'i16': return 'string';
2745
+
2746
+ default: return 'array';
2747
+ }
2748
+ };
2749
+
2585
2750
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2586
2751
  const out = [];
2587
2752
 
@@ -2591,7 +2756,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2591
2756
 
2592
2757
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2593
2758
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2594
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
2759
+ arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2595
2760
  }
2596
2761
 
2597
2762
  const pointer = arrays.get(name);
@@ -2602,19 +2767,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2602
2767
  const valtype = itemTypeToValtype[itemType];
2603
2768
  const length = elements.length;
2604
2769
 
2605
- if (firstAssign && useRawElements) {
2606
- let bytes = compileBytes(length, 'i32');
2770
+ if (firstAssign && useRawElements && !Prefs.noData) {
2771
+ // if length is 0 memory/data will just be 0000... anyway
2772
+ if (length !== 0) {
2773
+ let bytes = compileBytes(length, 'i32');
2607
2774
 
2608
- if (!initEmpty) for (let i = 0; i < length; i++) {
2609
- if (elements[i] == null) continue;
2775
+ if (!initEmpty) for (let i = 0; i < length; i++) {
2776
+ if (elements[i] == null) continue;
2610
2777
 
2611
- bytes.push(...compileBytes(elements[i], itemType));
2612
- }
2778
+ bytes.push(...compileBytes(elements[i], itemType));
2779
+ }
2613
2780
 
2614
- data.push({
2615
- offset: pointer,
2616
- bytes
2617
- });
2781
+ const ind = data.push({
2782
+ offset: pointer,
2783
+ bytes
2784
+ }) - 1;
2785
+
2786
+ scope.data ??= [];
2787
+ scope.data.push(ind);
2788
+ }
2618
2789
 
2619
2790
  // local value as pointer
2620
2791
  out.push(...number(pointer));
@@ -2637,7 +2808,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2637
2808
  out.push(
2638
2809
  ...number(0, Valtype.i32),
2639
2810
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2640
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2811
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2641
2812
  );
2642
2813
  }
2643
2814
 
@@ -2647,15 +2818,31 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2647
2818
  return [ out, pointer ];
2648
2819
  };
2649
2820
 
2650
- const makeString = (scope, str, global = false, name = '$undeclared') => {
2821
+ const byteStringable = str => {
2822
+ if (!Prefs.bytestring) return false;
2823
+
2824
+ for (let i = 0; i < str.length; i++) {
2825
+ if (str.charCodeAt(i) > 0xFF) return false;
2826
+ }
2827
+
2828
+ return true;
2829
+ };
2830
+
2831
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2651
2832
  const rawElements = new Array(str.length);
2833
+ let byteStringable = Prefs.bytestring;
2652
2834
  for (let i = 0; i < str.length; i++) {
2653
- rawElements[i] = str.charCodeAt(i);
2835
+ const c = str.charCodeAt(i);
2836
+ rawElements[i] = c;
2837
+
2838
+ if (byteStringable && c > 0xFF) byteStringable = false;
2654
2839
  }
2655
2840
 
2841
+ if (byteStringable && forceBytestring === false) byteStringable = false;
2842
+
2656
2843
  return makeArray(scope, {
2657
2844
  rawElements
2658
- }, global, name, false, 'i16')[0];
2845
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2659
2846
  };
2660
2847
 
2661
2848
  let arrays = new Map();
@@ -2683,10 +2870,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2683
2870
  ];
2684
2871
  }
2685
2872
 
2873
+ const object = generate(scope, decl.object);
2874
+ const property = generate(scope, decl.property);
2875
+
2686
2876
  // // todo: we should only do this for strings but we don't know at compile-time :(
2687
2877
  // hack: this is naughty and will break things!
2688
2878
  let newOut = number(0, valtypeBinary), newPointer = -1;
2689
- if (pages.hasString) {
2879
+ if (pages.hasAnyString) {
2690
2880
  0, [ newOut, newPointer ] = makeArray(scope, {
2691
2881
  rawElements: new Array(1)
2692
2882
  }, _global, _name, true, 'i16');
@@ -2695,7 +2885,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2695
2885
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2696
2886
  [TYPES._array]: [
2697
2887
  // get index as valtype
2698
- ...generate(scope, decl.property),
2888
+ ...property,
2699
2889
 
2700
2890
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2701
2891
  Opcodes.i32_to_u,
@@ -2703,7 +2893,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2703
2893
  [ Opcodes.i32_mul ],
2704
2894
 
2705
2895
  ...(aotPointer ? [] : [
2706
- ...generate(scope, decl.object),
2896
+ ...object,
2707
2897
  Opcodes.i32_to_u,
2708
2898
  [ Opcodes.i32_add ]
2709
2899
  ]),
@@ -2722,14 +2912,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2722
2912
 
2723
2913
  ...number(0, Valtype.i32), // base 0 for store later
2724
2914
 
2725
- ...generate(scope, decl.property),
2726
-
2915
+ ...property,
2727
2916
  Opcodes.i32_to_u,
2917
+
2728
2918
  ...number(ValtypeSize.i16, Valtype.i32),
2729
2919
  [ Opcodes.i32_mul ],
2730
2920
 
2731
2921
  ...(aotPointer ? [] : [
2732
- ...generate(scope, decl.object),
2922
+ ...object,
2733
2923
  Opcodes.i32_to_u,
2734
2924
  [ Opcodes.i32_add ]
2735
2925
  ]),
@@ -2746,8 +2936,36 @@ export const generateMember = (scope, decl, _global, _name) => {
2746
2936
  ...number(TYPES.string, Valtype.i32),
2747
2937
  setLastType(scope)
2748
2938
  ],
2939
+ [TYPES._bytestring]: [
2940
+ // setup new/out array
2941
+ ...newOut,
2942
+ [ Opcodes.drop ],
2943
+
2944
+ ...number(0, Valtype.i32), // base 0 for store later
2945
+
2946
+ ...property,
2947
+ Opcodes.i32_to_u,
2948
+
2949
+ ...(aotPointer ? [] : [
2950
+ ...object,
2951
+ Opcodes.i32_to_u,
2952
+ [ Opcodes.i32_add ]
2953
+ ]),
2954
+
2955
+ // load current string ind {arg}
2956
+ [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2957
+
2958
+ // store to new string ind 0
2959
+ [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2960
+
2961
+ // return new string (page)
2962
+ ...number(newPointer),
2963
+
2964
+ ...number(TYPES._bytestring, Valtype.i32),
2965
+ setLastType(scope)
2966
+ ],
2749
2967
 
2750
- default: [ [ Opcodes.unreachable ] ]
2968
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
2751
2969
  });
2752
2970
  };
2753
2971
 
@@ -2764,13 +2982,16 @@ const objectHack = node => {
2764
2982
  // if object is not identifier or another member exp, give up
2765
2983
  if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
2766
2984
 
2767
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
2985
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2768
2986
 
2769
2987
  // if .length, give up (hack within a hack!)
2770
2988
  if (node.property.name === 'length') return node;
2771
2989
 
2990
+ // no object name, give up
2991
+ if (!objectName) return node;
2992
+
2772
2993
  const name = '__' + objectName + '_' + node.property.name;
2773
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2994
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2774
2995
 
2775
2996
  return {
2776
2997
  type: 'Identifier',
@@ -2827,13 +3048,13 @@ const generateFunc = (scope, decl) => {
2827
3048
  const func = {
2828
3049
  name,
2829
3050
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2830
- returns: innerScope.returns,
2831
- locals: innerScope.locals,
2832
- throws: innerScope.throws,
2833
- index: currentFuncIndex++
3051
+ index: currentFuncIndex++,
3052
+ ...innerScope
2834
3053
  };
2835
3054
  funcIndex[name] = func.index;
2836
3055
 
3056
+ if (name === 'main') func.gotLastType = true;
3057
+
2837
3058
  // quick hack fixes
2838
3059
  for (const inst of wasm) {
2839
3060
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -2868,6 +3089,16 @@ const generateCode = (scope, decl) => {
2868
3089
  };
2869
3090
 
2870
3091
  const internalConstrs = {
3092
+ Boolean: {
3093
+ generate: (scope, decl) => {
3094
+ if (decl.arguments.length === 0) return number(0);
3095
+
3096
+ // should generate/run all args
3097
+ return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3098
+ },
3099
+ type: TYPES.boolean
3100
+ },
3101
+
2871
3102
  Array: {
2872
3103
  generate: (scope, decl, global, name) => {
2873
3104
  // new Array(i0, i1, ...)
@@ -2989,7 +3220,7 @@ export default program => {
2989
3220
  body: program.body
2990
3221
  };
2991
3222
 
2992
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3223
+ if (Prefs.astLog) console.log(program.body.body);
2993
3224
 
2994
3225
  generateFunc(scope, program);
2995
3226