porffor 0.14.0-f67c123a1 → 0.16.0-a2e115b05

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,
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 = (() => {
@@ -1244,7 +1254,7 @@ const getNodeType = (scope, node) => {
1244
1254
  const func = funcs.find(x => x.name === name);
1245
1255
 
1246
1256
  if (func) {
1247
- if (func.returnType) return func.returnType;
1257
+ if (func.returnType != null) return func.returnType;
1248
1258
  }
1249
1259
 
1250
1260
  if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
@@ -1259,7 +1269,9 @@ 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 != null) return protoFuncs[0].returnType;
1274
+ }
1263
1275
  }
1264
1276
 
1265
1277
  if (name.startsWith('__Porffor_wasm_')) {
@@ -1354,22 +1366,27 @@ const getNodeType = (scope, node) => {
1354
1366
  }
1355
1367
 
1356
1368
  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
- }
1369
+ const name = node.property.name;
1370
+
1371
+ if (name === 'length') {
1372
+ if (hasFuncWithName(node.object.name)) return TYPES.number;
1373
+ if (Prefs.fastLength) return TYPES.number;
1364
1374
  }
1365
1375
 
1366
- // hack: if something.length, number type
1367
- if (node.property.name === 'length') return TYPES.number;
1368
1376
 
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;
1377
+ const objectKnownType = knownType(scope, getNodeType(scope, node.object));
1378
+ if (objectKnownType != null) {
1379
+ if (name === 'length') {
1380
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(objectKnownType)) return TYPES.number;
1381
+ else return TYPES.undefined;
1382
+ }
1383
+
1384
+ if (node.computed) {
1385
+ if (objectKnownType === TYPES.string) return TYPES.string;
1386
+ if (objectKnownType === TYPES.bytestring) return TYPES.bytestring;
1387
+ if (objectKnownType === TYPES.array) return TYPES.number;
1388
+ }
1389
+ }
1373
1390
 
1374
1391
  if (scope.locals['#last_type']) return getLastType(scope);
1375
1392
 
@@ -1440,17 +1457,16 @@ const countLeftover = wasm => {
1440
1457
  else if (inst[0] === Opcodes.return) count = 0;
1441
1458
  else if (inst[0] === Opcodes.call) {
1442
1459
  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;
1460
+ if (inst[1] < importedFuncs.length) {
1461
+ func = importedFuncs[inst[1]];
1462
+ count = count - func.params + func.returns;
1448
1463
  } else {
1449
- if (func) {
1450
- count -= func.params.length;
1451
- } else count--;
1452
- if (func) count += func.returns.length;
1464
+ count = count - func.params.length + func.returns.length;
1453
1465
  }
1466
+ } else if (inst[0] === Opcodes.call_indirect) {
1467
+ count--; // funcidx
1468
+ count -= inst[1] * 2; // params * 2 (typed)
1469
+ count += 2; // fixed return (value, type)
1454
1470
  } else count--;
1455
1471
 
1456
1472
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1468,7 +1484,7 @@ const disposeLeftover = wasm => {
1468
1484
  const generateExp = (scope, decl) => {
1469
1485
  const expression = decl.expression;
1470
1486
 
1471
- const out = generate(scope, expression, undefined, undefined, true);
1487
+ const out = generate(scope, expression, undefined, undefined, Prefs.optUnused);
1472
1488
  disposeLeftover(out);
1473
1489
 
1474
1490
  return out;
@@ -1559,16 +1575,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1559
1575
  out.splice(out.length - 1, 1);
1560
1576
 
1561
1577
  const finalStatement = parsed.body[parsed.body.length - 1];
1562
- out.push(
1563
- ...getNodeType(scope, finalStatement),
1564
- ...setLastType(scope)
1565
- );
1578
+ out.push(...setLastType(scope, getNodeType(scope, finalStatement)));
1566
1579
  } else if (countLeftover(out) === 0) {
1567
1580
  out.push(...number(UNDEFINED));
1568
- out.push(
1569
- ...number(TYPES.undefined, Valtype.i32),
1570
- ...setLastType(scope)
1571
- );
1581
+ out.push(...setLastType(scope, TYPES.undefined));
1572
1582
  }
1573
1583
 
1574
1584
  // if (lastInst && lastInst[0] === Opcodes.drop) {
@@ -1604,6 +1614,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1604
1614
 
1605
1615
  if (!funcIndex[rhemynName]) {
1606
1616
  const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1617
+ func.internal = true;
1607
1618
 
1608
1619
  funcIndex[func.name] = func.index;
1609
1620
  funcs.push(func);
@@ -1620,8 +1631,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1620
1631
  [ Opcodes.call, idx ],
1621
1632
  Opcodes.i32_from_u,
1622
1633
 
1623
- ...number(TYPES.boolean, Valtype.i32),
1624
- ...setLastType(scope)
1634
+ ...setLastType(scope, TYPES.boolean)
1625
1635
  ];
1626
1636
  }
1627
1637
 
@@ -1693,9 +1703,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1693
1703
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
1694
1704
  protoBC[x] = [
1695
1705
  ...RTArrayUtil.getLength(getPointer),
1696
-
1697
- ...number(TYPES.number, Valtype.i32),
1698
- ...setLastType(scope)
1706
+ ...setLastType(scope, TYPES.number)
1699
1707
  ];
1700
1708
  continue;
1701
1709
  }
