porffor 0.2.0-623cdf0 → 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;
@@ -160,7 +161,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
160
161
 
161
162
  case 'TaggedTemplateExpression': {
162
163
  const funcs = {
163
- __Porffor_asm: str => {
164
+ __Porffor_wasm: str => {
164
165
  let out = [];
165
166
 
166
167
  for (const line of str.split('\n')) {
@@ -168,8 +169,8 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
168
169
  if (asm[0] === '') continue; // blank
169
170
 
170
171
  if (asm[0] === 'local') {
171
- const [ name, idx, type ] = asm.slice(1);
172
- 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] };
173
174
  continue;
174
175
  }
175
176
 
@@ -179,7 +180,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
179
180
  }
180
181
 
181
182
  if (asm[0] === 'memory') {
182
- allocPage('asm instrinsic');
183
+ allocPage(scope, 'asm instrinsic');
183
184
  // todo: add to store/load offset insts
184
185
  continue;
185
186
  }
@@ -188,7 +189,11 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
188
189
  if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
189
190
 
190
191
  if (!Array.isArray(inst)) inst = [ inst ];
191
- 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
+ });
192
197
 
193
198
  out.push([ ...inst, ...immediates ]);
194
199
  }
@@ -214,13 +219,22 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
214
219
  // hack for inline asm
215
220
  if (!funcs[name]) return todo('tagged template expressions not implemented');
216
221
 
217
- 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
+
218
231
  return funcs[name](str);
219
232
  }
220
233
 
221
234
  default:
222
- if (decl.type.startsWith('TS')) {
223
- // ignore typescript nodes
235
+ // ignore typescript nodes
236
+ if (decl.type.startsWith('TS') ||
237
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
224
238
  return [];
225
239
  }
226
240
 
@@ -276,7 +290,10 @@ const generateIdent = (scope, decl) => {
276
290
 
277
291
  if (Object.hasOwn(builtinVars, name)) {
278
292
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
279
- return builtinVars[name];
293
+
294
+ let wasm = builtinVars[name];
295
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
296
+ return wasm;
280
297
  }
281
298
 
282
299
  if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
@@ -409,9 +426,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
409
426
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
410
427
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
411
428
 
412
- const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
413
- if (aotWFA) addVarMeta(name, { wellFormed: undefined });
414
-
415
429
  if (assign) {
416
430
  const pointer = arrays.get(name ?? '$undeclared');
417
431
 
@@ -649,11 +663,12 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
649
663
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
650
664
  ];
651
665
 
652
- 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);
653
668
 
654
669
  const def = [
655
670
  // if value != 0
656
- [ Opcodes.local_get, tmp ],
671
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
657
672
 
658
673
  // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
659
674
  ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
@@ -665,7 +680,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
665
680
 
666
681
  return [
667
682
  ...wasm,
668
- [ Opcodes.local_set, tmp ],
683
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
669
684
 
670
685
  ...typeSwitch(scope, type, {
671
686
  // [TYPES.number]: def,
@@ -674,7 +689,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
674
689
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
675
690
  ],
676
691
  [TYPES.string]: [
677
- [ Opcodes.local_get, tmp ],
692
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
678
693
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
679
694
 
680
695
  // get length
@@ -686,7 +701,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
686
701
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
687
702
  ],
688
703
  [TYPES._bytestring]: [ // duplicate of string
689
- [ Opcodes.local_get, tmp ],
704
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
690
705
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
691
706
 
692
707
  // get length
@@ -700,10 +715,12 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
700
715
  };
701
716
 
702
717
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
703
- 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
+
704
721
  return [
705
722
  ...wasm,
706
- [ Opcodes.local_set, tmp ],
723
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
707
724
 
708
725
  ...typeSwitch(scope, type, {
709
726
  [TYPES._array]: [
@@ -711,7 +728,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
711
728
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
712
729
  ],
713
730
  [TYPES.string]: [
714
- [ Opcodes.local_get, tmp ],
731
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
715
732
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
716
733
 
717
734
  // get length
@@ -722,7 +739,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
722
739
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
723
740
  ],
724
741
  [TYPES._bytestring]: [ // duplicate of string
725
- [ Opcodes.local_get, tmp ],
742
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
726
743
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
727
744
 
728
745
  // get length
@@ -734,7 +751,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
734
751
  ],
735
752
  default: [
736
753
  // if value == 0
737
- [ Opcodes.local_get, tmp ],
754
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
738
755
 
739
756
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
740
757
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -744,10 +761,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
744
761
  };
745
762
 
746
763
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
747
- 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
+
748
767
  return [
749
768
  ...wasm,
750
- [ Opcodes.local_set, tmp ],
769
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
751
770
 
752
771
  ...typeSwitch(scope, type, {
753
772
  [TYPES.undefined]: [
@@ -756,7 +775,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
756
775
  ],
757
776
  [TYPES.object]: [
758
777
  // object, null if == 0
759
- [ Opcodes.local_get, tmp ],
778
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
760
779
 
761
780
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
762
781
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -785,11 +804,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
785
804
  return performLogicOp(scope, op, left, right, leftType, rightType);
786
805
  }
787
806
 
807
+ const knownLeft = knownType(scope, leftType);
808
+ const knownRight = knownType(scope, rightType);
809
+
788
810
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
789
811
  const strictOp = op === '===' || op === '!==';
790
812
 
791
813
  const startOut = [], endOut = [];
792
- const finalise = out => startOut.concat(out, endOut);
814
+ const finalize = out => startOut.concat(out, endOut);
793
815
 
794
816
  // if strict (in)equal check types match
795
817
  if (strictOp) {
@@ -834,31 +856,32 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
834
856
  // todo: if equality op and an operand is undefined, return false
835
857
  // todo: niche null hell with 0
836
858
 
837
- // if (leftType === TYPES.string || rightType === TYPES.string) {
838
- // if (op === '+') {
839
- // // string concat (a + b)
840
- // return finalise(concatStrings(scope, left, right, _global, _name, assign));
841
- // }
842
-
843
- // // not an equality op, NaN
844
- // if (!eqOp) return finalise(number(NaN));
845
-
846
- // // else leave bool ops
847
- // // todo: convert string to number if string and number/bool
848
- // // todo: string (>|>=|<|<=) string
849
-
850
- // // string comparison
851
- // if (op === '===' || op === '==') {
852
- // return finalise(compareStrings(scope, left, right));
853
- // }
854
-
855
- // if (op === '!==' || op === '!=') {
856
- // return finalise([
857
- // ...compareStrings(scope, left, right),
858
- // [ Opcodes.i32_eqz ]
859
- // ]);
860
- // }
861
- // }
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
+ }
862
885
 
863
886
  let ops = operatorOpcode[valtype][op];
864
887
 
@@ -868,7 +891,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
868
891
  includeBuiltin(scope, builtinName);
869
892
  const idx = funcIndex[builtinName];
870
893
 
871
- return finalise([
894
+ return finalize([
872
895
  ...left,
873
896
  ...right,
874
897
  [ Opcodes.call, idx ]
@@ -883,9 +906,6 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
883
906
  let tmpLeft, tmpRight;
884
907
  // if equal op, check if strings for compareStrings
885
908
  if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
886
- const knownLeft = knownType(scope, leftType);
887
- const knownRight = knownType(scope, rightType);
888
-
889
909
  // todo: intelligent partial skip later
890
910
  // if neither known are string, stop this madness
891
911
  if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
@@ -943,7 +963,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
943
963
  // }
944
964
  })();
945
965
 
946
- return finalise([
966
+ return finalize([
947
967
  ...left,
948
968
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
949
969
  ...right,
@@ -960,7 +980,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
960
980
  return out;
961
981
  };
962
982
 
963
- 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 = [] }) => {
964
999
  const existing = funcs.find(x => x.name === name);
965
1000
  if (existing) return existing;
966
1001
 
@@ -972,18 +1007,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
972
1007
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
973
1008
  }
974
1009
 
975
- if (typeof wasm === 'function') {
976
- const scope = {
977
- name,
978
- params,
979
- locals,
980
- returns,
981
- localInd: allLocals.length,
982
- };
983
-
984
- wasm = wasm(scope, { TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString });
1010
+ for (const x of _data) {
1011
+ const copy = { ...x };
1012
+ copy.offset += pages.size * pageSize;
1013
+ data.push(copy);
985
1014
  }
986
1015
 
1016
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1017
+
987
1018
  let baseGlobalIdx, i = 0;
988
1019
  for (const type of globalTypes) {
989
1020
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -1086,7 +1117,10 @@ const TYPE_NAMES = {
1086
1117
  const getType = (scope, _name) => {
1087
1118
  const name = mapName(_name);
1088
1119
 
1120
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1089
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);
1090
1124
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1091
1125
 
1092
1126
  let type = TYPES.undefined;
@@ -1164,7 +1198,7 @@ const getNodeType = (scope, node) => {
1164
1198
  if (func.returnType) return func.returnType;
1165
1199
  }
1166
1200
 
1167
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1201
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1168
1202
  if (internalConstrs[name]) return internalConstrs[name].type;
1169
1203
 
1170
1204
  // check if this is a prototype function
@@ -1179,6 +1213,11 @@ const getNodeType = (scope, node) => {
1179
1213
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1180
1214
  }
1181
1215
 
1216
+ if (name.startsWith('__Porffor_wasm_')) {
1217
+ // todo: return undefined for non-returning ops
1218
+ return TYPES.number;
1219
+ }
1220
+
1182
1221
  if (scope.locals['#last_type']) return [ getLastType(scope) ];
1183
1222
 
1184
1223
  // presume
@@ -1227,6 +1266,14 @@ const getNodeType = (scope, node) => {
1227
1266
 
1228
1267
  if (node.type === 'BinaryExpression') {
1229
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
+
1230
1277
  return TYPES.number;
1231
1278
 
1232
1279
  // todo: string concat types
@@ -1251,7 +1298,7 @@ const getNodeType = (scope, node) => {
1251
1298
  if (node.operator === '!') return TYPES.boolean;
1252
1299
  if (node.operator === 'void') return TYPES.undefined;
1253
1300
  if (node.operator === 'delete') return TYPES.boolean;
1254
- if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
1301
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
1255
1302
 
1256
1303
  return TYPES.number;
1257
1304
  }
@@ -1320,7 +1367,7 @@ const countLeftover = wasm => {
1320
1367
  if (inst[0] === Opcodes.end) depth--;
1321
1368
 
1322
1369
  if (depth === 0)
1323
- if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1370
+ if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1324
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)) {}
1325
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++;
1326
1373
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
@@ -1645,6 +1692,32 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1645
1692
  idx = -1;
1646
1693
  }
1647
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
+
1648
1721
  if (idx === undefined) {
1649
1722
  if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1650
1723
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
@@ -1654,7 +1727,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1654
1727
 
1655
1728
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1656
1729
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1657
- const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
1730
+ const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
1658
1731
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1659
1732
 
1660
1733
  let args = decl.arguments;
@@ -1671,14 +1744,20 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1671
1744
  if (func && func.throws) scope.throws = true;
1672
1745
 
1673
1746
  let out = [];
1674
- for (const arg of args) {
1747
+ for (let i = 0; i < args.length; i++) {
1748
+ const arg = args[i];
1675
1749
  out = out.concat(generate(scope, arg));
1750
+
1751
+ if (builtinFuncs[name] && builtinFuncs[name].params[i] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1752
+ out.push(Opcodes.i32_to);
1753
+ }
1754
+
1676
1755
  if (typedParams) out = out.concat(getNodeType(scope, arg));
1677
1756
  }
1678
1757
 
1679
1758
  out.push([ Opcodes.call, idx ]);
1680
1759
 
1681
- if (!typedReturn) {
1760
+ if (!typedReturns) {
1682
1761
  // let type;
1683
1762
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1684
1763
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1690,6 +1769,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1690
1769
  // );
1691
1770
  } else out.push(setLastType(scope));
1692
1771
 
1772
+ if (builtinFuncs[name] && builtinFuncs[name].returns[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1773
+ out.push(Opcodes.i32_from);
1774
+ }
1775
+
1693
1776
  return out;
1694
1777
  };
1695
1778
 
@@ -1814,14 +1897,14 @@ const brTable = (input, bc, returns) => {
1814
1897
  };
1815
1898
 
1816
1899
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1817
- if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
1900
+ if (!Prefs.bytestring) delete bc[TYPES._bytestring];
1818
1901
 
1819
1902
  const known = knownType(scope, type);
1820
1903
  if (known != null) {
1821
1904
  return bc[known] ?? bc.default;
1822
1905
  }
1823
1906
 
1824
- if (process.argv.includes('-typeswitch-use-brtable'))
1907
+ if (Prefs.typeswitchUseBrtable)
1825
1908
  return brTable(type, bc, returns);
1826
1909
 
1827
1910
  const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
@@ -1856,7 +1939,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1856
1939
  return out;
1857
1940
  };
1858
1941
 
1859
- const allocVar = (scope, name, global = false) => {
1942
+ const allocVar = (scope, name, global = false, type = true) => {
1860
1943
  const target = global ? globals : scope.locals;
1861
1944
 
1862
1945
  // already declared
@@ -1870,8 +1953,10 @@ const allocVar = (scope, name, global = false) => {
1870
1953
  let idx = global ? globalInd++ : scope.localInd++;
1871
1954
  target[name] = { idx, type: valtypeBinary };
1872
1955
 
1873
- let typeIdx = global ? globalInd++ : scope.localInd++;
1874
- 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
+ }
1875
1960
 
1876
1961
  return idx;
1877
1962
  };
@@ -1891,6 +1976,7 @@ const typeAnnoToPorfType = x => {
1891
1976
 
1892
1977
  switch (x) {
1893
1978
  case 'i32':
1979
+ case 'i64':
1894
1980
  return TYPES.number;
1895
1981
  }
1896
1982
 
@@ -1914,7 +2000,7 @@ const extractTypeAnnotation = decl => {
1914
2000
  const typeName = type;
1915
2001
  type = typeAnnoToPorfType(type);
1916
2002
 
1917
- if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
2003
+ if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
1918
2004
 
1919
2005
  // if (decl.name) console.log(decl.name, { type, elementType });
1920
2006
 
@@ -1949,7 +2035,7 @@ const generateVar = (scope, decl) => {
1949
2035
  continue; // always ignore
1950
2036
  }
1951
2037
 
1952
- let idx = allocVar(scope, name, global);
2038
+ let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
1953
2039
 
1954
2040
  if (typedInput && x.id.typeAnnotation) {
1955
2041
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
@@ -2120,9 +2206,7 @@ const generateAssign = (scope, decl) => {
2120
2206
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
2121
2207
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2122
2208
 
2123
- getLastType(scope),
2124
- // hack: type is idx+1
2125
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2209
+ ...setType(scope, name, getLastType(scope))
2126
2210
  ];
2127
2211
  }
2128
2212
 
@@ -2133,9 +2217,7 @@ const generateAssign = (scope, decl) => {
2133
2217
 
2134
2218
  // todo: string concat types
2135
2219
 
2136
- // hack: type is idx+1
2137
- ...number(TYPES.number, Valtype.i32),
2138
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2220
+ ...setType(scope, name, TYPES.number)
2139
2221
  ];
2140
2222
  };
2141
2223
 
@@ -2546,6 +2628,9 @@ const generateThrow = (scope, decl) => {
2546
2628
  let exceptId = exceptions.push({ constructor, message }) - 1;
2547
2629
  let tagIdx = tags[0].idx;
2548
2630
 
2631
+ scope.exceptions ??= [];
2632
+ scope.exceptions.push(exceptId);
2633
+
2549
2634
  // todo: write a description of how this works lol
2550
2635
 
2551
2636
  return [
@@ -2590,7 +2675,7 @@ const generateAssignPat = (scope, decl) => {
2590
2675
  };
2591
2676
 
2592
2677
  let pages = new Map();
2593
- const allocPage = (reason, type) => {
2678
+ const allocPage = (scope, reason, type) => {
2594
2679
  if (pages.has(reason)) return pages.get(reason).ind;
2595
2680
 
2596
2681
  if (reason.startsWith('array:')) pages.hasArray = true;
@@ -2601,16 +2686,20 @@ const allocPage = (reason, type) => {
2601
2686
  const ind = pages.size;
2602
2687
  pages.set(reason, { ind, type });
2603
2688
 
2604
- 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})`);
2605
2693
 
2606
2694
  return ind;
2607
2695
  };
2608
2696
 
2697
+ // todo: add scope.pages
2609
2698
  const freePage = reason => {
2610
2699
  const { ind } = pages.get(reason);
2611
2700
  pages.delete(reason);
2612
2701
 
2613
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2702
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2614
2703
 
2615
2704
  return ind;
2616
2705
  };
@@ -2636,15 +2725,14 @@ const StoreOps = {
2636
2725
 
2637
2726
  let data = [];
2638
2727
 
2639
- const compileBytes = (val, itemType, signed = true) => {
2728
+ const compileBytes = (val, itemType) => {
2640
2729
  // todo: this is a mess and needs confirming / ????
2641
2730
  switch (itemType) {
2642
2731
  case 'i8': return [ val % 256 ];
2643
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2644
-
2645
- case 'i32':
2646
- case 'i64':
2647
- 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
2648
2736
 
2649
2737
  case 'f64': return ieee754_binary64(val);
2650
2738
  }
@@ -2668,7 +2756,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2668
2756
 
2669
2757
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2670
2758
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2671
- arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2759
+ arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2672
2760
  }
2673
2761
 
2674
2762
  const pointer = arrays.get(name);
@@ -2679,19 +2767,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2679
2767
  const valtype = itemTypeToValtype[itemType];
2680
2768
  const length = elements.length;
2681
2769
 
2682
- if (firstAssign && useRawElements) {
2683
- 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');
2684
2774
 
2685
- if (!initEmpty) for (let i = 0; i < length; i++) {
2686
- if (elements[i] == null) continue;
2775
+ if (!initEmpty) for (let i = 0; i < length; i++) {
2776
+ if (elements[i] == null) continue;
2687
2777
 
2688
- bytes.push(...compileBytes(elements[i], itemType));
2689
- }
2778
+ bytes.push(...compileBytes(elements[i], itemType));
2779
+ }
2690
2780
 
2691
- data.push({
2692
- offset: pointer,
2693
- bytes
2694
- });
2781
+ const ind = data.push({
2782
+ offset: pointer,
2783
+ bytes
2784
+ }) - 1;
2785
+
2786
+ scope.data ??= [];
2787
+ scope.data.push(ind);
2788
+ }
2695
2789
 
2696
2790
  // local value as pointer
2697
2791
  out.push(...number(pointer));
@@ -2725,7 +2819,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2725
2819
  };
2726
2820
 
2727
2821
  const byteStringable = str => {
2728
- if (!process.argv.includes('-bytestring')) return false;
2822
+ if (!Prefs.bytestring) return false;
2729
2823
 
2730
2824
  for (let i = 0; i < str.length; i++) {
2731
2825
  if (str.charCodeAt(i) > 0xFF) return false;
@@ -2736,7 +2830,7 @@ const byteStringable = str => {
2736
2830
 
2737
2831
  const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2738
2832
  const rawElements = new Array(str.length);
2739
- let byteStringable = process.argv.includes('-bytestring');
2833
+ let byteStringable = Prefs.bytestring;
2740
2834
  for (let i = 0; i < str.length; i++) {
2741
2835
  const c = str.charCodeAt(i);
2742
2836
  rawElements[i] = c;
@@ -2897,7 +2991,7 @@ const objectHack = node => {
2897
2991
  if (!objectName) return node;
2898
2992
 
2899
2993
  const name = '__' + objectName + '_' + node.property.name;
2900
- 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}`);
2901
2995
 
2902
2996
  return {
2903
2997
  type: 'Identifier',
@@ -2959,6 +3053,8 @@ const generateFunc = (scope, decl) => {
2959
3053
  };
2960
3054
  funcIndex[name] = func.index;
2961
3055
 
3056
+ if (name === 'main') func.gotLastType = true;
3057
+
2962
3058
  // quick hack fixes
2963
3059
  for (const inst of wasm) {
2964
3060
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -3124,7 +3220,7 @@ export default program => {
3124
3220
  body: program.body
3125
3221
  };
3126
3222
 
3127
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3223
+ if (Prefs.astLog) console.log(program.body.body);
3128
3224
 
3129
3225
  generateFunc(scope, program);
3130
3226