porffor 0.14.0-f67c123a1 → 0.16.0-594397507

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':
@@ -140,18 +140,23 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
140
140
  case 'ArrayExpression':
141
141
  return generateArray(scope, decl, global, name);
142
142
 
143
+ case 'ObjectExpression':
144
+ return generateObject(scope, decl, global, name);
145
+
143
146
  case 'MemberExpression':
144
147
  return generateMember(scope, decl, global, name);
145
148
 
146
149
  case 'ExportNamedDeclaration':
147
- // hack to flag new func for export
148
- const funcsBefore = funcs.length;
150
+ const funcsBefore = funcs.map(x => x.name);
149
151
  generate(scope, decl.declaration);
150
152
 
151
- if (funcsBefore !== funcs.length) {
152
- // new func added
153
- const newFunc = funcs[funcs.length - 1];
154
- 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
+ }
155
160
  }
156
161
 
157
162
  return [];
@@ -200,19 +205,11 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
200
205
 
201
206
  __Porffor_bs: str => [
202
207
  ...makeString(scope, str, global, name, true),
203
-
204
- ...(name ? setType(scope, name, TYPES.bytestring) : [
205
- ...number(TYPES.bytestring, Valtype.i32),
206
- ...setLastType(scope)
207
- ])
208
+ ...(name ? setType(scope, name, TYPES.bytestring) : setLastType(scope, TYPES.bytestring))
208
209
  ],
209
210
  __Porffor_s: str => [
210
211
  ...makeString(scope, str, global, name, false),
211
-
212
- ...(name ? setType(scope, name, TYPES.string) : [
213
- ...number(TYPES.string, Valtype.i32),
214
- ...setLastType(scope)
215
- ])
212
+ ...(name ? setType(scope, name, TYPES.string) : setLastType(scope, TYPES.string))
216
213
  ],
217
214
  };
218
215
 
@@ -351,9 +348,7 @@ const generateReturn = (scope, decl) => {
351
348
 
352
349
  return [
353
350
  ...generate(scope, decl.argument),
354
- ...(scope.returnType != null ? [] : [
355
- ...getNodeType(scope, decl.argument)
356
- ]),
351
+ ...(scope.returnType != null ? [] : getNodeType(scope, decl.argument)),
357
352
  [ Opcodes.return ]
358
353
  ];
359
354
  };
@@ -368,7 +363,7 @@ const localTmp = (scope, name, type = valtypeBinary) => {
368
363
  };
369
364
 
370
365
  const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
371
- const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
366
+ const isIntToFloatOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
372
367
 
373
368
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
374
369
  const checks = {
@@ -385,10 +380,10 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
385
380
 
386
381
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
387
382
  // (like if we are in an if condition - very common)
388
- const leftIsInt = isFloatToIntOp(left[left.length - 1]);
389
- const rightIsInt = isFloatToIntOp(right[right.length - 1]);
383
+ const leftWasInt = isIntToFloatOp(left[left.length - 1]);
384
+ const rightWasInt = isIntToFloatOp(right[right.length - 1]);
390
385
 
391
- const canInt = leftIsInt && rightIsInt;
386
+ const canInt = leftWasInt && rightWasInt;
392
387
 
393
388
  if (canInt) {
394
389
  // remove int -> float conversions from left and right
@@ -402,13 +397,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
402
397
  [ Opcodes.if, Valtype.i32 ],
403
398
  ...right,
404
399
  // note type
405
- ...rightType,
406
- ...setLastType(scope),
400
+ ...setLastType(scope, rightType),
407
401
  [ Opcodes.else ],
408
402
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
409
403
  // note type
410
- ...leftType,
411
- ...setLastType(scope),
404
+ ...setLastType(scope, leftType),
412
405
  [ Opcodes.end ],
413
406
  Opcodes.i32_from
414
407
  ];
@@ -421,13 +414,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
421
414
  [ Opcodes.if, valtypeBinary ],
422
415
  ...right,
423
416
  // note type
424
- ...rightType,
425
- ...setLastType(scope),
417
+ ...setLastType(scope, rightType),
426
418
  [ Opcodes.else ],
427
419
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
428
420
  // note type
429
- ...leftType,
430
- ...setLastType(scope),
421
+ ...setLastType(scope, leftType),
431
422
  [ Opcodes.end ]
432
423
  ];
433
424
  };
@@ -455,11 +446,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
455
446
  ...number(0, Valtype.i32), // base 0 for store later
456
447
 
457
448
  ...number(pointer, Valtype.i32),
458
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
449
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
459
450
  [ Opcodes.local_tee, leftLength ],
460
451
 
461
452
  [ Opcodes.local_get, rightPointer ],
462
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
453
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
463
454
  [ Opcodes.local_tee, rightLength ],
464
455
 
465
456
  [ Opcodes.i32_add ],
@@ -515,11 +506,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
515
506
  ...number(0, Valtype.i32), // base 0 for store later
516
507
 
517
508
  [ Opcodes.local_get, leftPointer ],
518
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
509
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
519
510
  [ Opcodes.local_tee, leftLength ],
520
511
 
521
512
  [ Opcodes.local_get, rightPointer ],
522
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
513
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
523
514
  [ Opcodes.local_tee, rightLength ],
524
515
 
525
516
  [ Opcodes.i32_add ],
@@ -597,11 +588,11 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
597
588
 
598
589
  // get lengths
599
590
  [ Opcodes.local_get, leftPointer ],
600
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
591
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
601
592
  [ Opcodes.local_tee, leftLength ],
602
593
 
603
594
  [ Opcodes.local_get, rightPointer ],
604
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
595
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
605
596
 
606
597
  // fast path: check leftLength != rightLength
607
598
  [ Opcodes.i32_ne ],
@@ -657,9 +648,9 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
657
648
  [ Opcodes.i32_add ],
658
649
  [ Opcodes.local_tee, index ],
659
650
 
660
- // if index != index end (length * sizeof valtype), loop
651
+ // if index < index end (length * sizeof valtype), loop
661
652
  [ Opcodes.local_get, indexEnd ],
662
- [ Opcodes.i32_ne ],
653
+ [ Opcodes.i32_lt_s ],
663
654
  [ Opcodes.br_if, 0 ],