@@ -1714,7 +1722,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1714
1722
  getI32: () => RTArrayUtil.getLengthI32(getPointer),
1715
1723
  set: value => RTArrayUtil.setLength(getPointer, value),
1716
1724
  setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
1717
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1725
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), getNodeType(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1718
1726
  return makeArray(scope, {
1719
1727
  rawElements: new Array(length)
1720
1728
  }, _global, _name, true, itemType);
@@ -1728,9 +1736,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1728
1736
  protoBC[x] = [
1729
1737
  [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1730
1738
  ...protoOut,
1731
-
1732
- ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1733
- ...setLastType(scope),
1739
+ ...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
1734
1740
  [ Opcodes.end ]
1735
1741
  ];
1736
1742
  }
@@ -1780,11 +1786,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1780
1786
 
1781
1787
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1782
1788
 
1783
- if (idx === undefined && name === scope.name) {
1784
- // hack: calling self, func generator will fix later
1785
- idx = -1;
1786
- }
1787
-
1788
1789
  if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1789
1790
  const wasmOps = {
1790
1791
  // pointer, align, offset
@@ -1833,30 +1834,143 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1833
1834
 
1834
1835
  if (idx === undefined) {
1835
1836
  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
1837
  const [ local, global ] = lookupName(scope, name);
1838
+ if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1839
+
1840
+ // todo: only works when function uses typedParams and typedReturns
1841
+
1842
+ const indirectMode = Prefs.indirectCallMode ?? 'vararg';
1843
+ // options: vararg, strict
1844
+ // - strict: simpler, smaller size usage, no func argc lut needed.
1845
+ // ONLY works when arg count of call == arg count of function being called
1846
+ // - vararg: large size usage, cursed.
1847
+ // works when arg count of call != arg count of function being called*
1848
+ // * most of the time, some edgecases
1849
+
1839
1850
  funcs.table = true;
1851
+ scope.table = true;
1852
+
1853
+ let args = decl.arguments;
1854
+ let out = [];
1855
+
1856
+ let locals = [];
1857
+
1858
+ if (indirectMode === 'vararg') {
1859
+ const minArgc = Prefs.indirectCallMinArgc ?? 3;
1860
+
1861
+ if (args.length < minArgc) {
1862
+ args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
1863
+ }
1864
+ }
1865
+
1866
+ for (let i = 0; i < args.length; i++) {
1867
+ const arg = args[i];
1868
+ out = out.concat(generate(scope, arg));
1869
+
1870
+ if (valtypeBinary !== Valtype.i32 && (
1871
+ (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1872
+ (importedFuncs[name] && name.startsWith('profile'))
1873
+ )) {
1874
+ out.push(Opcodes.i32_to);
1875
+ }
1840
1876
 
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), {
1877
+ out = out.concat(getNodeType(scope, arg));
1878
+
1879
+ if (indirectMode === 'vararg') {
1880
+ const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
1881
+ const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
1882
+
1883
+ locals.push([valLocal, typeLocal]);
1884
+
1885
+ out.push(
1886
+ [ Opcodes.local_set, typeLocal ],
1887
+ [ Opcodes.local_set, valLocal ]
1888
+ );
1889
+ }
1890
+ }
1891
+
1892
+ if (indirectMode === 'strict') {
1893
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1894
+ [TYPES.function]: [
1895
+ ...argWasm,
1896
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1897
+ Opcodes.i32_to_u,
1898
+ [ Opcodes.call_indirect, args.length, 0 ],
1899
+ ...setLastType(scope)
1900
+ ],
1901
+ default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1902
+ });
1903
+ }
1904
+
1905
+ // hi, I will now explain how vararg mode works:
1906
+ // wasm's indirect_call instruction requires you know the func type at compile-time
1907
+ // since we have varargs (variable argument count), we do not know it.
1908
+ // we could just store args in memory and not use wasm func args,
1909
+ // but that is slow (probably) and breaks js exports.
1910
+ // instead, we generate every* possibility of argc and use different indirect_call
1911
+ // ops for each one, with type depending on argc for that branch.
1912
+ // then we load the argc for the wanted function from a memory lut,
1913
+ // and call the branch with the matching argc we require.
1914
+ // sorry, yes it is very cursed (and size inefficient), but indirect calls
1915
+ // are kind of rare anyway (mostly callbacks) so I am not concerned atm.
1916
+ // *for argc 0-3, in future (todo:) the max number should be
1917
+ // dynamically changed to the max argc of any func in the js file.
1918
+
1919
+ const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
1920
+
1921
+ const gen = argc => {
1922
+ const out = [];
1923
+ for (let i = 0; i < argc; i++) {
1924
+ out.push(
1925
+ [ Opcodes.local_get, locals[i][0] ],
1926
+ [ Opcodes.local_get, locals[i][1] ]
1927
+ );
1928
+ }
1929
+
1930
+ out.push(
1931
+ [ Opcodes.local_get, funcLocal ],
1932
+ [ Opcodes.call_indirect, argc, 0 ],
1933
+ ...setLastType(scope)
1934
+ )
1935
+
1936
+ return out;
1937
+ };
1938
+
1939
+ const tableBc = {};
1940
+ for (let i = 0; i <= args.length; i++) {
1941
+ tableBc[i] = gen(i);
1942
+ }
1943
+
1944
+ // todo/perf: check if we should use br_table here or just generate our own big if..elses
1945
+
1946
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1845
1947
  [TYPES.function]: [
1948
+ ...out,
1949
+
1846
1950
  [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1847
- [ Opcodes.call_indirect ]
1951
+ Opcodes.i32_to_u,
1952
+ [ Opcodes.local_set, funcLocal ],
1953
+
1954
+ ...brTable([
1955
+ // get argc of func we are calling
1956
+ [ Opcodes.local_get, funcLocal ],
1957
+ ...number(ValtypeSize.i16, Valtype.i32),
1958
+ [ Opcodes.i32_mul ],
1959
+
1960
+ [ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
1961
+ ], tableBc, valtypeBinary)
1848
1962
  ],
1849
1963
  default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1850
1964
  });
1851
1965
  }
1966
+
1852
1967
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1853
1968
  }
