porffor 0.2.0-1afe9b8 → 0.2.0-2265539

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
- 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,26 +189,54 @@ 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
  }
195
200
 
196
201
  return out;
197
- }
198
- }
202
+ },
203
+
204
+ __Porffor_bs: str => [
205
+ ...makeString(scope, str, undefined, undefined, true),
206
+
207
+ ...number(TYPES._bytestring, Valtype.i32),
208
+ setLastType(scope)
209
+ ],
210
+ __Porffor_s: str => [
211
+ ...makeString(scope, str, undefined, undefined, false),
212
+
213
+ ...number(TYPES.string, Valtype.i32),
214
+ setLastType(scope)
215
+ ],
216
+ };
199
217
 
200
218
  const name = decl.tag.name;
201
219
  // hack for inline asm
202
220
  if (!funcs[name]) return todo('tagged template expressions not implemented');
203
221
 
204
- 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
+
231
+ console.log(str);
232
+
205
233
  return funcs[name](str);
206
234
  }
207
235
 
208
236
  default:
209
- if (decl.type.startsWith('TS')) {
210
- // ignore typescript nodes
237
+ // ignore typescript nodes
238
+ if (decl.type.startsWith('TS') ||
239
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
211
240
  return [];
212
241
  }
213
242
 
@@ -396,9 +425,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
396
425
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
397
426
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
398
427
 
399
- const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
400
- if (aotWFA) addVarMeta(name, { wellFormed: undefined });
401
-
402
428
  if (assign) {
403
429
  const pointer = arrays.get(name ?? '$undeclared');
404
430
 
@@ -636,11 +662,12 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
636
662
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
637
663
  ];
638
664
 
639
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
665
+ const useTmp = knownType(scope, type) == null;
666
+ const tmp = !useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
640
667
 
641
668
  const def = [
642
669
  // if value != 0
643
- [ Opcodes.local_get, tmp ],
670
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
644
671
 
645
672
  // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
646
673
  ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
@@ -652,7 +679,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
652
679
 
653
680
  return [
654
681
  ...wasm,
655
- [ Opcodes.local_set, tmp ],
682
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
656
683
 
657
684
  ...typeSwitch(scope, type, {
658
685
  // [TYPES.number]: def,
@@ -661,7 +688,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
661
688
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
662
689
  ],
663
690
  [TYPES.string]: [
664
- [ Opcodes.local_get, tmp ],
691
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
665
692
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
666
693
 
667
694
  // get length
@@ -673,7 +700,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
673
700
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
674
701
  ],
675
702
  [TYPES._bytestring]: [ // duplicate of string
676
- [ Opcodes.local_get, tmp ],
703
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
677
704
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
678
705
 
679
706
  // get length
@@ -687,10 +714,12 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
687
714
  };
688
715
 
689
716
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
690
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
717
+ const useTmp = knownType(scope, type) == null;
718
+ const tmp = !useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
719
+
691
720
  return [
692
721
  ...wasm,
693
- [ Opcodes.local_set, tmp ],
722
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
694
723
 
695
724
  ...typeSwitch(scope, type, {
696
725
  [TYPES._array]: [
@@ -698,7 +727,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
698
727
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
699
728
  ],
700
729
  [TYPES.string]: [
701
- [ Opcodes.local_get, tmp ],
730
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
702
731
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
703
732
 
704
733
  // get length
@@ -709,7 +738,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
709
738
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
710
739
  ],
711
740
  [TYPES._bytestring]: [ // duplicate of string
712
- [ Opcodes.local_get, tmp ],
741
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
713
742
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
714
743
 
715
744
  // get length
@@ -721,7 +750,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
721
750
  ],
722
751
  default: [
723
752
  // if value == 0
724
- [ Opcodes.local_get, tmp ],
753
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
725
754
 
726
755
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
727
756
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -731,10 +760,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
731
760
  };
732
761
 
733
762
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
734
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
763
+ const useTmp = knownType(scope, type) == null;
764
+ const tmp = !useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
765
+
735
766
  return [
736
767
  ...wasm,
737
- [ Opcodes.local_set, tmp ],
768
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
738
769
 
739
770
  ...typeSwitch(scope, type, {
740
771
  [TYPES.undefined]: [
@@ -743,7 +774,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
743
774
  ],
744
775
  [TYPES.object]: [
745
776
  // object, null if == 0
746
- [ Opcodes.local_get, tmp ],
777
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
747
778
 
748
779
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
749
780
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -772,11 +803,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
772
803
  return performLogicOp(scope, op, left, right, leftType, rightType);
773
804
  }
774
805
 
806
+ const knownLeft = knownType(scope, leftType);
807
+ const knownRight = knownType(scope, rightType);
808
+
775
809
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
776
810
  const strictOp = op === '===' || op === '!==';
777
811
 
778
812
  const startOut = [], endOut = [];
779
- const finalise = out => startOut.concat(out, endOut);
813
+ const finalize = out => startOut.concat(out, endOut);
780
814
 
781
815
  // if strict (in)equal check types match
782
816
  if (strictOp) {
@@ -821,31 +855,32 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
821
855
  // todo: if equality op and an operand is undefined, return false
822
856
  // todo: niche null hell with 0
823
857
 
824
- // if (leftType === TYPES.string || rightType === TYPES.string) {
825
- // if (op === '+') {
826
- // // string concat (a + b)
827
- // return finalise(concatStrings(scope, left, right, _global, _name, assign));
828
- // }
829
-
830
- // // not an equality op, NaN
831
- // if (!eqOp) return finalise(number(NaN));
832
-
833
- // // else leave bool ops
834
- // // todo: convert string to number if string and number/bool
835
- // // todo: string (>|>=|<|<=) string
836
-
837
- // // string comparison
838
- // if (op === '===' || op === '==') {
839
- // return finalise(compareStrings(scope, left, right));
840
- // }
841
-
842
- // if (op === '!==' || op === '!=') {
843
- // return finalise([
844
- // ...compareStrings(scope, left, right),
845
- // [ Opcodes.i32_eqz ]
846
- // ]);
847
- // }
848
- // }
858
+ // todo: this should be dynamic but for now only static
859
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) {
860
+ if (op === '+') {
861
+ // string concat (a + b)
862
+ return concatStrings(scope, left, right, _global, _name, assign);
863
+ }
864
+
865
+ // not an equality op, NaN
866
+ if (!eqOp) return number(NaN);
867
+
868
+ // else leave bool ops
869
+ // todo: convert string to number if string and number/bool
870
+ // todo: string (>|>=|<|<=) string
871
+
872
+ // string comparison
873
+ if (op === '===' || op === '==') {
874
+ return compareStrings(scope, left, right);
875
+ }
876
+
877
+ if (op === '!==' || op === '!=') {
878
+ return [
879
+ ...compareStrings(scope, left, right),
880
+ [ Opcodes.i32_eqz ]
881
+ ];
882
+ }
883
+ }
849
884
 
850
885
  let ops = operatorOpcode[valtype][op];
851
886
 
@@ -855,7 +890,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
855
890
  includeBuiltin(scope, builtinName);
856
891
  const idx = funcIndex[builtinName];
857
892
 
858
- return finalise([
893
+ return finalize([
859
894
  ...left,
860
895
  ...right,
861
896
  [ Opcodes.call, idx ]
@@ -870,9 +905,6 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
870
905
  let tmpLeft, tmpRight;
871
906
  // if equal op, check if strings for compareStrings
872
907
  if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
873
- const knownLeft = knownType(scope, leftType);
874
- const knownRight = knownType(scope, rightType);
875
-
876
908
  // todo: intelligent partial skip later
877
909
  // if neither known are string, stop this madness
878
910
  if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
@@ -912,7 +944,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
912
944
  [ Opcodes.i32_or ],
913
945
  [ Opcodes.if, Blocktype.void ],
914
946
  ...number(0, Valtype.i32),
915
- [ Opcodes.br, 1 ],
947
+ [ Opcodes.br, 2 ],
916
948
  [ Opcodes.end ],
917
949
 
918
950
  ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
@@ -930,7 +962,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
930
962
  // }
931
963
  })();
932
964
 
933
- return finalise([
965
+ return finalize([
934
966
  ...left,
935
967
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
936
968
  ...right,
@@ -947,7 +979,7 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
947
979
  return out;
948
980
  };
949
981
 
950
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
982
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
951
983
  const existing = funcs.find(x => x.name === name);
952
984
  if (existing) return existing;
953
985
 
@@ -959,6 +991,12 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
959
991
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
960
992
  }
961
993
 
994
+ for (const x of _data) {
995
+ const copy = { ...x };
996
+ copy.offset += pages.size * pageSize;
997
+ data.push(copy);
998
+ }
999
+
962
1000
  if (typeof wasm === 'function') {
963
1001
  const scope = {
964
1002
  name,
@@ -968,7 +1006,18 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
968
1006
  localInd: allLocals.length,
969
1007
  };
970
1008
 
971
- wasm = wasm(scope, { TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString });
1009
+ wasm = wasm(scope, {
1010
+ TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
1011
+ builtin: name => {
1012
+ let idx = funcIndex[name] ?? importedFuncs[name];
1013
+ if (idx === undefined && builtinFuncs[name]) {
1014
+ includeBuiltin(scope, name);
1015
+ idx = funcIndex[name];
1016
+ }
1017
+
1018
+ return idx;
1019
+ }
1020
+ });
972
1021
  }
973
1022
 
974
1023
  let baseGlobalIdx, i = 0;
@@ -1073,7 +1122,10 @@ const TYPE_NAMES = {
1073
1122
  const getType = (scope, _name) => {
1074
1123
  const name = mapName(_name);
1075
1124
 
1125
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1076
1126
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1127
+
1128
+ if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
1077
1129
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1078
1130
 
1079
1131
  let type = TYPES.undefined;
@@ -1151,7 +1203,7 @@ const getNodeType = (scope, node) => {
1151
1203
  if (func.returnType) return func.returnType;
1152
1204
  }
1153
1205
 
1154
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1206
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1155
1207
  if (internalConstrs[name]) return internalConstrs[name].type;
1156
1208
 
1157
1209
  // check if this is a prototype function
@@ -1166,6 +1218,11 @@ const getNodeType = (scope, node) => {
1166
1218
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1167
1219
  }
1168
1220
 
1221
+ if (name.startsWith('__Porffor_wasm_')) {
1222
+ // todo: return undefined for non-returning ops
1223
+ return TYPES.number;
1224
+ }
1225
+
1169
1226
  if (scope.locals['#last_type']) return [ getLastType(scope) ];
1170
1227
 
1171
1228
  // presume
@@ -1214,6 +1271,14 @@ const getNodeType = (scope, node) => {
1214
1271
 
1215
1272
  if (node.type === 'BinaryExpression') {
1216
1273
  if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1274
+ if (node.operator !== '+') return TYPES.number;
1275
+
1276
+ const knownLeft = knownType(scope, getNodeType(scope, node.left));
1277
+ const knownRight = knownType(scope, getNodeType(scope, node.right));
1278
+
1279
+ // todo: this should be dynamic but for now only static
1280
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1281
+
1217
1282
  return TYPES.number;
1218
1283
 
1219
1284
  // todo: string concat types
@@ -1238,7 +1303,7 @@ const getNodeType = (scope, node) => {
1238
1303
  if (node.operator === '!') return TYPES.boolean;
1239
1304
  if (node.operator === 'void') return TYPES.undefined;
1240
1305
  if (node.operator === 'delete') return TYPES.boolean;
1241
- if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
1306
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
1242
1307
 
1243
1308
  return TYPES.number;
1244
1309
  }
@@ -1632,6 +1697,44 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1632
1697
  idx = -1;
1633
1698
  }
1634
1699
 
1700
+ if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1701
+ const wasmOps = {
1702
+ // pointer, align, offset
1703
+ i32_load8_u: { imms: 2, args: 1 },
1704
+ // pointer, value, align, offset
1705
+ i32_store8: { imms: 2, args: 2 },
1706
+ // pointer, align, offset
1707
+ i64_load: { imms: 2, args: 1 },
1708
+ // a, b
1709
+ i64_shr_u: { imms: 0, args: 2 },
1710
+ // x
1711
+ i32_wrap_i64: { imms: 0, args: 1, },
1712
+ // x
1713
+ i64_extend_i32_u: { imms: 0, args: 1 },
1714
+ // a, b
1715
+ i64_and: { imms: 0, args: 2 },
1716
+ // val (todo: support >255)
1717
+ i64_const: { imms: 1, args: 0 },
1718
+ };
1719
+
1720
+ const opName = name.slice('__Porffor_wasm_'.length);
1721
+
1722
+ if (wasmOps[opName]) {
1723
+ const op = wasmOps[opName];
1724
+
1725
+ const argOut = [];
1726
+ for (let i = 0; i < op.args; i++) argOut.push(...generate(scope, decl.arguments[i]));
1727
+
1728
+ // literals only
1729
+ const imms = decl.arguments.slice(op.args).map(x => x.value);
1730
+
1731
+ return [
1732
+ ...argOut,
1733
+ [ Opcodes[opName], ...imms ]
1734
+ ];
1735
+ }
1736
+ }
1737
+
1635
1738
  if (idx === undefined) {
1636
1739
  if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1637
1740
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
@@ -1641,7 +1744,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1641
1744
 
1642
1745
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1643
1746
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1644
- const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
1747
+ const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
1645
1748
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1646
1749
 
1647
1750
  let args = decl.arguments;
@@ -1658,14 +1761,20 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1658
1761
  if (func && func.throws) scope.throws = true;
1659
1762
 
1660
1763
  let out = [];
1661
- for (const arg of args) {
1764
+ for (let i = 0; i < args.length; i++) {
1765
+ const arg = args[i];
1662
1766
  out = out.concat(generate(scope, arg));
1767
+
1768
+ if (builtinFuncs[name] && builtinFuncs[name].params[i] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1769
+ out.push(Opcodes.i32_to);
1770
+ }
1771
+
1663
1772
  if (typedParams) out = out.concat(getNodeType(scope, arg));
1664
1773
  }
1665
1774
 
1666
1775
  out.push([ Opcodes.call, idx ]);
1667
1776
 
1668
- if (!typedReturn) {
1777
+ if (!typedReturns) {
1669
1778
  // let type;
1670
1779
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1671
1780
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1677,6 +1786,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1677
1786
  // );
1678
1787
  } else out.push(setLastType(scope));
1679
1788
 
1789
+ if (builtinFuncs[name] && builtinFuncs[name].returns[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1790
+ out.push(Opcodes.i32_from);
1791
+ }
1792
+
1680
1793
  return out;
1681
1794
  };
1682
1795
 
@@ -1801,14 +1914,14 @@ const brTable = (input, bc, returns) => {
1801
1914
  };
1802
1915
 
1803
1916
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1804
- if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
1917
+ if (!Prefs.bytestring) delete bc[TYPES._bytestring];
1805
1918
 
1806
1919
  const known = knownType(scope, type);
1807
1920
  if (known != null) {
1808
1921
  return bc[known] ?? bc.default;
1809
1922
  }
1810
1923
 
1811
- if (process.argv.includes('-typeswitch-use-brtable'))
1924
+ if (Prefs.typeswitchUseBrtable)
1812
1925
  return brTable(type, bc, returns);
1813
1926
 
1814
1927
  const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
@@ -1843,7 +1956,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1843
1956
  return out;
1844
1957
  };
1845
1958
 
1846
- const allocVar = (scope, name, global = false) => {
1959
+ const allocVar = (scope, name, global = false, type = true) => {
1847
1960
  const target = global ? globals : scope.locals;
1848
1961
 
1849
1962
  // already declared
@@ -1857,8 +1970,10 @@ const allocVar = (scope, name, global = false) => {
1857
1970
  let idx = global ? globalInd++ : scope.localInd++;
1858
1971
  target[name] = { idx, type: valtypeBinary };
1859
1972
 
1860
- let typeIdx = global ? globalInd++ : scope.localInd++;
1861
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
1973
+ if (type) {
1974
+ let typeIdx = global ? globalInd++ : scope.localInd++;
1975
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
1976
+ }
1862
1977
 
1863
1978
  return idx;
1864
1979
  };
@@ -1878,6 +1993,7 @@ const typeAnnoToPorfType = x => {
1878
1993
 
1879
1994
  switch (x) {
1880
1995
  case 'i32':
1996
+ case 'i64':
1881
1997
  return TYPES.number;
1882
1998
  }
1883
1999
 
@@ -1901,7 +2017,7 @@ const extractTypeAnnotation = decl => {
1901
2017
  const typeName = type;
1902
2018
  type = typeAnnoToPorfType(type);
1903
2019
 
1904
- if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
2020
+ if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
1905
2021
 
1906
2022
  // if (decl.name) console.log(decl.name, { type, elementType });
1907
2023
 
@@ -1936,7 +2052,7 @@ const generateVar = (scope, decl) => {
1936
2052
  continue; // always ignore
1937
2053
  }
1938
2054
 
1939
- let idx = allocVar(scope, name, global);
2055
+ let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
1940
2056
 
1941
2057
  if (typedInput && x.id.typeAnnotation) {
1942
2058
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
@@ -2533,6 +2649,9 @@ const generateThrow = (scope, decl) => {
2533
2649
  let exceptId = exceptions.push({ constructor, message }) - 1;
2534
2650
  let tagIdx = tags[0].idx;
2535
2651
 
2652
+ scope.exceptions ??= [];
2653
+ scope.exceptions.push(exceptId);
2654
+
2536
2655
  // todo: write a description of how this works lol
2537
2656
 
2538
2657
  return [
@@ -2577,7 +2696,7 @@ const generateAssignPat = (scope, decl) => {
2577
2696
  };
2578
2697
 
2579
2698
  let pages = new Map();
2580
- const allocPage = (reason, type) => {
2699
+ const allocPage = (scope, reason, type) => {
2581
2700
  if (pages.has(reason)) return pages.get(reason).ind;
2582
2701
 
2583
2702
  if (reason.startsWith('array:')) pages.hasArray = true;
@@ -2588,16 +2707,20 @@ const allocPage = (reason, type) => {
2588
2707
  const ind = pages.size;
2589
2708
  pages.set(reason, { ind, type });
2590
2709
 
2591
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2710
+ scope.pages ??= new Map();
2711
+ scope.pages.set(reason, { ind, type });
2712
+
2713
+ if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2592
2714
 
2593
2715
  return ind;
2594
2716
  };
2595
2717
 
2718
+ // todo: add scope.pages
2596
2719
  const freePage = reason => {
2597
2720
  const { ind } = pages.get(reason);
2598
2721
  pages.delete(reason);
2599
2722
 
2600
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2723
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2601
2724
 
2602
2725
  return ind;
2603
2726
  };
@@ -2623,15 +2746,14 @@ const StoreOps = {
2623
2746
 
2624
2747
  let data = [];
2625
2748
 
2626
- const compileBytes = (val, itemType, signed = true) => {
2749
+ const compileBytes = (val, itemType) => {
2627
2750
  // todo: this is a mess and needs confirming / ????
2628
2751
  switch (itemType) {
2629
2752
  case 'i8': return [ val % 256 ];
2630
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2631
-
2632
- case 'i32':
2633
- case 'i64':
2634
- return enforceFourBytes(signedLEB128(val));
2753
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
2754
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
2755
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
2756
+ // todo: i64
2635
2757
 
2636
2758
  case 'f64': return ieee754_binary64(val);
2637
2759
  }
@@ -2655,7 +2777,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2655
2777
 
2656
2778
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2657
2779
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2658
- arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2780
+ arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2659
2781
  }
2660
2782
 
2661
2783
  const pointer = arrays.get(name);
@@ -2666,19 +2788,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2666
2788
  const valtype = itemTypeToValtype[itemType];
2667
2789
  const length = elements.length;
2668
2790
 
2669
- if (firstAssign && useRawElements) {
2670
- let bytes = compileBytes(length, 'i32');
2791
+ if (firstAssign && useRawElements && !Prefs.noData) {
2792
+ // if length is 0 memory/data will just be 0000... anyway
2793
+ if (length !== 0) {
2794
+ let bytes = compileBytes(length, 'i32');
2671
2795
 
2672
- if (!initEmpty) for (let i = 0; i < length; i++) {
2673
- if (elements[i] == null) continue;
2796
+ if (!initEmpty) for (let i = 0; i < length; i++) {
2797
+ if (elements[i] == null) continue;
2674
2798
 
2675
- bytes.push(...compileBytes(elements[i], itemType));
2676
- }
2799
+ bytes.push(...compileBytes(elements[i], itemType));
2800
+ }
2677
2801
 
2678
- data.push({
2679
- offset: pointer,
2680
- bytes
2681
- });
2802
+ const ind = data.push({
2803
+ offset: pointer,
2804
+ bytes
2805
+ }) - 1;
2806
+
2807
+ scope.data ??= [];
2808
+ scope.data.push(ind);
2809
+ }
2682
2810
 
2683
2811
  // local value as pointer
2684
2812
  out.push(...number(pointer));
@@ -2712,7 +2840,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2712
2840
  };
2713
2841
 
2714
2842
  const byteStringable = str => {
2715
- if (!process.argv.includes('-bytestring')) return false;
2843
+ if (!Prefs.bytestring) return false;
2716
2844
 
2717
2845
  for (let i = 0; i < str.length; i++) {
2718
2846
  if (str.charCodeAt(i) > 0xFF) return false;
@@ -2721,9 +2849,9 @@ const byteStringable = str => {
2721
2849
  return true;
2722
2850
  };
2723
2851
 
2724
- const makeString = (scope, str, global = false, name = '$undeclared') => {
2852
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2725
2853
  const rawElements = new Array(str.length);
2726
- let byteStringable = process.argv.includes('-bytestring');
2854
+ let byteStringable = Prefs.bytestring;
2727
2855
  for (let i = 0; i < str.length; i++) {
2728
2856
  const c = str.charCodeAt(i);
2729
2857
  rawElements[i] = c;
@@ -2731,6 +2859,8 @@ const makeString = (scope, str, global = false, name = '$undeclared') => {
2731
2859
  if (byteStringable && c > 0xFF) byteStringable = false;
2732
2860
  }
2733
2861
 
2862
+ if (byteStringable && forceBytestring === false) byteStringable = false;
2863
+
2734
2864
  return makeArray(scope, {
2735
2865
  rawElements
2736
2866
  }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
@@ -2882,7 +3012,7 @@ const objectHack = node => {
2882
3012
  if (!objectName) return node;
2883
3013
 
2884
3014
  const name = '__' + objectName + '_' + node.property.name;
2885
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3015
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2886
3016
 
2887
3017
  return {
2888
3018
  type: 'Identifier',
@@ -2944,6 +3074,8 @@ const generateFunc = (scope, decl) => {
2944
3074
  };
2945
3075
  funcIndex[name] = func.index;
2946
3076
 
3077
+ if (name === 'main') func.gotLastType = true;
3078
+
2947
3079
  // quick hack fixes
2948
3080
  for (const inst of wasm) {
2949
3081
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -3109,7 +3241,7 @@ export default program => {
3109
3241
  body: program.body
3110
3242
  };
3111
3243
 
3112
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3244
+ if (Prefs.astLog) console.log(program.body.body);
3113
3245
 
3114
3246
  generateFunc(scope, program);
3115
3247