664
655
  [ Opcodes.end ],
665
656
 
@@ -677,38 +668,50 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
677
668
  ];
678
669
  };
679
670
 
680
- const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
681
- 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 [
682
673
  ...wasm,
683
674
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
684
675
  ];
685
676
  // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
686
677
 
678
+ // todo/perf: use knownType and custom bytecode here instead of typeSwitch
679
+
687
680
  const useTmp = knownType(scope, type) == null;
688
681
  const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
689
682
 
690
- const def = [
691
- // if value != 0
692
- ...(!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 ]),
693
688
 
694
- // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
695
- ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
689
+ [ Opcodes.i32_eqz ],
690
+ [ Opcodes.i32_eqz ],
696
691
 
697
- /* Opcodes.eqz,
698
- [ Opcodes.i32_eqz ],
699
- Opcodes.i32_from */
700
- ];
692
+ ...(intOut ? [] : [ Opcodes.i32_from ]),
693
+ ];
694
+
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');
701
709
 
702
710
  return [
703
711
  ...wasm,
704
712
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
705
713
 
706
714
  ...typeSwitch(scope, type, {
707
- // [TYPES.number]: def,
708
- [TYPES.array]: [
709
- // arrays are always truthy
710
- ...number(1, intOut ? Valtype.i32 : valtypeBinary)
711
- ],
712
715
  [TYPES.string]: [
713
716
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
714
717
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -744,10 +747,6 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
744
747
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
745
748
 
746
749
  ...typeSwitch(scope, type, {
747
- [TYPES.array]: [
748
- // arrays are always truthy
749
- ...number(0, intOut ? Valtype.i32 : valtypeBinary)
750
- ],
751
750
  [TYPES.string]: [
752
751
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
753
752
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -991,7 +990,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
991
990
  // if both are true
992
991
  [ Opcodes.i32_and ],
993
992
  [ Opcodes.if, Blocktype.void ],
994
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
993
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], false),
995
994
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
996
995
  [ Opcodes.br, 1 ],
997
996
  [ Opcodes.end ],
@@ -1040,14 +1039,14 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
1040
1039
  return out;
1041
1040
  };
1042
1041
 
1043
- const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1044
- return func({ name, params, locals, returns, localInd }, {
1042
+ const asmFuncToAsm = (func, scope) => {
1043
+ return func(scope, {
1045
1044
  TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
1046
- builtin: name => {
1047
- let idx = funcIndex[name] ?? importedFuncs[name];
1048
- if (idx === undefined && builtinFuncs[name]) {
1049
- includeBuiltin(null, name);
1050
- 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];
1051
1050
  }
1052
1051
 
1053
1052
  return idx;
@@ -1055,7 +1054,7 @@ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals =
1055
1054
  });
1056
1055
  };
1057
1056
 
1058
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
1057
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false }) => {
1059
1058
  const existing = funcs.find(x => x.name === name);
1060
1059
  if (existing) return existing;
1061
1060
 
@@ -1073,7 +1072,22 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1073
1072
  data.push(copy);
1074
1073
  }
1075
1074
 
1076
- 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);
1077
1091
 
1078
1092
  let baseGlobalIdx, i = 0;
1079
1093
  for (const type of globalTypes) {
@@ -1092,19 +1106,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1092
1106
  }
1093
1107
  }
1094
1108
 
1095
- const func = {
1096
- name,
1097
- params,
1098
- locals,
1099
- returns,
1100
- returnType: returnType ?? TYPES.number,
1101
- wasm,
1102
- internal: true,
1103
- index: currentFuncIndex++
1104
- };
1109
+ if (table) for (const inst of wasm) {
1110
+ if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
1111
+ inst.splice(2, 99);
1112
+ inst.push(...unsignedLEB128(allocPage({}, 'func argc lut') * pageSize));
1113
+ }
1114
+ }
1105
1115
 
1106
- funcs.push(func);
1107
- funcIndex[name] = func.index;
1116
+ func.wasm = wasm;
1108
1117
 
1109
1118
  return func;
1110
1119
  };
@@ -1196,9 +1205,10 @@ const getLastType = scope => {
1196
1205
  return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1197
1206
  };
1198
1207
 
1199
- const setLastType = scope => {
1200
- return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1201
- };
1208
+ const setLastType = (scope, type = []) => [
1209
+ ...(typeof type === 'number' ? number(type, Valtype.i32) : type),
1210
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1211
+ ];
1202
1212
 