1854
1969
 
1855
- const func = funcs.find(x => x.index === idx);
1856
-
1857
- const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1970
+ const func = funcs[idx - importedFuncs.length]; // idx === scope.index ? scope : funcs.find(x => x.index === idx);
1971
+ const userFunc = func && !func.internal;
1858
1972
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1859
- const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1973
+ const typedReturns = (userFunc && func.returnType == null) || builtinFuncs[name]?.typedReturns;
1860
1974
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1861
1975
 
1862
1976
  let args = decl.arguments;
@@ -1877,6 +1991,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1877
1991
  const arg = args[i];
1878
1992
  out = out.concat(generate(scope, arg));
1879
1993
 
1994
+ // todo: this should be used instead of the too many args thing above (by removing that)
1995
+ if (i >= paramCount) {
1996
+ // over param count of func, drop arg
1997
+ out.push([ Opcodes.drop ]);
1998
+ continue;
1999
+ }
2000
+
1880
2001
  if (valtypeBinary !== Valtype.i32 && (
1881
2002
  (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1882
2003
  (importedFuncs[name] && name.startsWith('profile'))
@@ -1925,6 +2046,11 @@ const generateNew = (scope, decl, _global, _name) => {
1925
2046
  }, _global, _name);
1926
2047
  }
1927
2048
 
2049
+ if (
2050
+ (builtinFuncs[name] && !builtinFuncs[name].constr) ||
2051
+ (internalConstrs[name] && builtinFuncs[name].notConstr)
2052
+ ) return internalThrow(scope, 'TypeError', `${name} is not a constructor`);
2053
+
1928
2054
  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
2055
 
1930
2056
  return generateCall(scope, decl, _global, _name);
@@ -1950,8 +2076,11 @@ const knownType = (scope, type) => {
1950
2076
  const idx = type[0][1];
1951
2077
 
1952
2078
  // 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;
2079
+ const name = Object.values(scope.locals).find(x => x.idx === idx)?.name;
2080
+ if (name) {
2081
+ const local = scope.locals[name];
2082
+ if (local.metadata?.type != null) return v.metadata.type;
2083
+ }
1955
2084
  }
1956
2085
 
1957
2086
  return null;
@@ -1986,16 +2115,17 @@ const brTable = (input, bc, returns) => {
1986
2115
  }
1987
2116
 
1988
2117
  for (let i = 0; i < count; i++) {
1989
- if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2118
+ // if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2119
+ if (i === 0) out.push([ Opcodes.block, returns ]);
1990
2120
  else out.push([ Opcodes.block, Blocktype.void ]);
1991
2121
  }
1992
2122
 
1993
- const nums = keys.filter(x => +x);
2123
+ const nums = keys.filter(x => +x >= 0);
1994
2124
  const offset = Math.min(...nums);
1995
2125
  const max = Math.max(...nums);
1996
2126
 
1997
2127
  const table = [];
1998
- let br = 1;
2128
+ let br = 0;
1999
2129
 
2000
2130
  for (let i = offset; i <= max; i++) {
2001
2131
  // if branch for this num, go to that block
@@ -2035,10 +2165,9 @@ const brTable = (input, bc, returns) => {
2035
2165
  br--;
2036
2166
  }
2037
2167
 
2038
- return [
2039
- ...out,
2040
- [ Opcodes.end, 'br table end' ]
2041
- ];
2168
+ out.push([ Opcodes.end ]);
2169
+
2170
+ return out;
2042
2171
  };
2043
2172
 
2044
2173
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
@@ -2082,6 +2211,17 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2082
2211
  return out;
2083
2212
  };
