porffor 0.14.0-c6edfd328 → 0.14.0-cb572e8d4

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.
@@ -40,11 +40,11 @@ const todo = (scope, msg, expectsValue = undefined) => {
40
40
  }
41
41
  };
42
42
 
43
- const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
44
- const hasFuncWithName = name => {
45
- const func = funcs.find(x => x.name === name);
46
- return !!(func || builtinFuncs[name] || importedFuncs[name] || internalConstrs[name]);
47
- };
43
+ const isFuncType = type =>
44
+ type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
45
+ const hasFuncWithName = name =>
46
+ funcIndex[name] != null || builtinFuncs[name] != null || importedFuncs[name] != null || internalConstrs[name] != null;
47
+
48
48
  const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
49
49
  switch (decl.type) {
50
50
  case 'BinaryExpression':
@@ -147,14 +147,16 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
147
147
  return generateMember(scope, decl, global, name);
148
148
 
149
149
  case 'ExportNamedDeclaration':
150
- // hack to flag new func for export
151
- const funcsBefore = funcs.length;
150
+ const funcsBefore = funcs.map(x => x.name);
152
151
  generate(scope, decl.declaration);
153
152
 
154
- if (funcsBefore !== funcs.length) {
155
- // new func added
156
- const newFunc = funcs[funcs.length - 1];
157
- newFunc.export = true;
153
+ // set new funcs as exported
154
+ if (funcsBefore.length !== funcs.length) {
155
+ const newFuncs = funcs.filter(x => !funcsBefore.includes(x.name)).filter(x => !x.internal);
156
+
157
+ for (const x of newFuncs) {
158
+ x.export = true;
159
+ }
158
160
  }
159
161
 
160
162
  return [];
@@ -361,7 +363,7 @@ const localTmp = (scope, name, type = valtypeBinary) => {
361
363
  };
362
364
 
363
365
  const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
364
- const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
366
+ const isIntToFloatOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
365
367
 
366
368
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
367
369
  const checks = {
@@ -378,10 +380,10 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
378
380
 
379
381
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
380
382
  // (like if we are in an if condition - very common)
381
- const leftIsInt = isFloatToIntOp(left[left.length - 1]);
382
- const rightIsInt = isFloatToIntOp(right[right.length - 1]);
383
+ const leftWasInt = isIntToFloatOp(left[left.length - 1]);
384
+ const rightWasInt = isIntToFloatOp(right[right.length - 1]);
383
385
 
384
- const canInt = leftIsInt && rightIsInt;
386
+ const canInt = leftWasInt && rightWasInt;
385
387
 
386
388
  if (canInt) {
387
389
  // remove int -> float conversions from left and right
@@ -646,9 +648,9 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
646
648
  [ Opcodes.i32_add ],
647
649
  [ Opcodes.local_tee, index ],
648
650
 
649
- // if index != index end (length * sizeof valtype), loop
651
+ // if index < index end (length * sizeof valtype), loop
650
652
  [ Opcodes.local_get, indexEnd ],
651
- [ Opcodes.i32_ne ],
653
+ [ Opcodes.i32_lt_s ],
652
654
  [ Opcodes.br_if, 0 ],
653
655
  [ Opcodes.end ],
654
656
 
@@ -666,57 +668,50 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
666
668
  ];
667
669
  };
668
670
 
669
- const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
670
- if (isFloatToIntOp(wasm[wasm.length - 1])) return [
671
+ const truthy = (scope, wasm, type, intIn = false, intOut = false, forceTruthyMode = undefined) => {
672
+ if (isIntToFloatOp(wasm[wasm.length - 1])) return [
671
673
  ...wasm,
672
674
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
673
675
  ];
674
676
  // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
675
677
 
678
+ // todo/perf: use knownType and custom bytecode here instead of typeSwitch
679
+
676
680
  const useTmp = knownType(scope, type) == null;
677
681
  const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
678
682
 
679
- const def = [
680
- // if value != 0
681
- ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
683
+ const def = (truthyMode => {
684
+ if (truthyMode === 'full') return [
685
+ // if value != 0 or NaN
686
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
687
+ ...(intIn ? [ ] : [ Opcodes.i32_to ]),
688
+
689
+ [ Opcodes.i32_eqz ],
690
+ [ Opcodes.i32_eqz ],
682
691
 
683
- // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
684
- // [ Opcodes.i32_eqz ],
692
+ ...(intOut ? [] : [ Opcodes.i32_from ]),
693
+ ];
685
694
 
686
- // ...(intIn ? [
687
- // ...number(0, Valtype.i32),
688
- // [ Opcodes.i32_gt_s ]
689
- // ] : [
690
- // ...number(0),
691
- // [ Opcodes.f64_gt ]
692
- // ]),
693
-
694
- // ...(intOut ? [] : [ Opcodes.i32_from ]),
695
-
696
- // ...(intIn ? [] : [ Opcodes.i32_to ]),
697
- // [ Opcodes.if, intOut ? Valtype.i32 : valtypeBinary ],
698
- // ...number(1, intOut ? Valtype.i32 : valtypeBinary),
699
- // [ Opcodes.else ],
700
- // ...number(0, intOut ? Valtype.i32 : valtypeBinary),
701
- // [ Opcodes.end ],
702
-
703
- ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
704
-
705
- /* Opcodes.eqz,
706
- [ Opcodes.i32_eqz ],
707
- Opcodes.i32_from */
708
- ];
695
+ if (truthyMode === 'no_negative') return [
696
+ // if value != 0 or NaN, non-binary output. negative numbers not truthy :/
697
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
698
+ ...(intIn ? [] : [ Opcodes.i32_to ]),
699
+ ...(intOut ? [] : [ Opcodes.i32_from ])
700
+ ];
701
+
702
+ if (truthyMode === 'no_nan_negative') return [
703
+ // simpler and faster but makes NaN truthy and negative numbers not truthy,
704
+ // plus non-binary output
705
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
706
+ ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ])
707
+ ];
708
+ })(forceTruthyMode ?? Prefs.truthy ?? 'full');
709
709
 
710
710
  return [
711
711
  ...wasm,
712
712
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
713
713
 
714
714
  ...typeSwitch(scope, type, {
715
- // [TYPES.number]: def,
716
- [TYPES.array]: [
717
- // arrays are always truthy
718
- ...number(1, intOut ? Valtype.i32 : valtypeBinary)
719
- ],
720
715
  [TYPES.string]: [
721
716
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
722
717
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -752,10 +747,6 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
752
747
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
753
748
 
754
749
  ...typeSwitch(scope, type, {
755
- [TYPES.array]: [
756
- // arrays are always truthy
757
- ...number(0, intOut ? Valtype.i32 : valtypeBinary)
758
- ],
759
750
  [TYPES.string]: [
760
751
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
761
752
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -999,7 +990,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
999
990
  // if both are true
1000
991
  [ Opcodes.i32_and ],
1001
992
  [ Opcodes.if, Blocktype.void ],
1002
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
993
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], false),
1003
994
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1004
995
  [ Opcodes.br, 1 ],
1005
996
  [ Opcodes.end ],
@@ -1048,14 +1039,14 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
1048
1039
  return out;
1049
1040
  };
1050
1041
 
1051
- const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1052
- return func({ name, params, locals, returns, localInd }, {
1042
+ const asmFuncToAsm = (func, scope) => {
1043
+ return func(scope, {
1053
1044
  TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
1054
- builtin: name => {
1055
- let idx = funcIndex[name] ?? importedFuncs[name];
1056
- if (idx === undefined && builtinFuncs[name]) {
1057
- includeBuiltin(null, name);
1058
- idx = funcIndex[name];
1045
+ builtin: n => {
1046
+ let idx = funcIndex[n] ?? importedFuncs[n];
1047
+ if (idx == null && builtinFuncs[n]) {
1048
+ includeBuiltin(null, n);
1049
+ idx = funcIndex[n];
1059
1050
  }
1060
1051
 
1061
1052
  return idx;
@@ -1063,7 +1054,7 @@ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals =
1063
1054
  });
1064
1055
  };
1065
1056
 
1066
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false, callsSelf = false }) => {
1057
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false }) => {
1067
1058
  const existing = funcs.find(x => x.name === name);
1068
1059
  if (existing) return existing;
1069
1060
 
@@ -1081,7 +1072,22 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1081
1072
  data.push(copy);
1082
1073
  }
1083
1074
 
1084
- if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1075
+ const func = {
1076
+ name,
1077
+ params,
1078
+ locals,
1079
+ localInd: allLocals.length,
1080
+ returns,
1081
+ returnType: returnType ?? TYPES.number,
1082
+ internal: true,
1083
+ index: currentFuncIndex++,
1084
+ table
1085
+ };
1086
+
1087
+ funcs.push(func);
1088
+ funcIndex[name] = func.index;
1089
+
1090
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, func);
1085
1091
 
1086
1092
  let baseGlobalIdx, i = 0;
1087
1093
  for (const type of globalTypes) {
@@ -1100,23 +1106,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1100
1106
  }
1101
1107
  }
1102
1108
 
1103
- const func = {
1104
- name,
1105
- params,
1106
- locals,
1107
- returns,
1108
- returnType: returnType ?? TYPES.number,
1109
- wasm,
1110
- internal: true,
1111
- index: currentFuncIndex++
1112
- };
1113
-
1114
- if (callsSelf) for (const inst of wasm) {
1115
- if (inst[0] === Opcodes.call && inst[1] === -1) {
1116
- inst[1] = func.index;
1117
- }
1118
- }
1119
-
1120
1109
  if (table) for (const inst of wasm) {
1121
1110
  if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
1122
1111
  inst.splice(2, 99);
@@ -1124,10 +1113,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1124
1113
  }
1125
1114
  }
1126
1115
 
1127
- funcs.push(func);
1128
- funcIndex[name] = func.index;
1129
-
1130
- if (table) funcs.table = true;
1116
+ func.wasm = wasm;
1131
1117
 
1132
1118
  return func;
1133
1119
  };
@@ -1479,16 +1465,11 @@ const countLeftover = wasm => {
1479
1465
  else if (inst[0] === Opcodes.return) count = 0;
1480
1466
  else if (inst[0] === Opcodes.call) {
1481
1467
  let func = funcs.find(x => x.index === inst[1]);
1482
- if (inst[1] === -1) {
1483
- // todo: count for calling self
1484
- } else if (!func && inst[1] < importedFuncs.length) {
1485
- count -= importedFuncs[inst[1]].params;
1486
- count += importedFuncs[inst[1]].returns;
1468
+ if (inst[1] < importedFuncs.length) {
1469
+ func = importedFuncs[inst[1]];
1470
+ count = count - func.params + func.returns;
1487
1471
  } else {
1488
- if (func) {
1489
- count -= func.params.length;
1490
- } else count--;
1491
- if (func) count += func.returns.length;
1472
+ count = count - func.params.length + func.returns.length;
1492
1473
  }
1493
1474
  } else if (inst[0] === Opcodes.call_indirect) {
1494
1475
  count--; // funcidx
@@ -1641,6 +1622,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1641
1622
 
1642
1623
  if (!funcIndex[rhemynName]) {
1643
1624
  const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1625
+ func.internal = true;
1644
1626
 
1645
1627
  funcIndex[func.name] = func.index;
1646
1628
  funcs.push(func);
@@ -1812,11 +1794,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1812
1794
 
1813
1795
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1814
1796
 
1815
- if (idx === undefined && name === scope.name) {
1816
- // hack: calling self, func generator will fix later
1817
- idx = -1;
1818
- }
1819
-
1820
1797
  if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1821
1798
  const wasmOps = {
1822
1799
  // pointer, align, offset
@@ -1998,11 +1975,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1998
1975
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1999
1976
  }
2000
1977
 
2001
- const func = funcs.find(x => x.index === idx);
2002
-
2003
- const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1978
+ const func = funcs[idx - importedFuncs.length]; // idx === scope.index ? scope : funcs.find(x => x.index === idx);
1979
+ const userFunc = func && !func.internal;
2004
1980
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
2005
- const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1981
+ const typedReturns = (func && func.returnType == null) || builtinFuncs[name]?.typedReturns;
2006
1982
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
2007
1983
 
2008
1984
  let args = decl.arguments;
@@ -2605,6 +2581,11 @@ const generateUnary = (scope, decl) => {
2605
2581
  ];
2606
2582
 
2607
2583
  case '!':
2584
+ const arg = decl.argument;
2585
+ if (arg.type === "UnaryExpression" && arg.operator === "!") {
2586
+ // !!x -> is x truthy
2587
+ return truthy(scope, generate(scope, arg.argument), getNodeType(scope, arg.argument), false, false);
2588
+ }
2608
2589
  // !=
2609
2590
  return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
2610
2591
 
@@ -3043,6 +3024,44 @@ const generateForOf = (scope, decl) => {
3043
3024
  [ Opcodes.end ],
3044
3025
  [ Opcodes.end ]
3045
3026
  ],
3027
+ [TYPES.set]: [
3028
+ [ Opcodes.loop, Blocktype.void ],
3029
+
3030
+ [ Opcodes.local_get, pointer ],
3031
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
3032
+
3033
+ ...setType(scope, leftName, [
3034
+ [ Opcodes.local_get, pointer ],
3035
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
3036
+ ]),
3037
+
3038
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
3039
+
3040
+ [ Opcodes.block, Blocktype.void ],
3041
+ [ Opcodes.block, Blocktype.void ],
3042
+ ...generate(scope, decl.body),
3043
+ [ Opcodes.end ],
3044
+
3045
+ // increment iter pointer by valtype size + 1
3046
+ [ Opcodes.local_get, pointer ],
3047
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3048
+ [ Opcodes.i32_add ],
3049
+ [ Opcodes.local_set, pointer ],
3050
+
3051
+ // increment counter by 1
3052
+ [ Opcodes.local_get, counter ],
3053
+ ...number(1, Valtype.i32),
3054
+ [ Opcodes.i32_add ],
3055
+ [ Opcodes.local_tee, counter ],
3056
+
3057
+ // loop if counter != length
3058
+ [ Opcodes.local_get, length ],
3059
+ [ Opcodes.i32_ne ],
3060
+ [ Opcodes.br_if, 1 ],
3061
+
3062
+ [ Opcodes.end ],
3063
+ [ Opcodes.end ]
3064
+ ],
3046
3065
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
3047
3066
  }, Blocktype.void));
3048
3067
 
@@ -3144,14 +3163,18 @@ const generateThrow = (scope, decl) => {
3144
3163
  };
3145
3164
 
3146
3165
  const generateTry = (scope, decl) => {
3147
- if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
3166
+ // todo: handle control-flow pre-exit for finally
3167
+ // "Immediately before a control-flow statement (return, throw, break, continue) is executed in the try block or catch block."
3148
3168
 
3149
3169
  const out = [];
3150
3170
 
3171
+ const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
3172
+
3151
3173
  out.push([ Opcodes.try, Blocktype.void ]);
3152
3174
  depth.push('try');
3153
3175
 
3154
3176
  out.push(...generate(scope, decl.block));
3177
+ out.push(...finalizer);
3155
3178
 
3156
3179
  if (decl.handler) {
3157
3180
  depth.pop();
@@ -3159,6 +3182,7 @@ const generateTry = (scope, decl) => {
3159
3182
 
3160
3183
  out.push([ Opcodes.catch_all ]);
3161
3184
  out.push(...generate(scope, decl.handler.body));
3185
+ out.push(...finalizer);
3162
3186
  }
3163
3187
 
3164
3188
  out.push([ Opcodes.end ]);
@@ -3256,8 +3280,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3256
3280
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
3257
3281
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
3258
3282
 
3259
- if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
3260
- else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3283
+ let page;
3284
+ if (Prefs.scopedPageNames) page = allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType);
3285
+ else page = allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType);
3286
+
3287
+ // hack: use 1 for page 0 pointer for fast truthiness
3288
+ const ptr = page === 0 ? 1 : (page * pageSize);
3289
+ scope.arrays.set(name, ptr);
3261
3290
  }
3262
3291
 
3263
3292
  const pointer = scope.arrays.get(name);
@@ -3466,8 +3495,7 @@ const generateMember = (scope, decl, _global, _name) => {
3466
3495
  if (decl.property.name === 'length') {
3467
3496
  const func = funcs.find(x => x.name === name);
3468
3497
  if (func) {
3469
- const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3470
- const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3498
+ const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
3471
3499
  return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3472
3500
  }
3473
3501
 
@@ -3693,34 +3721,39 @@ const generateFunc = (scope, decl) => {
3693
3721
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3694
3722
  const params = decl.params ?? [];
3695
3723
 
3696
- // const innerScope = { ...scope };
3697
3724
  // TODO: share scope/locals between !!!
3698
- const innerScope = {
3725
+ const func = {
3699
3726
  locals: {},
3700
3727
  localInd: 0,
3701
3728
  // value, type
3702
3729
  returns: [ valtypeBinary, Valtype.i32 ],
3703
3730
  throws: false,
3704
- name
3731
+ name,
3732
+ index: currentFuncIndex++
3705
3733
  };
3706
3734
 
3707
3735
  if (typedInput && decl.returnType) {
3708
3736
  const { type } = extractTypeAnnotation(decl.returnType);
3709
3737
  // if (type != null && !Prefs.indirectCalls) {
3710
3738
  if (type != null) {
3711
- innerScope.returnType = type;
3712
- innerScope.returns = [ valtypeBinary ];
3739
+ func.returnType = type;
3740
+ func.returns = [ valtypeBinary ];
3713
3741
  }
3714
3742
  }
3715
3743
 
3716
3744
  for (let i = 0; i < params.length; i++) {
3717
- allocVar(innerScope, params[i].name, false);
3745
+ const name = params[i].name;
3746
+ // if (name == null) return todo('non-identifier args are not supported');
3747
+
3748
+ allocVar(func, name, false);
3718
3749
 
3719
3750
  if (typedInput && params[i].typeAnnotation) {
3720
- addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
3751
+ addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
3721
3752
  }
3722
3753
  }
3723
3754
 
3755
+ func.params = Object.values(func.locals).map(x => x.type);
3756
+
3724
3757
  let body = objectHack(decl.body);
3725
3758
  if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
3726
3759
  // hack: () => 0 -> () => return 0
@@ -3730,37 +3763,23 @@ const generateFunc = (scope, decl) => {
3730
3763
  };
3731
3764
  }
3732
3765
 
3733
- const wasm = generate(innerScope, body);
3734
- const func = {
3735
- name,
3736
- params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
3737
- index: currentFuncIndex++,
3738
- ...innerScope
3739
- };
3740
3766
  funcIndex[name] = func.index;
3767
+ funcs.push(func);
3741
3768
 
3742
- if (name === 'main') func.gotLastType = true;
3769
+ const wasm = generate(func, body);
3770
+ func.wasm = wasm;
3743
3771
 
3744
- // quick hack fixes
3745
- for (const inst of wasm) {
3746
- if (inst[0] === Opcodes.call && inst[1] === -1) {
3747
- inst[1] = func.index;
3748
- }
3749
- }
3772
+ if (name === 'main') func.gotLastType = true;
3750
3773
 
3751
3774
  // add end return if not found
3752
3775
  if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
3753
3776
  wasm.push(
3754
3777
  ...number(0),
3755
- ...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3778
+ ...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3756
3779
  [ Opcodes.return ]
3757
3780
  );
3758
3781
  }
3759
3782
 
3760
- func.wasm = wasm;
3761
-
3762
- funcs.push(func);
3763
-
3764
3783
  return func;
3765
3784
  };
3766
3785
 
@@ -3864,7 +3883,7 @@ const internalConstrs = {
3864
3883
  generate: (scope, decl) => {
3865
3884
  // todo: boolean object when used as constructor
3866
3885
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3867
- return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3886
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg), false, false, 'full');
3868
3887
  },
3869
3888
  type: TYPES.boolean,
3870
3889
  length: 1
@@ -4009,9 +4028,8 @@ export default program => {
4009
4028
 
4010
4029
  if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
4011
4030
 
4012
- generateFunc(scope, program);
4031
+ const main = generateFunc(scope, program);
4013
4032
 
4014
- const main = funcs[funcs.length - 1];
4015
4033
  main.export = true;
4016
4034
  main.returns = [ valtypeBinary, Valtype.i32 ];
4017
4035
 
@@ -4038,7 +4056,7 @@ export default program => {
4038
4056
  }
4039
4057
 
4040
4058
  // if blank main func and other exports, remove it
4041
- if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
4059
+ if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(main.index - importedFuncs.length, 1);
4042
4060
 
4043
4061
  return { funcs, globals, tags, exceptions, pages, data };
4044
4062
  };
@@ -89,6 +89,10 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
89
89
  if (inst[0] === Opcodes.call || inst[0] === Opcodes.return_call) {
90
90
  const callFunc = funcs.find(x => x.index === inst[1]);
91
91
  if (callFunc) out += ` ;; $${callFunc.name} ${makeSignature(callFunc.params, callFunc.returns)}`;
92
+ if (globalThis.importFuncs && inst[1] < importFuncs.length) {
93
+ const importFunc = importFuncs[inst[1]];
94
+ out += ` ;; import ${importFunc.name} ${makeSignature(new Array(importFunc.params).fill(valtypeBinary), new Array(importFunc.returns).fill(valtypeBinary),)}`;
95
+ }
92
96
  }
93
97
 
94
98
  if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set || inst[0] === Opcodes.local_tee) {