1203
1213
  const getNodeType = (scope, node) => {
1204
1214
  const ret = (() => {
@@ -1259,7 +1269,17 @@ const getNodeType = (scope, node) => {
1259
1269
 
1260
1270
  const func = spl[spl.length - 1];
1261
1271
  const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
1262
- if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1272
+ if (protoFuncs.length === 1) {
1273
+ if (protoFuncs[0].returnType) return protoFuncs[0].returnType;
1274
+ }
1275
+
1276
+ if (protoFuncs.length > 0) {
1277
+ if (scope.locals['#last_type']) return getLastType(scope);
1278
+
1279
+ // presume
1280
+ // todo: warn here?
1281
+ return TYPES.number;
1282
+ }
1263
1283
  }
1264
1284
 
1265
1285
  if (name.startsWith('__Porffor_wasm_')) {
@@ -1354,22 +1374,27 @@ const getNodeType = (scope, node) => {
1354
1374
  }
1355
1375
 
1356
1376
  if (node.type === 'MemberExpression') {
1357
- // hack: if something.name, string type
1358
- if (node.property.name === 'name') {
1359
- if (hasFuncWithName(node.object.name)) {
1360
- return TYPES.bytestring;
1361
- } else {
1362
- return TYPES.undefined;
1363
- }
1377
+ const name = node.property.name;
1378
+
1379
+ if (name === 'length') {
1380
+ if (hasFuncWithName(node.object.name)) return TYPES.number;
1381
+ if (Prefs.fastLength) return TYPES.number;
1364
1382
  }
1365
1383
 
1366
- // hack: if something.length, number type
1367
- if (node.property.name === 'length') return TYPES.number;
1368
1384
 
1369
- // ts hack
1370
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1371
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.bytestring) return TYPES.bytestring;
1372
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.array) return TYPES.number;
1385
+ const objectKnownType = knownType(scope, getNodeType(scope, node.object));
1386
+ if (objectKnownType != null) {
1387
+ if (name === 'length') {
1388
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(objectKnownType)) return TYPES.number;
1389
+ else return TYPES.undefined;
1390
+ }
1391
+
1392
+ if (node.computed) {
1393
+ if (objectKnownType === TYPES.string) return TYPES.string;
1394
+ if (objectKnownType === TYPES.bytestring) return TYPES.bytestring;
1395
+ if (objectKnownType === TYPES.array) return TYPES.number;
1396
+ }
1397
+ }
1373
1398
 
1374
1399
  if (scope.locals['#last_type']) return getLastType(scope);
1375
1400
 
@@ -1440,17 +1465,16 @@ const countLeftover = wasm => {
1440
1465
  else if (inst[0] === Opcodes.return) count = 0;
1441
1466
  else if (inst[0] === Opcodes.call) {
1442
1467
  let func = funcs.find(x => x.index === inst[1]);
1443
- if (inst[1] === -1) {
1444
- // todo: count for calling self
1445
- } else if (!func && inst[1] < importedFuncs.length) {
1446
- count -= importedFuncs[inst[1]].params;
1447
- count += importedFuncs[inst[1]].returns;
1468
+ if (inst[1] < importedFuncs.length) {
1469
+ func = importedFuncs[inst[1]];
1470
+ count = count - func.params + func.returns;
1448
1471
  } else {
1449
- if (func) {
1450
- count -= func.params.length;
1451
- } else count--;
1452
- if (func) count += func.returns.length;
1472
+ count = count - func.params.length + func.returns.length;
1453
1473
  }
1474
+ } else if (inst[0] === Opcodes.call_indirect) {
1475
+ count--; // funcidx
1476
+ count -= inst[1] * 2; // params * 2 (typed)
1477
+ count += 2; // fixed return (value, type)
1454
1478
  } else count--;
1455
1479
 
1456
1480
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1468,7 +1492,7 @@ const disposeLeftover = wasm => {
1468
1492
  const generateExp = (scope, decl) => {
1469
1493
  const expression = decl.expression;
1470
1494
 
1471
- const out = generate(scope, expression, undefined, undefined, true);
1495
+ const out = generate(scope, expression, undefined, undefined, Prefs.optUnused);
1472
1496
  disposeLeftover(out);
1473
1497
 
1474
1498
  return out;
@@ -1559,16 +1583,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1559
1583
  out.splice(out.length - 1, 1);
1560
1584
 
1561
1585
  const finalStatement = parsed.body[parsed.body.length - 1];
1562
- out.push(
1563
- ...getNodeType(scope, finalStatement),
1564
- ...setLastType(scope)
1565
- );
1586
+ out.push(...setLastType(scope, getNodeType(scope, finalStatement)));
1566
1587
  } else if (countLeftover(out) === 0) {
1567
1588
  out.push(...number(UNDEFINED));
1568
- out.push(
1569
- ...number(TYPES.undefined, Valtype.i32),
1570
- ...setLastType(scope)
1571
- );
1589
+ out.push(...setLastType(scope, TYPES.undefined));
1572
1590
  }
1573
1591
 
1574
1592
  // if (lastInst && lastInst[0] === Opcodes.drop) {
@@ -1604,6 +1622,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1604
1622
 
1605
1623
  if (!funcIndex[rhemynName]) {
1606
1624
  const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1625
+ func.internal = true;
1607
1626
 
1608
1627
  funcIndex[func.name] = func.index;
1609
1628
  funcs.push(func);
@@ -1620,8 +1639,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1620
1639
  [ Opcodes.call, idx ],
1621
1640
  Opcodes.i32_from_u,
1622
1641
 
1623
- ...number(TYPES.boolean, Valtype.i32),
1624
- ...setLastType(scope)
1642
+ ...setLastType(scope, TYPES.boolean)
1625
1643
  ];
1626
1644
  }
1627
1645
 
@@ -1693,9 +1711,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1693
1711
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
1694
1712
  protoBC[x] = [
1695
1713
  ...RTArrayUtil.getLength(getPointer),
1696
-
1697
- ...number(TYPES.number, Valtype.i32),
1698
- ...setLastType(scope)
1714
+ ...setLastType(scope, TYPES.number)
1699
1715
  ];
1700
1716
  continue;
1701
1717
  }
@@ -1714,7 +1730,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1714
1730
  getI32: () => RTArrayUtil.getLengthI32(getPointer),
1715
1731
  set: value => RTArrayUtil.setLength(getPointer, value),
1716
1732
  setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
1717
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1733
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), getNodeType(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1718
1734
  return makeArray(scope, {
1719
1735
  rawElements: new Array(length)
1720
1736
  }, _global, _name, true, itemType);
@@ -1728,9 +1744,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1728
1744
  protoBC[x] = [
1729
1745
  [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1730
1746
  ...protoOut,
1731
-
1732
- ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1733
- ...setLastType(scope),
1747
+ ...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
1734
1748
  [ Opcodes.end ]
1735
1749
  ];
1736
1750
  }
@@ -1780,11 +1794,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1780
1794
 
1781
1795
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1782
1796
 
1783
- if (idx === undefined && name === scope.name) {
1784
- // hack: calling self, func generator will fix later
1785
- idx = -1;
1786
- }
1787
-
1788
1797
  if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1789
1798
  const wasmOps = {
1790
1799
  // pointer, align, offset
@@ -1833,30 +1842,143 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1833
1842
 
1834
1843
  if (idx === undefined) {
1835
1844
  if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) {
1836
- if (!Prefs.indirectCalls) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1837
-
1838
1845
  const [ local, global ] = lookupName(scope, name);
1846
+ if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1847
+
1848
+ // todo: only works when function uses typedParams and typedReturns
1849
+
1850
+ const indirectMode = Prefs.indirectCallMode ?? 'vararg';
1851
+ // options: vararg, strict
1852
+ // - strict: simpler, smaller size usage, no func argc lut needed.
1853
+ // ONLY works when arg count of call == arg count of function being called
1854
+ // - vararg: large size usage, cursed.
1855
+ // works when arg count of call != arg count of function being called*
1856
+ // * most of the time, some edgecases
1857
+
1839
1858
  funcs.table = true;
1859
+ scope.table = true;
1860
+
1861
+ let args = decl.arguments;
1862
+ let out = [];
1863
+
1864
+ let locals = [];
1865
+
1866
+ if (indirectMode === 'vararg') {
1867
+ const minArgc = Prefs.indirectCallMinArgc ?? 3;
1868
+
1869
+ if (args.length < minArgc) {
1870
+ args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
1871
+ }
1872
+ }
1873
+
1874
+ for (let i = 0; i < args.length; i++) {
1875
+ const arg = args[i];
1876
+ out = out.concat(generate(scope, arg));
1877
+
1878
+ if (valtypeBinary !== Valtype.i32 && (
1879
+ (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1880
+ (importedFuncs[name] && name.startsWith('profile'))
1881
+ )) {
1882
+ out.push(Opcodes.i32_to);
1883
+ }
1884
+
1885
+ out = out.concat(getNodeType(scope, arg));
1886
+
1887
+ if (indirectMode === 'vararg') {
1888
+ const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
1889
+ const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
1890
+
1891
+ locals.push([valLocal, typeLocal]);
1892
+
1893
+ out.push(
1894
+ [ Opcodes.local_set, typeLocal ],
1895
+ [ Opcodes.local_set, valLocal ]
1896
+ );
1897
+ }
1898
+ }
1899
+
1900
+ if (indirectMode === 'strict') {
1901
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1902
+ [TYPES.function]: [
1903
+ ...argWasm,
1904
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1905
+ Opcodes.i32_to_u,
1906
+ [ Opcodes.call_indirect, args.length, 0 ],
1907
+ ...setLastType(scope)
1908
+ ],
1909
+ default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1910
+ });
1911
+ }
1912
+
1913
+ // hi, I will now explain how vararg mode works:
1914
+ // wasm's indirect_call instruction requires you know the func type at compile-time
1915
+ // since we have varargs (variable argument count), we do not know it.
1916
+ // we could just store args in memory and not use wasm func args,
1917
+ // but that is slow (probably) and breaks js exports.
1918
+ // instead, we generate every* possibility of argc and use different indirect_call
1919
+ // ops for each one, with type depending on argc for that branch.
1920
+ // then we load the argc for the wanted function from a memory lut,
1921
+ // and call the branch with the matching argc we require.
1922
+ // sorry, yes it is very cursed (and size inefficient), but indirect calls
1923
+ // are kind of rare anyway (mostly callbacks) so I am not concerned atm.
1924
+ // *for argc 0-3, in future (todo:) the max number should be
1925
+ // dynamically changed to the max argc of any func in the js file.
1926
+
1927
+ const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
1928
+
1929
+ const gen = argc => {
1930
+ const out = [];
1931
+ for (let i = 0; i < argc; i++) {
1932
+ out.push(
1933
+ [ Opcodes.local_get, locals[i][0] ],
1934
+ [ Opcodes.local_get, locals[i][1] ]
1935
+ );
1936
+ }
1937
+
1938
+ out.push(
1939
+ [ Opcodes.local_get, funcLocal ],
1940
+ [ Opcodes.call_indirect, argc, 0 ],
1941
+ ...setLastType(scope)
1942
+ )
1943
+
1944
+ return out;
1945
+ };
1946
+
1947
+ const tableBc = {};
1948
+ for (let i = 0; i <= args.length; i++) {
1949
+ tableBc[i] = gen(i);
1950
+ }
1840
1951
 
1841
- // todo: only works when:
1842
- // 1. arg count matches arg count of function
1843
- // 2. function uses typedParams and typedReturns
1844
- return typeSwitch(scope, getNodeType(decl.callee), {
1952
+ // todo/perf: check if we should use br_table here or just generate our own big if..elses
1953
+
1954
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1845
1955
  [TYPES.function]: [
1956
+ ...out,
1957
+
1846
1958
  [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1847
- [ Opcodes.call_indirect ]
1959
+ Opcodes.i32_to_u,
1960
+ [ Opcodes.local_set, funcLocal ],
1961
+
1962
+ ...brTable([
1963
+ // get argc of func we are calling
1964
+ [ Opcodes.local_get, funcLocal ],
1965
+ ...number(ValtypeSize.i16, Valtype.i32),
1966
+ [ Opcodes.i32_mul ],
1967
+
1968
+ [ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
1969
+ ], tableBc, valtypeBinary)
1848
1970
  ],
1849
1971
  default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1850
1972
  });
1851
1973
  }
1974
+
1852
1975
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1853
1976
  }
1854
1977
 
1855
- const func = funcs.find(x => x.index === idx);
1856
-
1857
- 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;
1858
1980
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1859
- const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1981
+ const typedReturns = (func && func.returnType == null) || builtinFuncs[name]?.typedReturns;
1860
1982
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1861
1983
 
1862
1984
  let args = decl.arguments;
@@ -1877,6 +1999,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1877
1999
  const arg = args[i];
1878
2000
  out = out.concat(generate(scope, arg));
1879
2001
 
2002
+ // todo: this should be used instead of the too many args thing above (by removing that)
2003
+ if (i >= paramCount) {
2004
+ // over param count of func, drop arg
2005
+ out.push([ Opcodes.drop ]);
2006
+ continue;
2007
+ }
2008
+
1880
2009
  if (valtypeBinary !== Valtype.i32 && (
1881
2010
  (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1882
2011
  (importedFuncs[name] && name.startsWith('profile'))
@@ -1925,6 +2054,11 @@ const generateNew = (scope, decl, _global, _name) => {
1925
2054
  }, _global, _name);
1926
2055
  }
1927
2056
 
2057
+ if (
2058
+ (builtinFuncs[name] && !builtinFuncs[name].constr) ||
2059
+ (internalConstrs[name] && builtinFuncs[name].notConstr)
2060
+ ) return internalThrow(scope, 'TypeError', `${name} is not a constructor`);
2061
+
1928
2062
  if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1929
2063
 
1930
2064
  return generateCall(scope, decl, _global, _name);
@@ -1950,8 +2084,11 @@ const knownType = (scope, type) => {
1950
2084
  const idx = type[0][1];
1951
2085
 
1952
2086
  // type idx = var idx + 1
1953
- const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
1954
- if (v.metadata?.type != null) return v.metadata.type;
2087
+ const name = Object.values(scope.locals).find(x => x.idx === idx)?.name;
2088
+ if (name) {
2089
+ const local = scope.locals[name];
2090
+ if (local.metadata?.type != null) return v.metadata.type;
2091
+ }
1955
2092
  }
1956
2093
 
1957
2094
  return null;
@@ -1986,16 +2123,17 @@ const brTable = (input, bc, returns) => {
1986
2123
  }
1987
2124
 
1988
2125
  for (let i = 0; i < count; i++) {
1989
- if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2126
+ // if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2127
+ if (i === 0) out.push([ Opcodes.block, returns ]);
1990
2128
  else out.push([ Opcodes.block, Blocktype.void ]);
1991
2129
  }
1992
2130
 
1993
- const nums = keys.filter(x => +x);
2131
+ const nums = keys.filter(x => +x >= 0);
1994
2132
  const offset = Math.min(...nums);
1995
2133
  const max = Math.max(...nums);
1996
2134
 
1997
2135
  const table = [];
1998
- let br = 1;
2136
+ let br = 0;
1999
2137
 
2000
2138
  for (let i = offset; i <= max; i++) {
2001
2139
  // if branch for this num, go to that block
@@ -2035,10 +2173,9 @@ const brTable = (input, bc, returns) => {
2035
2173
  br--;
2036
2174
  }
2037
2175
 
2038
- return [
2039
- ...out,
2040
- [ Opcodes.end, 'br table end' ]
2041
- ];
2176
+ out.push([ Opcodes.end ]);
2177
+
2178
+ return out;
2042
2179
  };
2043
2180
 
2044
2181
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
@@ -2082,6 +2219,17 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2082
2219
  return out;
2083
2220
  };
2084
2221
 
2222
+ const typeIsOneOf = (type, types, valtype = Valtype.i32) => {
2223
+ const out = [];
2224
+
2225
+ for (let i = 0; i < types.length; i++) {
2226
+ out.push(...type, ...number(types[i], valtype), valtype === Valtype.f64 ? [ Opcodes.f64_eq ] : [ Opcodes.i32_eq ]);
2227
+ if (i !== 0) out.push([ Opcodes.i32_or ]);
2228
+ }
2229
+
2230
+ return out;
2231
+ };
2232
+
2085
2233
  const allocVar = (scope, name, global = false, type = true) => {
2086
2234
  const target = global ? globals : scope.locals;
2087
2235
 
@@ -2098,7 +2246,7 @@ const allocVar = (scope, name, global = false, type = true) => {
2098
2246
 
2099
2247
  if (type) {
2100
2248
  let typeIdx = global ? globalInd++ : scope.localInd++;
2101
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2249
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32, name };
2102
2250
  }
2103
2251
 
2104
2252
  return idx;
@@ -2311,18 +2459,21 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2311
2459
  Opcodes.i32_to_u,
2312
2460
 
2313
2461
  // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2314
- ...number(ValtypeSize[valtype], Valtype.i32),
2462
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2315
2463
  [ Opcodes.i32_mul ],
2316
2464
  ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
2317
2465
  ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2318
2466
 
2319
2467
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2320
2468
  [ Opcodes.local_get, pointerTmp ],
2321
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2322
- ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2469
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2470
+ ], generate(scope, decl.right), [
2471
+ [ Opcodes.local_get, pointerTmp ],
2472
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
2473
+ ], getNodeType(scope, decl.right), false, name, true)),
2323
2474
  [ Opcodes.local_tee, newValueTmp ],
2324
2475
 
2325
- [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2476
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2326
2477
  ],
2327
2478
 
2328
2479
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
@@ -2430,6 +2581,11 @@ const generateUnary = (scope, decl) => {
2430
2581
  ];
2431
2582
 
2432
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
+ }
2433
2589
  // !=
2434
2590
  return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
2435
2591
 
@@ -2499,6 +2655,7 @@ const generateUnary = (scope, decl) => {
2499
2655
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2500
2656
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2501
2657
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2658
+ [TYPES.symbol]: makeString(scope, 'symbol', false, '#typeof_result'),
2502
2659
 
2503
2660
  [TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2504
2661
 
@@ -2575,21 +2732,16 @@ const generateConditional = (scope, decl) => {
2575
2732
  out.push([ Opcodes.if, valtypeBinary ]);
2576
2733
  depth.push('if');
2577
2734
 
2578
- out.push(...generate(scope, decl.consequent));
2579
-
2580
- // note type
2581
2735
  out.push(
2582
- ...getNodeType(scope, decl.consequent),
2583
- ...setLastType(scope)
2736
+ ...generate(scope, decl.consequent),
2737
+ ...setLastType(scope, getNodeType(scope, decl.consequent))
2584
2738
  );
2585
2739
 
2586
2740
  out.push([ Opcodes.else ]);
2587
- out.push(...generate(scope, decl.alternate));
2588
2741
 
2589
- // note type
2590
2742
  out.push(
2591
- ...getNodeType(scope, decl.alternate),
2592
- ...setLastType(scope)
2743
+ ...generate(scope, decl.alternate),
2744
+ ...setLastType(scope, getNodeType(scope, decl.alternate))
2593
2745
  );
2594
2746
 
2595
2747
  out.push([ Opcodes.end ]);
@@ -2737,12 +2889,15 @@ const generateForOf = (scope, decl) => {
2737
2889
  // todo: optimize away counter and use end pointer
2738
2890
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2739
2891
  [TYPES.array]: [
2740
- ...setType(scope, leftName, TYPES.number),
2741
-
2742
2892
  [ Opcodes.loop, Blocktype.void ],
2743
2893
 
2744
2894
  [ Opcodes.local_get, pointer ],
2745
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2895
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2896
+
2897
+ ...setType(scope, leftName, [
2898
+ [ Opcodes.local_get, pointer ],
2899
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
2900
+ ]),
2746
2901
 
2747
2902
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2748
2903
 
@@ -2751,9 +2906,9 @@ const generateForOf = (scope, decl) => {
2751
2906
  ...generate(scope, decl.body),
2752
2907
  [ Opcodes.end ],
2753
2908
 
2754
- // increment iter pointer by valtype size
2909
+ // increment iter pointer by valtype size + 1
2755
2910
  [ Opcodes.local_get, pointer ],
2756
- ...number(ValtypeSize[valtype], Valtype.i32),
2911
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2757
2912
  [ Opcodes.i32_add ],
2758
2913
  [ Opcodes.local_set, pointer ],
2759
2914
 
@@ -2869,6 +3024,44 @@ const generateForOf = (scope, decl) => {
2869
3024
  [ Opcodes.end ],
2870
3025
  [ Opcodes.end ]
2871
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
+ ],
2872
3065
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2873
3066
  }, Blocktype.void));
2874
3067
 
@@ -2970,14 +3163,18 @@ const generateThrow = (scope, decl) => {
2970
3163
  };
2971
3164
 
2972
3165
  const generateTry = (scope, decl) => {
2973
- 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."
2974
3168
 
2975
3169
  const out = [];
2976
3170
 
3171
+ const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
3172
+
2977
3173
  out.push([ Opcodes.try, Blocktype.void ]);
2978
3174
  depth.push('try');
2979
3175
 
2980
3176
  out.push(...generate(scope, decl.block));
3177
+ out.push(...finalizer);
2981
3178
 
2982
3179
  if (decl.handler) {
2983
3180
  depth.pop();
@@ -2985,6 +3182,7 @@ const generateTry = (scope, decl) => {
2985
3182
 
2986
3183
  out.push([ Opcodes.catch_all ]);
2987
3184
  out.push(...generate(scope, decl.handler.body));
3185
+ out.push(...finalizer);
2988
3186
  }
2989
3187
 
2990
3188
  out.push([ Opcodes.end ]);
@@ -3070,7 +3268,7 @@ const getAllocType = itemType => {
3070
3268
  }
3071
3269
  };
3072
3270
 
3073
- const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
3271
+ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype, typed = false) => {
3074
3272
  const out = [];
3075
3273
 
3076
3274
  scope.arrays ??= new Map();
@@ -3082,8 +3280,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3082
3280
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
3083
3281
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
3084
3282
 
3085
- if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
3086
- 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);
3087
3290
  }
3088
3291
 
3089
3292
  const pointer = scope.arrays.get(name);
@@ -3133,7 +3336,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3133
3336
 
3134
3337
  const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3135
3338
 
3136
- // store length as 0th array
3339
+ // store length
3137
3340
  out.push(
3138
3341
  ...pointerWasm,
3139
3342
  ...number(length, Valtype.i32),
@@ -3141,14 +3344,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3141
3344
  );
3142
3345
 
3143
3346
  const storeOp = StoreOps[itemType];
3144
-
3347
+ const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
3145
3348
  if (!initEmpty) for (let i = 0; i < length; i++) {
3146
3349
  if (elements[i] == null) continue;
3147
3350
 
3351
+ const offset = ValtypeSize.i32 + i * sizePerEl;
3148
3352
  out.push(
3149
3353
  ...pointerWasm,
3150
3354
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
3151
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3355
+ [ storeOp, 0, ...unsignedLEB128(offset) ],
3356
+ ...(!typed ? [] : [ // typed presumes !useRawElements
3357
+ ...pointerWasm,
3358
+ ...getNodeType(scope, elements[i]),
3359
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(offset + ValtypeSize[itemType]) ]
3360
+ ])
3152
3361
  );
3153
3362
  }
3154
3363
 
@@ -3158,6 +3367,65 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3158
3367
  return [ out, pointer ];
3159
3368
  };
3160
3369
 
3370
+ const storeArray = (scope, array, index, element, aotPointer = null) => {
3371
+ if (!Array.isArray(element)) element = generate(scope, element);
3372
+ if (typeof index === 'number') index = number(index);
3373
+
3374
+ const offset = localTmp(scope, '#storeArray_offset', Valtype.i32);
3375
+
3376
+ return [
3377
+ // calculate offset
3378
+ ...index,
3379
+ Opcodes.i32_to_u,
3380
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3381
+ [ Opcodes.i32_mul ],
3382
+ ...(aotPointer ? [] : [
3383
+ ...array,
3384
+ Opcodes.i32_to_u,
3385
+ [ Opcodes.i32_add ],
3386
+ ]),
3387
+ [ Opcodes.local_set, offset ],
3388
+
3389
+ // store value
3390
+ [ Opcodes.local_get, offset ],
3391
+ ...generate(scope, element),
3392
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3393
+
3394
+ // store type
3395
+ [ Opcodes.local_get, offset ],
3396
+ ...getNodeType(scope, element),
3397
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3398
+ ];
3399
+ };
3400
+
3401
+ const loadArray = (scope, array, index, aotPointer = null) => {
3402
+ if (typeof index === 'number') index = number(index);
3403
+
3404
+ const offset = localTmp(scope, '#loadArray_offset', Valtype.i32);
3405
+
3406
+ return [
3407
+ // calculate offset
3408
+ ...index,
3409
+ Opcodes.i32_to_u,
3410
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3411
+ [ Opcodes.i32_mul ],
3412
+ ...(aotPointer ? [] : [
3413
+ ...array,
3414
+ Opcodes.i32_to_u,
3415
+ [ Opcodes.i32_add ],
3416
+ ]),
3417
+ [ Opcodes.local_set, offset ],
3418
+
3419
+ // load value
3420
+ [ Opcodes.local_get, offset ],
3421
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3422
+
3423
+ // load type
3424
+ [ Opcodes.local_get, offset ],
3425
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3426
+ ];
3427
+ };
3428
+
3161
3429
  const byteStringable = str => {
3162
3430
  if (!Prefs.bytestring) return false;
3163
3431
 
@@ -3186,14 +3454,28 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
3186
3454
  };
3187
3455
 
3188
3456
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
3189
- return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
3457
+ return makeArray(scope, decl, global, name, initEmpty, valtype, true)[0];
3458
+ };
3459
+
3460
+ const generateObject = (scope, decl, global = false, name = '$undeclared') => {
3461
+ if (decl.properties.length > 0) return todo(scope, 'objects are not supported yet', true);
3462
+
3463
+ return [
3464
+ ...number(1),
3465
+ ...setLastType(scope, TYPES.object)
3466
+ ];
3190
3467
  };
3191
3468
 
3192
- export const generateMember = (scope, decl, _global, _name) => {
3469
+ const withType = (scope, wasm, type) => [
3470
+ ...wasm,
3471
+ ...setLastType(scope, type)
3472
+ ];
3473
+
3474
+ const generateMember = (scope, decl, _global, _name) => {
3193
3475
  const name = decl.object.name;
3194
3476
  const pointer = scope.arrays?.get(name);
3195
3477
 
3196
- const aotPointer = Prefs.aotPointerOpt && pointer != null;
3478
+ const aotPointer = Prefs.aotPointerOpt && pointer;
3197
3479
 
3198
3480
  // hack: .name
3199
3481
  if (decl.property.name === 'name') {
@@ -3203,9 +3485,9 @@ export const generateMember = (scope, decl, _global, _name) => {
3203
3485
  // eg: __String_prototype_toLowerCase -> toLowerCase
3204
3486
  if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
3205
3487
 
3206
- return makeString(scope, nameProp, _global, _name, true);
3488
+ return withType(scope, makeString(scope, nameProp, _global, _name, true), TYPES.bytestring);
3207
3489
  } else {
3208
- return generate(scope, DEFAULT_VALUE);
3490
+ return withType(scope, number(0), TYPES.undefined);
3209
3491
  }
3210
3492
  }
3211
3493
 
@@ -3213,9 +3495,8 @@ export const generateMember = (scope, decl, _global, _name) => {
3213
3495
  if (decl.property.name === 'length') {
3214
3496
  const func = funcs.find(x => x.name === name);
3215
3497
  if (func) {
3216
- const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3217
- const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3218
- return number(typedParams ? func.params.length / 2 : func.params.length);
3498
+ const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
3499
+ return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3219
3500
  }
3220
3501
 
3221
3502
  if (builtinFuncs[name + '$constructor']) {
@@ -3225,24 +3506,88 @@ export const generateMember = (scope, decl, _global, _name) => {
3225
3506
  const constructorFunc = builtinFuncs[name + '$constructor'];
3226
3507
  const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3227
3508
 
3228
- return number(Math.max(regularParams, constructorParams));
3509
+ return withType(scope, number(Math.max(regularParams, constructorParams)), TYPES.number);
3229
3510
  }
3230
3511
 
3231
- if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3232
- if (importedFuncs[name]) return number(importedFuncs[name].params);
3233
- if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3512
+ if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
3513
+ if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params), TYPES.number);
3514
+ if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
3515
+
3516
+ if (Prefs.fastLength) {
3517
+ // presume valid length object
3518
+ return [
3519
+ ...(aotPointer ? number(0, Valtype.i32) : [
3520
+ ...generate(scope, decl.object),
3521
+ Opcodes.i32_to_u
3522
+ ]),
3523
+
3524
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3525
+ Opcodes.i32_from_u
3526
+ ];
3527
+ }
3528
+
3529
+ const type = getNodeType(scope, decl.object);
3530
+ const known = knownType(scope, type);
3531
+ if (known != null) {
3532
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
3533
+ ...(aotPointer ? number(0, Valtype.i32) : [
3534
+ ...generate(scope, decl.object),
3535
+ Opcodes.i32_to_u
3536
+ ]),
3537
+
3538
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3539
+ Opcodes.i32_from_u
3540
+ ];
3541
+
3542
+ return number(0);
3543
+ }
3234
3544
 
3235
3545
  return [
3236
- ...(aotPointer ? number(0, Valtype.i32) : [
3237
- ...generate(scope, decl.object),
3238
- Opcodes.i32_to_u
3239
- ]),
3546
+ ...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
3547
+ [ Opcodes.if, valtypeBinary ],
3548
+ ...(aotPointer ? number(0, Valtype.i32) : [
3549
+ ...generate(scope, decl.object),
3550
+ Opcodes.i32_to_u
3551
+ ]),
3240
3552
 
3241
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128((aotPointer ? pointer : 0)) ],
3242
- Opcodes.i32_from_u
3553
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3554
+ Opcodes.i32_from_u,
3555
+
3556
+ ...setLastType(scope, TYPES.number),
3557
+ [ Opcodes.else ],
3558
+ ...number(0),
3559
+ ...setLastType(scope, TYPES.undefined),
3560
+ [ Opcodes.end ]
3243
3561
  ];
3244
3562
  }
3245
3563
 
3564
+ // todo: generate this array procedurally during builtinFuncs creation
3565
+ if (['size', 'description'].includes(decl.property.name)) {
3566
+ const bc = {};
3567
+ const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3568
+
3569
+ if (cands.length > 0) {
3570
+ for (const x of cands) {
3571
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
3572
+ if (type == null) continue;
3573
+
3574
+ bc[type] = generateCall(scope, {
3575
+ callee: {
3576
+ type: 'Identifier',
3577
+ name: x
3578
+ },
3579
+ arguments: [ decl.object ],
3580
+ _protoInternalCall: true
3581
+ });
3582
+ }
3583
+ }
3584
+
3585
+ return typeSwitch(scope, getNodeType(scope, decl.object), {
3586
+ ...bc,
3587
+ default: withType(scope, number(0), TYPES.undefined)
3588
+ }, valtypeBinary);
3589
+ }
3590
+
3246
3591
  const object = generate(scope, decl.object);
3247
3592
  const property = generate(scope, decl.property);
3248
3593
 
@@ -3257,24 +3602,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3257
3602
 
3258
3603
  return typeSwitch(scope, getNodeType(scope, decl.object), {
3259
3604
  [TYPES.array]: [
3260
- // get index as valtype
3261
- ...property,
3262
-
3263
- // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
3264
- Opcodes.i32_to_u,
3265
- ...number(ValtypeSize[valtype], Valtype.i32),
3266
- [ Opcodes.i32_mul ],
3267
-
3268
- ...(aotPointer ? [] : [
3269
- ...object,
3270
- Opcodes.i32_to_u,
3271
- [ Opcodes.i32_add ]
3272
- ]),
3273
-
3274
- // read from memory
3275
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3276
-
3277
- ...number(TYPES.number, Valtype.i32),
3605
+ ...loadArray(scope, object, property, aotPointer),
3278
3606
  ...setLastType(scope)
3279
3607
  ],
3280
3608
 
@@ -3305,9 +3633,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3305
3633
 
3306
3634
  // return new string (page)
3307
3635
  ...number(newPointer),
3308
-
3309
- ...number(TYPES.string, Valtype.i32),
3310
- ...setLastType(scope)
3636
+ ...setLastType(scope, TYPES.string)
3311
3637
  ],
3312
3638
  [TYPES.bytestring]: [
3313
3639
  // setup new/out array
@@ -3333,9 +3659,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3333
3659
 
3334
3660
  // return new string (page)
3335
3661
  ...number(newPointer),
3336
-
3337
- ...number(TYPES.bytestring, Valtype.i32),
3338
- ...setLastType(scope)
3662
+ ...setLastType(scope, TYPES.bytestring)
3339
3663
  ],
3340
3664
 
3341
3665
  default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
@@ -3360,7 +3684,7 @@ const objectHack = node => {
3360
3684
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3361
3685
 
3362
3686
  // if .name or .length, give up (hack within a hack!)
3363
- if (['name', 'length'].includes(node.property.name)) {
3687
+ if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
3364
3688
  node.object = objectHack(node.object);
3365
3689
  return;
3366
3690
  }
@@ -3397,33 +3721,39 @@ const generateFunc = (scope, decl) => {
3397
3721
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3398
3722
  const params = decl.params ?? [];
3399
3723
 
3400
- // const innerScope = { ...scope };
3401
3724
  // TODO: share scope/locals between !!!
3402
- const innerScope = {
3725
+ const func = {
3403
3726
  locals: {},
3404
3727
  localInd: 0,
3405
3728
  // value, type
3406
3729
  returns: [ valtypeBinary, Valtype.i32 ],
3407
3730
  throws: false,
3408
- name
3731
+ name,
3732
+ index: currentFuncIndex++
3409
3733
  };
3410
3734
 
3411
3735
  if (typedInput && decl.returnType) {
3412
3736
  const { type } = extractTypeAnnotation(decl.returnType);
3413
- if (type != null && !Prefs.indirectCalls) {
3414
- innerScope.returnType = type;
3415
- innerScope.returns = [ valtypeBinary ];
3737
+ // if (type != null && !Prefs.indirectCalls) {
3738
+ if (type != null) {
3739
+ func.returnType = type;
3740
+ func.returns = [ valtypeBinary ];
3416
3741
  }
3417
3742
  }
3418
3743
 
3419
3744
  for (let i = 0; i < params.length; i++) {
3420
- 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);
3421
3749
 
3422
3750
  if (typedInput && params[i].typeAnnotation) {
3423
- addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
3751
+ addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
3424
3752
  }
3425
3753
  }
3426
3754
 
3755
+ func.params = Object.values(func.locals).map(x => x.type);
3756
+
3427
3757
  let body = objectHack(decl.body);
3428
3758
  if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
3429
3759
  // hack: () => 0 -> () => return 0
@@ -3433,37 +3763,23 @@ const generateFunc = (scope, decl) => {
3433
3763
  };
3434
3764
  }
3435
3765
 
3436
- const wasm = generate(innerScope, body);
3437
- const func = {
3438
- name,
3439
- params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
3440
- index: currentFuncIndex++,
3441
- ...innerScope
3442
- };
3443
3766
  funcIndex[name] = func.index;
3767
+ funcs.push(func);
3444
3768
 
3445
- if (name === 'main') func.gotLastType = true;
3769
+ const wasm = generate(func, body);
3770
+ func.wasm = wasm;
3446
3771
 
3447
- // quick hack fixes
3448
- for (const inst of wasm) {
3449
- if (inst[0] === Opcodes.call && inst[1] === -1) {
3450
- inst[1] = func.index;
3451
- }
3452
- }
3772
+ if (name === 'main') func.gotLastType = true;
3453
3773
 
3454
3774
  // add end return if not found
3455
3775
  if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
3456
3776
  wasm.push(
3457
3777
  ...number(0),
3458
- ...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3778
+ ...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3459
3779
  [ Opcodes.return ]
3460
3780
  );
3461
3781
  }
3462
3782
 
3463
- func.wasm = wasm;
3464
-
3465
- funcs.push(func);
3466
-
3467
3783
  return func;
3468
3784
  };
3469
3785
 
@@ -3567,7 +3883,7 @@ const internalConstrs = {
3567
3883
  generate: (scope, decl) => {
3568
3884
  // todo: boolean object when used as constructor
3569
3885
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3570
- return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3886
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg), false, false, 'full');
3571
3887
  },
3572
3888
  type: TYPES.boolean,
3573
3889
  length: 1
@@ -3628,8 +3944,10 @@ const internalConstrs = {
3628
3944
  }),