2084
2213
 
2214
+ const typeIsOneOf = (type, types, valtype = Valtype.i32) => {
2215
+ const out = [];
2216
+
2217
+ for (let i = 0; i < types.length; i++) {
2218
+ out.push(...type, ...number(types[i], valtype), valtype === Valtype.f64 ? [ Opcodes.f64_eq ] : [ Opcodes.i32_eq ]);
2219
+ if (i !== 0) out.push([ Opcodes.i32_or ]);
2220
+ }
2221
+
2222
+ return out;
2223
+ };
2224
+
2085
2225
  const allocVar = (scope, name, global = false, type = true) => {
2086
2226
  const target = global ? globals : scope.locals;
2087
2227
 
@@ -2098,7 +2238,7 @@ const allocVar = (scope, name, global = false, type = true) => {
2098
2238
 
2099
2239
  if (type) {
2100
2240
  let typeIdx = global ? globalInd++ : scope.localInd++;
2101
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2241
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32, name };
2102
2242
  }
2103
2243
 
2104
2244
  return idx;
@@ -2311,18 +2451,21 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2311
2451
  Opcodes.i32_to_u,
2312
2452
 
2313
2453
  // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2314
- ...number(ValtypeSize[valtype], Valtype.i32),
2454
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2315
2455
  [ Opcodes.i32_mul ],
2316
2456
  ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
2317
2457
  ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2318
2458
 
2319
2459
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2320
2460
  [ 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)),
2461
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2462
+ ], generate(scope, decl.right), [
2463
+ [ Opcodes.local_get, pointerTmp ],
2464
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
2465
+ ], getNodeType(scope, decl.right), false, name, true)),
2323
2466
  [ Opcodes.local_tee, newValueTmp ],
2324
2467
 
2325
- [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2468
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2326
2469
  ],
2327
2470
 
2328
2471
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
@@ -2430,6 +2573,11 @@ const generateUnary = (scope, decl) => {
2430
2573
  ];
2431
2574
 
2432
2575
  case '!':
2576
+ const arg = decl.argument;
2577
+ if (arg.type === 'UnaryExpression' && arg.operator === '!') {
2578
+ // !!x -> is x truthy
2579
+ return truthy(scope, generate(scope, arg.argument), getNodeType(scope, arg.argument), false, false);
2580
+ }
2433
2581
  // !=
2434
2582
  return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
2435
2583
 
@@ -2499,6 +2647,7 @@ const generateUnary = (scope, decl) => {
2499
2647
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2500
2648
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2501
2649
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2650
+ [TYPES.symbol]: makeString(scope, 'symbol', false, '#typeof_result'),
2502
2651
 
2503
2652
  [TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2504
2653
 
@@ -2575,21 +2724,16 @@ const generateConditional = (scope, decl) => {
2575
2724
  out.push([ Opcodes.if, valtypeBinary ]);
2576
2725
  depth.push('if');
2577
2726
 
2578
- out.push(...generate(scope, decl.consequent));
2579
-
2580
- // note type
2581
2727
  out.push(
2582
- ...getNodeType(scope, decl.consequent),
2583
- ...setLastType(scope)
2728
+ ...generate(scope, decl.consequent),
2729
+ ...setLastType(scope, getNodeType(scope, decl.consequent))
2584
2730
  );
2585
2731
 
2586
2732
  out.push([ Opcodes.else ]);
2587
- out.push(...generate(scope, decl.alternate));
2588
2733
 
2589
- // note type
2590
2734
  out.push(
2591
- ...getNodeType(scope, decl.alternate),
2592
- ...setLastType(scope)
2735
+ ...generate(scope, decl.alternate),
2736
+ ...setLastType(scope, getNodeType(scope, decl.alternate))
2593
2737
  );
2594
2738
 
2595
2739
  out.push([ Opcodes.end ]);
@@ -2737,12 +2881,15 @@ const generateForOf = (scope, decl) => {
2737
2881
  // todo: optimize away counter and use end pointer
2738
2882
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2739
2883
  [TYPES.array]: [
2740
- ...setType(scope, leftName, TYPES.number),
2741
-
2742
2884
  [ Opcodes.loop, Blocktype.void ],
2743
2885
 
2744
2886
  [ Opcodes.local_get, pointer ],
2745
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2887
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2888
+
2889
+ ...setType(scope, leftName, [
2890
+ [ Opcodes.local_get, pointer ],
2891
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
2892
+ ]),
2746
2893
 
2747
2894
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2748
2895
 
@@ -2751,9 +2898,9 @@ const generateForOf = (scope, decl) => {
2751
2898
  ...generate(scope, decl.body),
2752
2899
  [ Opcodes.end ],
2753
2900
 
2754
- // increment iter pointer by valtype size
2901
+ // increment iter pointer by valtype size + 1
2755
2902
  [ Opcodes.local_get, pointer ],
2756
- ...number(ValtypeSize[valtype], Valtype.i32),
2903
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2757
2904
  [ Opcodes.i32_add ],
2758
2905
  [ Opcodes.local_set, pointer ],
2759
2906
 
@@ -2869,6 +3016,44 @@ const generateForOf = (scope, decl) => {
2869
3016
  [ Opcodes.end ],
2870
3017
  [ Opcodes.end ]
2871
3018
  ],
3019
+ [TYPES.set]: [
3020
+ [ Opcodes.loop, Blocktype.void ],
3021
+
3022
+ [ Opcodes.local_get, pointer ],
3023
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
3024
+
3025
+ ...setType(scope, leftName, [
3026
+ [ Opcodes.local_get, pointer ],
3027
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
3028
+ ]),
3029
+
3030
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
3031
+
3032
+ [ Opcodes.block, Blocktype.void ],
3033
+ [ Opcodes.block, Blocktype.void ],
3034
+ ...generate(scope, decl.body),
3035
+ [ Opcodes.end ],
3036
+
3037
+ // increment iter pointer by valtype size + 1
3038
+ [ Opcodes.local_get, pointer ],
3039
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3040
+ [ Opcodes.i32_add ],
3041
+ [ Opcodes.local_set, pointer ],
3042
+
3043
+ // increment counter by 1
3044
+ [ Opcodes.local_get, counter ],
3045
+ ...number(1, Valtype.i32),
3046
+ [ Opcodes.i32_add ],
3047
+ [ Opcodes.local_tee, counter ],
3048
+
3049
+ // loop if counter != length
3050
+ [ Opcodes.local_get, length ],
3051
+ [ Opcodes.i32_ne ],
3052
+ [ Opcodes.br_if, 1 ],
3053
+
3054
+ [ Opcodes.end ],
3055
+ [ Opcodes.end ]
3056
+ ],
2872
3057
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2873
3058
  }, Blocktype.void));
2874
3059
 
@@ -2970,14 +3155,18 @@ const generateThrow = (scope, decl) => {
2970
3155
  };
2971
3156
 
2972
3157
  const generateTry = (scope, decl) => {
2973
- if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
3158
+ // todo: handle control-flow pre-exit for finally
3159
+ // "Immediately before a control-flow statement (return, throw, break, continue) is executed in the try block or catch block."
2974
3160
 
2975
3161
  const out = [];
2976
3162
 
3163
+ const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
3164
+
2977
3165
  out.push([ Opcodes.try, Blocktype.void ]);
2978
3166
  depth.push('try');
2979
3167
 
2980
3168
  out.push(...generate(scope, decl.block));
3169
+ out.push(...finalizer);
2981
3170
 
2982
3171
  if (decl.handler) {
2983
3172
  depth.pop();
@@ -2985,6 +3174,7 @@ const generateTry = (scope, decl) => {
2985
3174
 
2986
3175
  out.push([ Opcodes.catch_all ]);
2987
3176
  out.push(...generate(scope, decl.handler.body));
3177
+ out.push(...finalizer);
2988
3178
  }
2989
3179
 
2990
3180
  out.push([ Opcodes.end ]);
@@ -3070,7 +3260,7 @@ const getAllocType = itemType => {
3070
3260
  }
3071
3261
  };
3072
3262
 
3073
- const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
3263
+ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype, typed = false) => {
3074
3264
  const out = [];
3075
3265
 
3076
3266
  scope.arrays ??= new Map();
@@ -3082,8 +3272,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3082
3272
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
3083
3273
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
3084
3274
 
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);
3275
+ let page;
3276
+ if (Prefs.scopedPageNames) page = allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType);
3277
+ else page = allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType);
3278
+
3279
+ // hack: use 1 for page 0 pointer for fast truthiness
3280
+ const ptr = page === 0 ? 1 : (page * pageSize);
3281
+ scope.arrays.set(name, ptr);
3087
3282
  }
3088
3283
 
3089
3284
  const pointer = scope.arrays.get(name);
@@ -3133,7 +3328,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3133
3328
 
3134
3329
  const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3135
3330
 
3136
- // store length as 0th array
3331
+ // store length
3137
3332
  out.push(
3138
3333
  ...pointerWasm,
3139
3334
  ...number(length, Valtype.i32),
@@ -3141,14 +3336,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3141
3336
  );
3142
3337
 
3143
3338
  const storeOp = StoreOps[itemType];