3629
3945
 
3630
3946
  // print space
3631
- ...number(32),
3632
- [ Opcodes.call, importedFuncs.printChar ]
3947
+ ...(i !== decl.arguments.length - 1 ? [
3948
+ ...number(32),
3949
+ [ Opcodes.call, importedFuncs.printChar ]
3950
+ ] : [])
3633
3951
  );
3634
3952
  }
3635
3953
 
@@ -3639,6 +3957,8 @@ const internalConstrs = {
3639
3957
  [ Opcodes.call, importedFuncs.printChar ]
3640
3958
  );
3641
3959
 
3960
+ out.push(...number(UNDEFINED));
3961
+
3642
3962
  return out;
3643
3963
  },
3644
3964
  type: TYPES.undefined,
@@ -3708,9 +4028,8 @@ export default program => {
3708
4028
 
3709
4029
  if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3710
4030
 
3711
- generateFunc(scope, program);
4031
+ const main = generateFunc(scope, program);
3712
4032
 
3713
- const main = funcs[funcs.length - 1];
3714
4033
  main.export = true;
3715
4034
  main.returns = [ valtypeBinary, Valtype.i32 ];
3716
4035
 
@@ -3737,7 +4056,7 @@ export default program => {
3737
4056
  }
3738
4057
 
3739
4058
  // if blank main func and other exports, remove it
3740
- 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);
3741
4060
 
3742
4061
  return { funcs, globals, tags, exceptions, pages, data };
3743
4062
  };