3144
-
3339
+ const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
3145
3340
  if (!initEmpty) for (let i = 0; i < length; i++) {
3146
3341
  if (elements[i] == null) continue;
3147
3342
 
3343
+ const offset = ValtypeSize.i32 + i * sizePerEl;
3148
3344
  out.push(
3149
3345
  ...pointerWasm,
3150
3346
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
3151
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3347
+ [ storeOp, 0, ...unsignedLEB128(offset) ],
3348
+ ...(!typed ? [] : [ // typed presumes !useRawElements
3349
+ ...pointerWasm,
3350
+ ...getNodeType(scope, elements[i]),
3351
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(offset + ValtypeSize[itemType]) ]
3352
+ ])
3152
3353
  );
3153
3354
  }
3154
3355
 
@@ -3158,6 +3359,65 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3158
3359
  return [ out, pointer ];
3159
3360
  };
3160
3361
 
3362
+ const storeArray = (scope, array, index, element, aotPointer = null) => {
3363
+ if (!Array.isArray(element)) element = generate(scope, element);
3364
+ if (typeof index === 'number') index = number(index);
3365
+
3366
+ const offset = localTmp(scope, '#storeArray_offset', Valtype.i32);
3367
+
3368
+ return [
3369
+ // calculate offset
3370
+ ...index,
3371
+ Opcodes.i32_to_u,
3372
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3373
+ [ Opcodes.i32_mul ],
3374
+ ...(aotPointer ? [] : [
3375
+ ...array,
3376
+ Opcodes.i32_to_u,
3377
+ [ Opcodes.i32_add ],
3378
+ ]),
3379
+ [ Opcodes.local_set, offset ],
3380
+
3381
+ // store value
3382
+ [ Opcodes.local_get, offset ],
3383
+ ...generate(scope, element),
3384
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3385
+
3386
+ // store type
3387
+ [ Opcodes.local_get, offset ],
3388
+ ...getNodeType(scope, element),
3389
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3390
+ ];
3391
+ };
3392
+
3393
+ const loadArray = (scope, array, index, aotPointer = null) => {
3394
+ if (typeof index === 'number') index = number(index);
3395
+
3396
+ const offset = localTmp(scope, '#loadArray_offset', Valtype.i32);
3397
+
3398
+ return [
3399
+ // calculate offset
3400
+ ...index,
3401
+ Opcodes.i32_to_u,
3402
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3403
+ [ Opcodes.i32_mul ],
3404
+ ...(aotPointer ? [] : [
3405
+ ...array,
3406
+ Opcodes.i32_to_u,
3407
+ [ Opcodes.i32_add ],
3408
+ ]),
3409
+ [ Opcodes.local_set, offset ],
3410
+
3411
+ // load value
3412
+ [ Opcodes.local_get, offset ],
3413
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3414
+
3415
+ // load type
3416
+ [ Opcodes.local_get, offset ],
3417
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3418
+ ];
3419
+ };
3420
+
3161
3421
  const byteStringable = str => {
3162
3422
  if (!Prefs.bytestring) return false;
3163
3423
 
@@ -3186,14 +3446,28 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
3186
3446
  };
3187
3447
 
3188
3448
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
3189
- return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
3449
+ return makeArray(scope, decl, global, name, initEmpty, valtype, true)[0];
3190
3450
  };
3191
3451
 
3192
- export const generateMember = (scope, decl, _global, _name) => {
3452
+ const generateObject = (scope, decl, global = false, name = '$undeclared') => {
3453
+ if (decl.properties.length > 0) return todo(scope, 'objects are not supported yet', true);
3454
+
3455
+ return [
3456
+ ...number(1),
3457
+ ...setLastType(scope, TYPES.object)
3458
+ ];
3459
+ };
3460
+
3461
+ const withType = (scope, wasm, type) => [
3462
+ ...wasm,
3463
+ ...setLastType(scope, type)
3464
+ ];
3465
+
3466
+ const generateMember = (scope, decl, _global, _name) => {
3193
3467
  const name = decl.object.name;
3194
3468
  const pointer = scope.arrays?.get(name);
3195
3469
 
3196
- const aotPointer = Prefs.aotPointerOpt && pointer != null;
3470
+ const aotPointer = Prefs.aotPointerOpt && pointer;
3197
3471
 
3198
3472
  // hack: .name
3199
3473
  if (decl.property.name === 'name') {
@@ -3203,9 +3477,9 @@ export const generateMember = (scope, decl, _global, _name) => {
3203
3477
  // eg: __String_prototype_toLowerCase -> toLowerCase
3204
3478
  if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
3205
3479
 
3206
- return makeString(scope, nameProp, _global, _name, true);
3480
+ return withType(scope, makeString(scope, nameProp, _global, _name, true), TYPES.bytestring);
3207
3481
  } else {
3208
- return generate(scope, DEFAULT_VALUE);
3482
+ return withType(scope, number(0), TYPES.undefined);
3209
3483
  }
3210
3484
  }
3211
3485
 
@@ -3213,9 +3487,8 @@ export const generateMember = (scope, decl, _global, _name) => {
3213
3487
  if (decl.property.name === 'length') {
3214
3488
  const func = funcs.find(x => x.name === name);
3215
3489
  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);
3490
+ const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
3491
+ return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3219
3492
  }
3220
3493
 
3221
3494
  if (builtinFuncs[name + '$constructor']) {
@@ -3225,24 +3498,88 @@ export const generateMember = (scope, decl, _global, _name) => {
3225
3498
  const constructorFunc = builtinFuncs[name + '$constructor'];
3226
3499
  const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3227
3500
 
3228
- return number(Math.max(regularParams, constructorParams));
3501
+ return withType(scope, number(Math.max(regularParams, constructorParams)), TYPES.number);
3229
3502
  }
3230
3503
 
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);
3504
+ if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
3505
+ if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params), TYPES.number);
3506
+ if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
3507
+
3508
+ if (Prefs.fastLength) {
3509
+ // presume valid length object
3510
+ return [
3511
+ ...(aotPointer ? number(0, Valtype.i32) : [
3512
+ ...generate(scope, decl.object),
3513
+ Opcodes.i32_to_u
3514
+ ]),
3515
+
3516
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3517
+ Opcodes.i32_from_u
3518
+ ];
3519
+ }
3520
+
3521
+ const type = getNodeType(scope, decl.object);
3522
+ const known = knownType(scope, type);
3523
+ if (known != null) {
3524
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
3525
+ ...(aotPointer ? number(0, Valtype.i32) : [
3526
+ ...generate(scope, decl.object),
3527
+ Opcodes.i32_to_u
3528
+ ]),
3529
+
3530
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3531
+ Opcodes.i32_from_u
3532
+ ];
3533
+
3534
+ return number(0);
3535
+ }
3234
3536
 
3235
3537
  return [
3236
- ...(aotPointer ? number(0, Valtype.i32) : [
3237
- ...generate(scope, decl.object),
3238
- Opcodes.i32_to_u
3239
- ]),
3538
+ ...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
3539
+ [ Opcodes.if, valtypeBinary ],
3540
+ ...(aotPointer ? number(0, Valtype.i32) : [
3541
+ ...generate(scope, decl.object),
3542
+ Opcodes.i32_to_u
3543
+ ]),
3240
3544
 
3241
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128((aotPointer ? pointer : 0)) ],
3242
- Opcodes.i32_from_u
3545
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3546
+ Opcodes.i32_from_u,
3547
+
3548
+ ...setLastType(scope, TYPES.number),
3549
+ [ Opcodes.else ],
3550
+ ...number(0),
3551
+ ...setLastType(scope, TYPES.undefined),
3552
+ [ Opcodes.end ]
3243
3553
  ];
3244
3554
  }
3245
3555
 
3556
+ // todo: generate this array procedurally during builtinFuncs creation
3557
+ if (['size', 'description'].includes(decl.property.name)) {
3558
+ const bc = {};
3559
+ const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3560
+
3561
+ if (cands.length > 0) {
3562
+ for (const x of cands) {
3563
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
3564
+ if (type == null) continue;
3565
+
3566
+ bc[type] = generateCall(scope, {
3567
+ callee: {
3568
+ type: 'Identifier',
3569
+ name: x
3570
+ },
3571
+ arguments: [ decl.object ],
3572
+ _protoInternalCall: true
3573
+ });
3574
+ }
3575
+ }
3576
+
3577
+ return typeSwitch(scope, getNodeType(scope, decl.object), {
3578
+ ...bc,
3579
+ default: withType(scope, number(0), TYPES.undefined)
3580
+ }, valtypeBinary);
3581
+ }
3582
+
3246
3583
  const object = generate(scope, decl.object);
3247
3584
  const property = generate(scope, decl.property);
3248
3585
 
@@ -3257,24 +3594,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3257
3594
 
3258
3595
  return typeSwitch(scope, getNodeType(scope, decl.object), {
3259
3596
  [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),
3597
+ ...loadArray(scope, object, property, aotPointer),
3278
3598
  ...setLastType(scope)
3279
3599
  ],
3280
3600
 
@@ -3305,9 +3625,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3305
3625
 
3306
3626
  // return new string (page)
3307
3627
  ...number(newPointer),
3308
-
3309
- ...number(TYPES.string, Valtype.i32),
3310
- ...setLastType(scope)
3628
+ ...setLastType(scope, TYPES.string)
3311
3629
  ],
3312
3630
  [TYPES.bytestring]: [
3313
3631
  // setup new/out array
@@ -3333,9 +3651,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3333
3651
 
3334
3652
  // return new string (page)
3335
3653
  ...number(newPointer),
3336
-
3337
- ...number(TYPES.bytestring, Valtype.i32),
3338
- ...setLastType(scope)
3654
+ ...setLastType(scope, TYPES.bytestring)
3339
3655
  ],
3340
3656
 
3341
3657
  default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
@@ -3360,7 +3676,7 @@ const objectHack = node => {
3360
3676
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3361
3677
 
3362
3678
  // if .name or .length, give up (hack within a hack!)
3363
- if (['name', 'length'].includes(node.property.name)) {
3679
+ if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
3364
3680
  node.object = objectHack(node.object);
3365
3681
  return;
3366
3682
  }
@@ -3397,33 +3713,39 @@ const generateFunc = (scope, decl) => {
3397
3713
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3398
3714
  const params = decl.params ?? [];
3399
3715
 
3400
- // const innerScope = { ...scope };
3401
3716
  // TODO: share scope/locals between !!!
3402
- const innerScope = {
3717
+ const func = {
3403
3718
  locals: {},
3404
3719
  localInd: 0,
3405
3720
  // value, type
3406
3721
  returns: [ valtypeBinary, Valtype.i32 ],
3407
3722
  throws: false,
3408
- name
3723
+ name,
3724
+ index: currentFuncIndex++
3409
3725
  };
3410
3726
 
3411
3727
  if (typedInput && decl.returnType) {
3412
3728
  const { type } = extractTypeAnnotation(decl.returnType);
3413
- if (type != null && !Prefs.indirectCalls) {
3414
- innerScope.returnType = type;
3415
- innerScope.returns = [ valtypeBinary ];
3729
+ // if (type != null && !Prefs.indirectCalls) {
3730
+ if (type != null) {
3731
+ func.returnType = type;
3732
+ func.returns = [ valtypeBinary ];
3416
3733
  }
3417
3734
  }
3418
3735
 
3419
3736
  for (let i = 0; i < params.length; i++) {
3420
- allocVar(innerScope, params[i].name, false);
3737
+ const name = params[i].name;
3738
+ // if (name == null) return todo('non-identifier args are not supported');
3739
+
3740
+ allocVar(func, name, false);
3421
3741
 
3422
3742
  if (typedInput && params[i].typeAnnotation) {
3423
- addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
3743
+ addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
3424
3744
  }
3425
3745
  }
3426
3746
 
3747
+ func.params = Object.values(func.locals).map(x => x.type);
3748
+
3427
3749
  let body = objectHack(decl.body);
3428
3750
  if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
3429
3751
  // hack: () => 0 -> () => return 0
@@ -3433,37 +3755,23 @@ const generateFunc = (scope, decl) => {
3433
3755
  };
3434
3756
  }
3435
3757
 
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
3758
  funcIndex[name] = func.index;
3759
+ funcs.push(func);
3444
3760
 
3445
- if (name === 'main') func.gotLastType = true;
3761
+ const wasm = generate(func, body);
3762
+ func.wasm = wasm;
3446
3763
 
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
- }
3764
+ if (name === 'main') func.gotLastType = true;
3453
3765
 
3454
3766
  // add end return if not found
3455
3767
  if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
3456
3768
  wasm.push(
3457
3769
  ...number(0),
3458
- ...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3770
+ ...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3459
3771
  [ Opcodes.return ]
3460
3772
  );
3461
3773
  }
3462
3774
 
3463
- func.wasm = wasm;
3464
-
3465
- funcs.push(func);
3466
-
3467
3775
  return func;
3468
3776
  };
3469
3777
 
@@ -3567,7 +3875,7 @@ const internalConstrs = {
3567
3875
  generate: (scope, decl) => {
3568
3876
  // todo: boolean object when used as constructor
3569
3877
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3570
- return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3878
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg), false, false, 'full');
3571
3879
  },
3572
3880
  type: TYPES.boolean,
3573
3881
  length: 1
@@ -3628,8 +3936,10 @@ const internalConstrs = {
3628
3936
  }),
3629
3937
 
3630
3938
  // print space
3631
- ...number(32),
3632
- [ Opcodes.call, importedFuncs.printChar ]
3939
+ ...(i !== decl.arguments.length - 1 ? [
3940
+ ...number(32),
3941
+ [ Opcodes.call, importedFuncs.printChar ]
3942
+ ] : [])
3633
3943
  );
3634
3944
  }
3635
3945
 
@@ -3639,6 +3949,8 @@ const internalConstrs = {
3639
3949
  [ Opcodes.call, importedFuncs.printChar ]
3640
3950
  );
3641
3951
 
3952
+ out.push(...number(UNDEFINED));
3953
+
3642
3954
  return out;
3643
3955
  },
3644
3956
  type: TYPES.undefined,
@@ -3708,9 +4020,8 @@ export default program => {
3708
4020
 
3709
4021
  if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3710
4022
 
3711
- generateFunc(scope, program);
4023
+ const main = generateFunc(scope, program);
3712
4024
 
3713
- const main = funcs[funcs.length - 1];
3714
4025
  main.export = true;
3715
4026
  main.returns = [ valtypeBinary, Valtype.i32 ];
3716
4027
 
@@ -3737,7 +4048,7 @@ export default program => {
3737
4048
  }
3738
4049
 
3739
4050
  // 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);
4051
+ if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(main.index - importedFuncs.length, 1);
3741
4052
 
3742
4053
  return { funcs, globals, tags, exceptions, pages, data };
3743
4054
  };