porffor 0.14.0-2ce6edbb3 → 0.14.0-320727338

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,11 +40,11 @@ const todo = (scope, msg, expectsValue = undefined) => {
40
40
  }
41
41
  };
42
42
 
43
- const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
44
- const hasFuncWithName = name => {
45
- const func = funcs.find(x => x.name === name);
46
- return !!(func || builtinFuncs[name] || importedFuncs[name] || internalConstrs[name]);
47
- };
43
+ const isFuncType = type =>
44
+ type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
45
+ const hasFuncWithName = name =>
46
+ funcIndex[name] != null || builtinFuncs[name] != null || importedFuncs[name] != null || internalConstrs[name] != null;
47
+
48
48
  const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
49
49
  switch (decl.type) {
50
50
  case 'BinaryExpression':
@@ -147,14 +147,16 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
147
147
  return generateMember(scope, decl, global, name);
148
148
 
149
149
  case 'ExportNamedDeclaration':
150
- // hack to flag new func for export
151
- const funcsBefore = funcs.length;
150
+ const funcsBefore = funcs.map(x => x.name);
152
151
  generate(scope, decl.declaration);
153
152
 
154
- if (funcsBefore !== funcs.length) {
155
- // new func added
156
- const newFunc = funcs[funcs.length - 1];
157
- newFunc.export = true;
153
+ // set new funcs as exported
154
+ if (funcsBefore.length !== funcs.length) {
155
+ const newFuncs = funcs.filter(x => !funcsBefore.includes(x.name)).filter(x => !x.internal);
156
+
157
+ for (const x of newFuncs) {
158
+ x.export = true;
159
+ }
158
160
  }
159
161
 
160
162
  return [];
@@ -203,19 +205,11 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
203
205
 
204
206
  __Porffor_bs: str => [
205
207
  ...makeString(scope, str, global, name, true),
206
-
207
- ...(name ? setType(scope, name, TYPES.bytestring) : [
208
- ...number(TYPES.bytestring, Valtype.i32),
209
- ...setLastType(scope)
210
- ])
208
+ ...(name ? setType(scope, name, TYPES.bytestring) : setLastType(scope, TYPES.bytestring))
211
209
  ],
212
210
  __Porffor_s: str => [
213
211
  ...makeString(scope, str, global, name, false),
214
-
215
- ...(name ? setType(scope, name, TYPES.string) : [
216
- ...number(TYPES.string, Valtype.i32),
217
- ...setLastType(scope)
218
- ])
212
+ ...(name ? setType(scope, name, TYPES.string) : setLastType(scope, TYPES.string))
219
213
  ],
220
214
  };
221
215
 
@@ -369,7 +363,7 @@ const localTmp = (scope, name, type = valtypeBinary) => {
369
363
  };
370
364
 
371
365
  const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
372
- const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
366
+ const isIntToFloatOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
373
367
 
374
368
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
375
369
  const checks = {
@@ -386,10 +380,10 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
386
380
 
387
381
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
388
382
  // (like if we are in an if condition - very common)
389
- const leftIsInt = isFloatToIntOp(left[left.length - 1]);
390
- const rightIsInt = isFloatToIntOp(right[right.length - 1]);
383
+ const leftWasInt = isIntToFloatOp(left[left.length - 1]);
384
+ const rightWasInt = isIntToFloatOp(right[right.length - 1]);
391
385
 
392
- const canInt = leftIsInt && rightIsInt;
386
+ const canInt = leftWasInt && rightWasInt;
393
387
 
394
388
  if (canInt) {
395
389
  // remove int -> float conversions from left and right
@@ -403,13 +397,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
403
397
  [ Opcodes.if, Valtype.i32 ],
404
398
  ...right,
405
399
  // note type
406
- ...rightType,
407
- ...setLastType(scope),
400
+ ...setLastType(scope, rightType),
408
401
  [ Opcodes.else ],
409
402
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
410
403
  // note type
411
- ...leftType,
412
- ...setLastType(scope),
404
+ ...setLastType(scope, leftType),
413
405
  [ Opcodes.end ],
414
406
  Opcodes.i32_from
415
407
  ];
@@ -422,13 +414,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
422
414
  [ Opcodes.if, valtypeBinary ],
423
415
  ...right,
424
416
  // note type
425
- ...rightType,
426
- ...setLastType(scope),
417
+ ...setLastType(scope, rightType),
427
418
  [ Opcodes.else ],
428
419
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
429
420
  // note type
430
- ...leftType,
431
- ...setLastType(scope),
421
+ ...setLastType(scope, leftType),
432
422
  [ Opcodes.end ]
433
423
  ];
434
424
  };
@@ -456,11 +446,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
456
446
  ...number(0, Valtype.i32), // base 0 for store later
457
447
 
458
448
  ...number(pointer, Valtype.i32),
459
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
449
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
460
450
  [ Opcodes.local_tee, leftLength ],
461
451
 
462
452
  [ Opcodes.local_get, rightPointer ],
463
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
453
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
464
454
  [ Opcodes.local_tee, rightLength ],
465
455
 
466
456
  [ Opcodes.i32_add ],
@@ -516,11 +506,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
516
506
  ...number(0, Valtype.i32), // base 0 for store later
517
507
 
518
508
  [ Opcodes.local_get, leftPointer ],
519
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
509
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
520
510
  [ Opcodes.local_tee, leftLength ],
521
511
 
522
512
  [ Opcodes.local_get, rightPointer ],
523
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
513
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
524
514
  [ Opcodes.local_tee, rightLength ],
525
515
 
526
516
  [ Opcodes.i32_add ],
@@ -598,11 +588,11 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
598
588
 
599
589
  // get lengths
600
590
  [ Opcodes.local_get, leftPointer ],
601
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
591
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
602
592
  [ Opcodes.local_tee, leftLength ],
603
593
 
604
594
  [ Opcodes.local_get, rightPointer ],
605
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
595
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
606
596
 
607
597
  // fast path: check leftLength != rightLength
608
598
  [ Opcodes.i32_ne ],
@@ -658,9 +648,9 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
658
648
  [ Opcodes.i32_add ],
659
649
  [ Opcodes.local_tee, index ],
660
650
 
661
- // if index != index end (length * sizeof valtype), loop
651
+ // if index < index end (length * sizeof valtype), loop
662
652
  [ Opcodes.local_get, indexEnd ],
663
- [ Opcodes.i32_ne ],
653
+ [ Opcodes.i32_lt_s ],
664
654
  [ Opcodes.br_if, 0 ],
665
655
  [ Opcodes.end ],
666
656
 
@@ -678,8 +668,8 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
678
668
  ];
679
669
  };
680
670
 
681
- const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
682
- 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 [
683
673
  ...wasm,
684
674
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
685
675
  ];
@@ -688,28 +678,38 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
688
678
  const useTmp = knownType(scope, type) == null;
689
679
  const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
690
680
 
691
- const def = [
692
- // if value != 0
693
- ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
681
+ const def = (truthyMode => {
682
+ if (truthyMode === 'full') return [
683
+ // if value != 0 or NaN
684
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
685
+ ...(intIn ? [ ] : [ Opcodes.i32_to ]),
694
686
 
695
- // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
696
- ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
687
+ [ Opcodes.i32_eqz ],
688
+ [ Opcodes.i32_eqz ],
697
689
 
698
- /* Opcodes.eqz,
699
- [ Opcodes.i32_eqz ],
700
- Opcodes.i32_from */
701
- ];
690
+ ...(intOut ? [] : [ Opcodes.i32_from ]),
691
+ ];
692
+
693
+ if (truthyMode === 'no_negative') return [
694
+ // if value != 0 or NaN, non-binary output. negative numbers not truthy :/
695
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
696
+ ...(intIn ? [] : [ Opcodes.i32_to ]),
697
+ ...(intOut ? [] : [ Opcodes.i32_from ])
698
+ ];
699
+
700
+ if (truthyMode === 'no_nan_negative') return [
701
+ // simpler and faster but makes NaN truthy and negative numbers not truthy,
702
+ // plus non-binary output
703
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
704
+ ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ])
705
+ ];
706
+ })(forceTruthyMode ?? Prefs.truthy ?? 'full');
702
707
 
703
708
  return [
704
709
  ...wasm,
705
710
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
706
711
 
707
712
  ...typeSwitch(scope, type, {
708
- // [TYPES.number]: def,
709
- [TYPES.array]: [
710
- // arrays are always truthy
711
- ...number(1, intOut ? Valtype.i32 : valtypeBinary)
712
- ],
713
713
  [TYPES.string]: [
714
714
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
715
715
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -745,10 +745,6 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
745
745
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
746
746
 
747
747
  ...typeSwitch(scope, type, {
748
- [TYPES.array]: [
749
- // arrays are always truthy
750
- ...number(0, intOut ? Valtype.i32 : valtypeBinary)
751
- ],
752
748
  [TYPES.string]: [
753
749
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
754
750
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -992,7 +988,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
992
988
  // if both are true
993
989
  [ Opcodes.i32_and ],
994
990
  [ Opcodes.if, Blocktype.void ],
995
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
991
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], false),
996
992
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
997
993
  [ Opcodes.br, 1 ],
998
994
  [ Opcodes.end ],
@@ -1041,14 +1037,14 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
1041
1037
  return out;
1042
1038
  };
1043
1039
 
1044
- const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1045
- return func({ name, params, locals, returns, localInd }, {
1040
+ const asmFuncToAsm = (func, scope) => {
1041
+ return func(scope, {
1046
1042
  TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
1047
- builtin: name => {
1048
- let idx = funcIndex[name] ?? importedFuncs[name];
1049
- if (idx === undefined && builtinFuncs[name]) {
1050
- includeBuiltin(null, name);
1051
- idx = funcIndex[name];
1043
+ builtin: n => {
1044
+ let idx = funcIndex[n] ?? importedFuncs[n];
1045
+ if (idx == null && builtinFuncs[n]) {
1046
+ includeBuiltin(null, n);
1047
+ idx = funcIndex[n];
1052
1048
  }
1053
1049
 
1054
1050
  return idx;
@@ -1056,7 +1052,7 @@ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals =
1056
1052
  });
1057
1053
  };
1058
1054
 
1059
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
1055
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false }) => {
1060
1056
  const existing = funcs.find(x => x.name === name);
1061
1057
  if (existing) return existing;
1062
1058
 
@@ -1074,7 +1070,22 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1074
1070
  data.push(copy);
1075
1071
  }
1076
1072
 
1077
- if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1073
+ const func = {
1074
+ name,
1075
+ params,
1076
+ locals,
1077
+ localInd: allLocals.length,
1078
+ returns,
1079
+ returnType: returnType ?? TYPES.number,
1080
+ internal: true,
1081
+ index: currentFuncIndex++,
1082
+ table
1083
+ };
1084
+
1085
+ funcs.push(func);
1086
+ funcIndex[name] = func.index;
1087
+
1088
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, func);
1078
1089
 
1079
1090
  let baseGlobalIdx, i = 0;
1080
1091
  for (const type of globalTypes) {
@@ -1093,19 +1104,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1093
1104
  }
1094
1105
  }
1095
1106
 
1096
- const func = {
1097
- name,
1098
- params,
1099
- locals,
1100
- returns,
1101
- returnType: returnType ?? TYPES.number,
1102
- wasm,
1103
- internal: true,
1104
- index: currentFuncIndex++
1105
- };
1107
+ if (table) for (const inst of wasm) {
1108
+ if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
1109
+ inst.splice(2, 99);
1110
+ inst.push(...unsignedLEB128(allocPage({}, 'func argc lut') * pageSize));
1111
+ }
1112
+ }
1106
1113
 
1107
- funcs.push(func);
1108
- funcIndex[name] = func.index;
1114
+ func.wasm = wasm;
1109
1115
 
1110
1116
  return func;
1111
1117
  };
@@ -1197,9 +1203,10 @@ const getLastType = scope => {
1197
1203
  return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1198
1204
  };
1199
1205
 
1200
- const setLastType = scope => {
1201
- return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1202
- };
1206
+ const setLastType = (scope, type = []) => [
1207
+ ...(typeof type === 'number' ? number(type, Valtype.i32) : type),
1208
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1209
+ ];
1203
1210
 
1204
1211
  const getNodeType = (scope, node) => {
1205
1212
  const ret = (() => {
@@ -1260,7 +1267,17 @@ const getNodeType = (scope, node) => {
1260
1267
 
1261
1268
  const func = spl[spl.length - 1];
1262
1269
  const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
1263
- if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1270
+ if (protoFuncs.length === 1) {
1271
+ if (protoFuncs[0].returnType) return protoFuncs[0].returnType;
1272
+ }
1273
+
1274
+ if (protoFuncs.length > 0) {
1275
+ if (scope.locals['#last_type']) return getLastType(scope);
1276
+
1277
+ // presume
1278
+ // todo: warn here?
1279
+ return TYPES.number;
1280
+ }
1264
1281
  }
1265
1282
 
1266
1283
  if (name.startsWith('__Porffor_wasm_')) {
@@ -1355,22 +1372,27 @@ const getNodeType = (scope, node) => {
1355
1372
  }
1356
1373
 
1357
1374
  if (node.type === 'MemberExpression') {
1358
- // hack: if something.name, string type
1359
- if (node.property.name === 'name') {
1360
- if (hasFuncWithName(node.object.name)) {
1361
- return TYPES.bytestring;
1362
- } else {
1363
- return TYPES.undefined;
1364
- }
1375
+ const name = node.property.name;
1376
+
1377
+ if (name === 'length') {
1378
+ if (hasFuncWithName(node.object.name)) return TYPES.number;
1379
+ if (Prefs.fastLength) return TYPES.number;
1365
1380
  }
1366
1381
 
1367
- // hack: if something.length, number type
1368
- if (node.property.name === 'length') return TYPES.number;
1369
1382
 
1370
- // ts hack
1371
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1372
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.bytestring) return TYPES.bytestring;
1373
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.array) return TYPES.number;
1383
+ const objectKnownType = knownType(scope, getNodeType(scope, node.object));
1384
+ if (objectKnownType != null) {
1385
+ if (name === 'length') {
1386
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(objectKnownType)) return TYPES.number;
1387
+ else return TYPES.undefined;
1388
+ }
1389
+
1390
+ if (node.computed) {
1391
+ if (objectKnownType === TYPES.string) return TYPES.string;
1392
+ if (objectKnownType === TYPES.bytestring) return TYPES.bytestring;
1393
+ if (objectKnownType === TYPES.array) return TYPES.number;
1394
+ }
1395
+ }
1374
1396
 
1375
1397
  if (scope.locals['#last_type']) return getLastType(scope);
1376
1398
 
@@ -1441,16 +1463,11 @@ const countLeftover = wasm => {
1441
1463
  else if (inst[0] === Opcodes.return) count = 0;
1442
1464
  else if (inst[0] === Opcodes.call) {
1443
1465
  let func = funcs.find(x => x.index === inst[1]);
1444
- if (inst[1] === -1) {
1445
- // todo: count for calling self
1446
- } else if (!func && inst[1] < importedFuncs.length) {
1447
- count -= importedFuncs[inst[1]].params;
1448
- count += importedFuncs[inst[1]].returns;
1466
+ if (inst[1] < importedFuncs.length) {
1467
+ func = importedFuncs[inst[1]];
1468
+ count = count - func.params + func.returns;
1449
1469
  } else {
1450
- if (func) {
1451
- count -= func.params.length;
1452
- } else count--;
1453
- if (func) count += func.returns.length;
1470
+ count = count - func.params.length + func.returns.length;
1454
1471
  }
1455
1472
  } else if (inst[0] === Opcodes.call_indirect) {
1456
1473
  count--; // funcidx
@@ -1473,7 +1490,7 @@ const disposeLeftover = wasm => {
1473
1490
  const generateExp = (scope, decl) => {
1474
1491
  const expression = decl.expression;
1475
1492
 
1476
- const out = generate(scope, expression, undefined, undefined, true);
1493
+ const out = generate(scope, expression, undefined, undefined, Prefs.optUnused);
1477
1494
  disposeLeftover(out);
1478
1495
 
1479
1496
  return out;
@@ -1564,16 +1581,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1564
1581
  out.splice(out.length - 1, 1);
1565
1582
 
1566
1583
  const finalStatement = parsed.body[parsed.body.length - 1];
1567
- out.push(
1568
- ...getNodeType(scope, finalStatement),
1569
- ...setLastType(scope)
1570
- );
1584
+ out.push(...setLastType(scope, getNodeType(scope, finalStatement)));
1571
1585
  } else if (countLeftover(out) === 0) {
1572
1586
  out.push(...number(UNDEFINED));
1573
- out.push(
1574
- ...number(TYPES.undefined, Valtype.i32),
1575
- ...setLastType(scope)
1576
- );
1587
+ out.push(...setLastType(scope, TYPES.undefined));
1577
1588
  }
1578
1589
 
1579
1590
  // if (lastInst && lastInst[0] === Opcodes.drop) {
@@ -1609,6 +1620,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1609
1620
 
1610
1621
  if (!funcIndex[rhemynName]) {
1611
1622
  const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1623
+ func.internal = true;
1612
1624
 
1613
1625
  funcIndex[func.name] = func.index;
1614
1626
  funcs.push(func);
@@ -1625,8 +1637,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1625
1637
  [ Opcodes.call, idx ],
1626
1638
  Opcodes.i32_from_u,
1627
1639
 
1628
- ...number(TYPES.boolean, Valtype.i32),
1629
- ...setLastType(scope)
1640
+ ...setLastType(scope, TYPES.boolean)
1630
1641
  ];
1631
1642
  }
1632
1643
 
@@ -1698,9 +1709,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1698
1709
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
1699
1710
  protoBC[x] = [
1700
1711
  ...RTArrayUtil.getLength(getPointer),
1701
-
1702
- ...number(TYPES.number, Valtype.i32),
1703
- ...setLastType(scope)
1712
+ ...setLastType(scope, TYPES.number)
1704
1713
  ];
1705
1714
  continue;
1706
1715
  }
@@ -1719,7 +1728,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1719
1728
  getI32: () => RTArrayUtil.getLengthI32(getPointer),
1720
1729
  set: value => RTArrayUtil.setLength(getPointer, value),
1721
1730
  setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
1722
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1731
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), getNodeType(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1723
1732
  return makeArray(scope, {
1724
1733
  rawElements: new Array(length)
1725
1734
  }, _global, _name, true, itemType);
@@ -1733,9 +1742,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1733
1742
  protoBC[x] = [
1734
1743
  [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1735
1744
  ...protoOut,
1736
-
1737
- ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1738
- ...setLastType(scope),
1745
+ ...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
1739
1746
  [ Opcodes.end ]
1740
1747
  ];
1741
1748
  }
@@ -1785,11 +1792,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1785
1792
 
1786
1793
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1787
1794
 
1788
- if (idx === undefined && name === scope.name) {
1789
- // hack: calling self, func generator will fix later
1790
- idx = -1;
1791
- }
1792
-
1793
1795
  if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1794
1796
  const wasmOps = {
1795
1797
  // pointer, align, offset
@@ -1841,36 +1843,128 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1841
1843
  const [ local, global ] = lookupName(scope, name);
1842
1844
  if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1843
1845
 
1844
- // todo: only works when:
1845
- // 1. arg count matches arg count of function
1846
- // 2. function uses typedParams and typedReturns
1846
+ // todo: only works when function uses typedParams and typedReturns
1847
+
1848
+ const indirectMode = Prefs.indirectCallMode ?? 'vararg';
1849
+ // options: vararg, strict
1850
+ // - strict: simpler, smaller size usage, no func argc lut needed.
1851
+ // ONLY works when arg count of call == arg count of function being called
1852
+ // - vararg: large size usage, cursed.
1853
+ // works when arg count of call != arg count of function being called*
1854
+ // * most of the time, some edgecases
1847
1855
 
1848
1856
  funcs.table = true;
1857
+ scope.table = true;
1849
1858
 
1850
1859
  let args = decl.arguments;
1851
- let argWasm = [];
1860
+ let out = [];
1861
+
1862
+ let locals = [];
1863
+
1864
+ if (indirectMode === 'vararg') {
1865
+ const minArgc = Prefs.indirectCallMinArgc ?? 3;
1866
+
1867
+ if (args.length < minArgc) {
1868
+ args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
1869
+ }
1870
+ }
1852
1871
 
1853
1872
  for (let i = 0; i < args.length; i++) {
1854
1873
  const arg = args[i];
1855
- argWasm = argWasm.concat(generate(scope, arg));
1874
+ out = out.concat(generate(scope, arg));
1856
1875
 
1857
1876
  if (valtypeBinary !== Valtype.i32 && (
1858
1877
  (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1859
1878
  (importedFuncs[name] && name.startsWith('profile'))
1860
1879
  )) {
1861
- argWasm.push(Opcodes.i32_to);
1880
+ out.push(Opcodes.i32_to);
1881
+ }
1882
+
1883
+ out = out.concat(getNodeType(scope, arg));
1884
+
1885
+ if (indirectMode === 'vararg') {
1886
+ const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
1887
+ const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
1888
+
1889
+ locals.push([valLocal, typeLocal]);
1890
+
1891
+ out.push(
1892
+ [ Opcodes.local_set, typeLocal ],
1893
+ [ Opcodes.local_set, valLocal ]
1894
+ );
1862
1895
  }
1896
+ }
1863
1897
 
1864
- argWasm = argWasm.concat(getNodeType(scope, arg));
1898
+ if (indirectMode === 'strict') {
1899
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1900
+ [TYPES.function]: [
1901
+ ...argWasm,
1902
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1903
+ Opcodes.i32_to_u,
1904
+ [ Opcodes.call_indirect, args.length, 0 ],
1905
+ ...setLastType(scope)
1906
+ ],
1907
+ default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1908
+ });
1865
1909
  }
1866
1910
 
1911
+ // hi, I will now explain how vararg mode works:
1912
+ // wasm's indirect_call instruction requires you know the func type at compile-time
1913
+ // since we have varargs (variable argument count), we do not know it.
1914
+ // we could just store args in memory and not use wasm func args,
1915
+ // but that is slow (probably) and breaks js exports.
1916
+ // instead, we generate every* possibility of argc and use different indirect_call
1917
+ // ops for each one, with type depending on argc for that branch.
1918
+ // then we load the argc for the wanted function from a memory lut,
1919
+ // and call the branch with the matching argc we require.
1920
+ // sorry, yes it is very cursed (and size inefficient), but indirect calls
1921
+ // are kind of rare anyway (mostly callbacks) so I am not concerned atm.
1922
+ // *for argc 0-3, in future (todo:) the max number should be
1923
+ // dynamically changed to the max argc of any func in the js file.
1924
+
1925
+ const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
1926
+
1927
+ const gen = argc => {
1928
+ const out = [];
1929
+ for (let i = 0; i < argc; i++) {
1930
+ out.push(
1931
+ [ Opcodes.local_get, locals[i][0] ],
1932
+ [ Opcodes.local_get, locals[i][1] ]
1933
+ );
1934
+ }
1935
+
1936
+ out.push(
1937
+ [ Opcodes.local_get, funcLocal ],
1938
+ [ Opcodes.call_indirect, argc, 0 ],
1939
+ ...setLastType(scope)
1940
+ )
1941
+
1942
+ return out;
1943
+ };
1944
+
1945
+ const tableBc = {};
1946
+ for (let i = 0; i <= args.length; i++) {
1947
+ tableBc[i] = gen(i);
1948
+ }
1949
+
1950
+ // todo/perf: check if we should use br_table here or just generate our own big if..elses
1951
+
1867
1952
  return typeSwitch(scope, getNodeType(scope, decl.callee), {
1868
1953
  [TYPES.function]: [
1869
- ...argWasm,
1954
+ ...out,
1955
+
1870
1956
  [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1871
1957
  Opcodes.i32_to_u,
1872
- [ Opcodes.call_indirect, args.length, 0 ],
1873
- ...setLastType(scope)
1958
+ [ Opcodes.local_set, funcLocal ],
1959
+
1960
+ ...brTable([
1961
+ // get argc of func we are calling
1962
+ [ Opcodes.local_get, funcLocal ],
1963
+ ...number(ValtypeSize.i16, Valtype.i32),
1964
+ [ Opcodes.i32_mul ],
1965
+
1966
+ [ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
1967
+ ], tableBc, valtypeBinary)
1874
1968
  ],
1875
1969
  default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1876
1970
  });
@@ -1879,11 +1973,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1879
1973
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1880
1974
  }
1881
1975
 
1882
- const func = funcs.find(x => x.index === idx);
1883
-
1884
- const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1976
+ const func = funcs[idx - importedFuncs.length]; // idx === scope.index ? scope : funcs.find(x => x.index === idx);
1977
+ const userFunc = func && !func.internal;
1885
1978
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1886
- const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1979
+ const typedReturns = (func && func.returnType == null) || builtinFuncs[name]?.typedReturns;
1887
1980
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1888
1981
 
1889
1982
  let args = decl.arguments;
@@ -1904,6 +1997,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1904
1997
  const arg = args[i];
1905
1998
  out = out.concat(generate(scope, arg));
1906
1999
 
2000
+ // todo: this should be used instead of the too many args thing above (by removing that)
1907
2001
  if (i >= paramCount) {
1908
2002
  // over param count of func, drop arg
1909
2003
  out.push([ Opcodes.drop ]);
@@ -1988,8 +2082,11 @@ const knownType = (scope, type) => {
1988
2082
  const idx = type[0][1];
1989
2083
 
1990
2084
  // type idx = var idx + 1
1991
- const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
1992
- if (v.metadata?.type != null) return v.metadata.type;
2085
+ const name = Object.values(scope.locals).find(x => x.idx === idx)?.name;
2086
+ if (name) {
2087
+ const local = scope.locals[name];
2088
+ if (local.metadata?.type != null) return v.metadata.type;
2089
+ }
1993
2090
  }
1994
2091
 
1995
2092
  return null;
@@ -2024,16 +2121,17 @@ const brTable = (input, bc, returns) => {
2024
2121
  }
2025
2122
 
2026
2123
  for (let i = 0; i < count; i++) {
2027
- if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2124
+ // if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2125
+ if (i === 0) out.push([ Opcodes.block, returns ]);
2028
2126
  else out.push([ Opcodes.block, Blocktype.void ]);
2029
2127
  }
2030
2128
 
2031
- const nums = keys.filter(x => +x);
2129
+ const nums = keys.filter(x => +x >= 0);
2032
2130
  const offset = Math.min(...nums);
2033
2131
  const max = Math.max(...nums);
2034
2132
 
2035
2133
  const table = [];
2036
- let br = 1;
2134
+ let br = 0;
2037
2135
 
2038
2136
  for (let i = offset; i <= max; i++) {
2039
2137
  // if branch for this num, go to that block
@@ -2073,10 +2171,9 @@ const brTable = (input, bc, returns) => {
2073
2171
  br--;
2074
2172
  }
2075
2173
 
2076
- return [
2077
- ...out,
2078
- [ Opcodes.end, 'br table end' ]
2079
- ];
2174
+ out.push([ Opcodes.end ]);
2175
+
2176
+ return out;
2080
2177
  };
2081
2178
 
2082
2179
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
@@ -2120,6 +2217,17 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2120
2217
  return out;
2121
2218
  };
2122
2219
 
2220
+ const typeIsOneOf = (type, types, valtype = Valtype.i32) => {
2221
+ const out = [];
2222
+
2223
+ for (let i = 0; i < types.length; i++) {
2224
+ out.push(...type, ...number(types[i], valtype), valtype === Valtype.f64 ? [ Opcodes.f64_eq ] : [ Opcodes.i32_eq ]);
2225
+ if (i !== 0) out.push([ Opcodes.i32_or ]);
2226
+ }
2227
+
2228
+ return out;
2229
+ };
2230
+
2123
2231
  const allocVar = (scope, name, global = false, type = true) => {
2124
2232
  const target = global ? globals : scope.locals;
2125
2233
 
@@ -2136,7 +2244,7 @@ const allocVar = (scope, name, global = false, type = true) => {
2136
2244
 
2137
2245
  if (type) {
2138
2246
  let typeIdx = global ? globalInd++ : scope.localInd++;
2139
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2247
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32, name };
2140
2248
  }
2141
2249
 
2142
2250
  return idx;
@@ -2349,18 +2457,21 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2349
2457
  Opcodes.i32_to_u,
2350
2458
 
2351
2459
  // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2352
- ...number(ValtypeSize[valtype], Valtype.i32),
2460
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2353
2461
  [ Opcodes.i32_mul ],
2354
2462
  ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
2355
2463
  ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2356
2464
 
2357
2465
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2358
2466
  [ Opcodes.local_get, pointerTmp ],
2359
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2360
- ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2467
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2468
+ ], generate(scope, decl.right), [
2469
+ [ Opcodes.local_get, pointerTmp ],
2470
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
2471
+ ], getNodeType(scope, decl.right), false, name, true)),
2361
2472
  [ Opcodes.local_tee, newValueTmp ],
2362
2473
 
2363
- [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2474
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2364
2475
  ],
2365
2476
 
2366
2477
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
@@ -2468,6 +2579,7 @@ const generateUnary = (scope, decl) => {
2468
2579
  ];
2469
2580
 
2470
2581
  case '!':
2582
+ // todo/perf: optimize !!
2471
2583
  // !=
2472
2584
  return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
2473
2585
 
@@ -2614,21 +2726,16 @@ const generateConditional = (scope, decl) => {
2614
2726
  out.push([ Opcodes.if, valtypeBinary ]);
2615
2727
  depth.push('if');
2616
2728
 
2617
- out.push(...generate(scope, decl.consequent));
2618
-
2619
- // note type
2620
2729
  out.push(
2621
- ...getNodeType(scope, decl.consequent),
2622
- ...setLastType(scope)
2730
+ ...generate(scope, decl.consequent),
2731
+ ...setLastType(scope, getNodeType(scope, decl.consequent))
2623
2732
  );
2624
2733
 
2625
2734
  out.push([ Opcodes.else ]);
2626
- out.push(...generate(scope, decl.alternate));
2627
2735
 
2628
- // note type
2629
2736
  out.push(
2630
- ...getNodeType(scope, decl.alternate),
2631
- ...setLastType(scope)
2737
+ ...generate(scope, decl.alternate),
2738
+ ...setLastType(scope, getNodeType(scope, decl.alternate))
2632
2739
  );
2633
2740
 
2634
2741
  out.push([ Opcodes.end ]);
@@ -2776,12 +2883,15 @@ const generateForOf = (scope, decl) => {
2776
2883
  // todo: optimize away counter and use end pointer
2777
2884
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2778
2885
  [TYPES.array]: [
2779
- ...setType(scope, leftName, TYPES.number),
2780
-
2781
2886
  [ Opcodes.loop, Blocktype.void ],
2782
2887
 
2783
2888
  [ Opcodes.local_get, pointer ],
2784
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2889
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2890
+
2891
+ ...setType(scope, leftName, [
2892
+ [ Opcodes.local_get, pointer ],
2893
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
2894
+ ]),
2785
2895
 
2786
2896
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2787
2897
 
@@ -2790,9 +2900,9 @@ const generateForOf = (scope, decl) => {
2790
2900
  ...generate(scope, decl.body),
2791
2901
  [ Opcodes.end ],
2792
2902
 
2793
- // increment iter pointer by valtype size
2903
+ // increment iter pointer by valtype size + 1
2794
2904
  [ Opcodes.local_get, pointer ],
2795
- ...number(ValtypeSize[valtype], Valtype.i32),
2905
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2796
2906
  [ Opcodes.i32_add ],
2797
2907
  [ Opcodes.local_set, pointer ],
2798
2908
 
@@ -3009,14 +3119,18 @@ const generateThrow = (scope, decl) => {
3009
3119
  };
3010
3120
 
3011
3121
  const generateTry = (scope, decl) => {
3012
- if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
3122
+ // todo: handle control-flow pre-exit for finally
3123
+ // "Immediately before a control-flow statement (return, throw, break, continue) is executed in the try block or catch block."
3013
3124
 
3014
3125
  const out = [];
3015
3126
 
3127
+ const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
3128
+
3016
3129
  out.push([ Opcodes.try, Blocktype.void ]);
3017
3130
  depth.push('try');
3018
3131
 
3019
3132
  out.push(...generate(scope, decl.block));
3133
+ out.push(...finalizer);
3020
3134
 
3021
3135
  if (decl.handler) {
3022
3136
  depth.pop();
@@ -3024,6 +3138,7 @@ const generateTry = (scope, decl) => {
3024
3138
 
3025
3139
  out.push([ Opcodes.catch_all ]);
3026
3140
  out.push(...generate(scope, decl.handler.body));
3141
+ out.push(...finalizer);
3027
3142
  }
3028
3143
 
3029
3144
  out.push([ Opcodes.end ]);
@@ -3109,7 +3224,7 @@ const getAllocType = itemType => {
3109
3224
  }
3110
3225
  };
3111
3226
 
3112
- const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
3227
+ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype, typed = false) => {
3113
3228
  const out = [];
3114
3229
 
3115
3230
  scope.arrays ??= new Map();
@@ -3121,8 +3236,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3121
3236
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
3122
3237
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
3123
3238
 
3124
- if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
3125
- else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3239
+ let page;
3240
+ if (Prefs.scopedPageNames) page = allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType);
3241
+ else page = allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType);
3242
+
3243
+ // hack: use 1 for page 0 pointer for fast truthiness
3244
+ const ptr = page === 0 ? 1 : (page * pageSize);
3245
+ scope.arrays.set(name, ptr);
3126
3246
  }
3127
3247
 
3128
3248
  const pointer = scope.arrays.get(name);
@@ -3172,7 +3292,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3172
3292
 
3173
3293
  const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3174
3294
 
3175
- // store length as 0th array
3295
+ // store length
3176
3296
  out.push(
3177
3297
  ...pointerWasm,
3178
3298
  ...number(length, Valtype.i32),
@@ -3180,14 +3300,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3180
3300
  );
3181
3301
 
3182
3302
  const storeOp = StoreOps[itemType];
3183
-
3303
+ const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
3184
3304
  if (!initEmpty) for (let i = 0; i < length; i++) {
3185
3305
  if (elements[i] == null) continue;
3186
3306
 
3307
+ const offset = ValtypeSize.i32 + i * sizePerEl;
3187
3308
  out.push(
3188
3309
  ...pointerWasm,
3189
3310
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
3190
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3311
+ [ storeOp, 0, ...unsignedLEB128(offset) ],
3312
+ ...(!typed ? [] : [ // typed presumes !useRawElements
3313
+ ...pointerWasm,
3314
+ ...getNodeType(scope, elements[i]),
3315
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(offset + ValtypeSize[itemType]) ]
3316
+ ])
3191
3317
  );
3192
3318
  }
3193
3319
 
@@ -3197,6 +3323,65 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3197
3323
  return [ out, pointer ];
3198
3324
  };
3199
3325
 
3326
+ const storeArray = (scope, array, index, element, aotPointer = null) => {
3327
+ if (!Array.isArray(element)) element = generate(scope, element);
3328
+ if (typeof index === 'number') index = number(index);
3329
+
3330
+ const offset = localTmp(scope, '#storeArray_offset', Valtype.i32);
3331
+
3332
+ return [
3333
+ // calculate offset
3334
+ ...index,
3335
+ Opcodes.i32_to_u,
3336
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3337
+ [ Opcodes.i32_mul ],
3338
+ ...(aotPointer ? [] : [
3339
+ ...array,
3340
+ Opcodes.i32_to_u,
3341
+ [ Opcodes.i32_add ],
3342
+ ]),
3343
+ [ Opcodes.local_set, offset ],
3344
+
3345
+ // store value
3346
+ [ Opcodes.local_get, offset ],
3347
+ ...generate(scope, element),
3348
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3349
+
3350
+ // store type
3351
+ [ Opcodes.local_get, offset ],
3352
+ ...getNodeType(scope, element),
3353
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3354
+ ];
3355
+ };
3356
+
3357
+ const loadArray = (scope, array, index, aotPointer = null) => {
3358
+ if (typeof index === 'number') index = number(index);
3359
+
3360
+ const offset = localTmp(scope, '#loadArray_offset', Valtype.i32);
3361
+
3362
+ return [
3363
+ // calculate offset
3364
+ ...index,
3365
+ Opcodes.i32_to_u,
3366
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3367
+ [ Opcodes.i32_mul ],
3368
+ ...(aotPointer ? [] : [
3369
+ ...array,
3370
+ Opcodes.i32_to_u,
3371
+ [ Opcodes.i32_add ],
3372
+ ]),
3373
+ [ Opcodes.local_set, offset ],
3374
+
3375
+ // load value
3376
+ [ Opcodes.local_get, offset ],
3377
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3378
+
3379
+ // load type
3380
+ [ Opcodes.local_get, offset ],
3381
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3382
+ ];
3383
+ };
3384
+
3200
3385
  const byteStringable = str => {
3201
3386
  if (!Prefs.bytestring) return false;
3202
3387
 
@@ -3225,7 +3410,7 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
3225
3410
  };
3226
3411
 
3227
3412
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
3228
- return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
3413
+ return makeArray(scope, decl, global, name, initEmpty, valtype, true)[0];
3229
3414
  };
3230
3415
 
3231
3416
  const generateObject = (scope, decl, global = false, name = '$undeclared') => {
@@ -3233,17 +3418,20 @@ const generateObject = (scope, decl, global = false, name = '$undeclared') => {
3233
3418
 
3234
3419
  return [
3235
3420
  ...number(1),
3236
-
3237
- ...number(TYPES.object, Valtype.i32),
3238
- ...setLastType(scope)
3421
+ ...setLastType(scope, TYPES.object)
3239
3422
  ];
3240
3423
  };
3241
3424
 
3425
+ const withType = (scope, wasm, type) => [
3426
+ ...wasm,
3427
+ ...setLastType(scope, type)
3428
+ ];
3429
+
3242
3430
  const generateMember = (scope, decl, _global, _name) => {
3243
3431
  const name = decl.object.name;
3244
3432
  const pointer = scope.arrays?.get(name);
3245
3433
 
3246
- const aotPointer = Prefs.aotPointerOpt && pointer != null;
3434
+ const aotPointer = Prefs.aotPointerOpt && pointer;
3247
3435
 
3248
3436
  // hack: .name
3249
3437
  if (decl.property.name === 'name') {
@@ -3253,9 +3441,9 @@ const generateMember = (scope, decl, _global, _name) => {
3253
3441
  // eg: __String_prototype_toLowerCase -> toLowerCase
3254
3442
  if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
3255
3443
 
3256
- return makeString(scope, nameProp, _global, _name, true);
3444
+ return withType(scope, makeString(scope, nameProp, _global, _name, true), TYPES.bytestring);
3257
3445
  } else {
3258
- return generate(scope, DEFAULT_VALUE);
3446
+ return withType(scope, number(0), TYPES.undefined);
3259
3447
  }
3260
3448
  }
3261
3449
 
@@ -3263,9 +3451,8 @@ const generateMember = (scope, decl, _global, _name) => {
3263
3451
  if (decl.property.name === 'length') {
3264
3452
  const func = funcs.find(x => x.name === name);
3265
3453
  if (func) {
3266
- const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3267
- const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3268
- return number(typedParams ? func.params.length / 2 : func.params.length);
3454
+ const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
3455
+ return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3269
3456
  }
3270
3457
 
3271
3458
  if (builtinFuncs[name + '$constructor']) {
@@ -3275,24 +3462,88 @@ const generateMember = (scope, decl, _global, _name) => {
3275
3462
  const constructorFunc = builtinFuncs[name + '$constructor'];
3276
3463
  const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3277
3464
 
3278
- return number(Math.max(regularParams, constructorParams));
3465
+ return withType(scope, number(Math.max(regularParams, constructorParams)), TYPES.number);
3279
3466
  }
3280
3467
 
3281
- if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3282
- if (importedFuncs[name]) return number(importedFuncs[name].params);
3283
- if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3468
+ if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
3469
+ if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params), TYPES.number);
3470
+ if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
3471
+
3472
+ if (Prefs.fastLength) {
3473
+ // presume valid length object
3474
+ return [
3475
+ ...(aotPointer ? number(0, Valtype.i32) : [
3476
+ ...generate(scope, decl.object),
3477
+ Opcodes.i32_to_u
3478
+ ]),
3479
+
3480
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3481
+ Opcodes.i32_from_u
3482
+ ];
3483
+ }
3484
+
3485
+ const type = getNodeType(scope, decl.object);
3486
+ const known = knownType(scope, type);
3487
+ if (known != null) {
3488
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
3489
+ ...(aotPointer ? number(0, Valtype.i32) : [
3490
+ ...generate(scope, decl.object),
3491
+ Opcodes.i32_to_u
3492
+ ]),
3493
+
3494
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3495
+ Opcodes.i32_from_u
3496
+ ];
3497
+
3498
+ return number(0);
3499
+ }
3284
3500
 
3285
3501
  return [
3286
- ...(aotPointer ? number(0, Valtype.i32) : [
3287
- ...generate(scope, decl.object),
3288
- Opcodes.i32_to_u
3289
- ]),
3502
+ ...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
3503
+ [ Opcodes.if, valtypeBinary ],
3504
+ ...(aotPointer ? number(0, Valtype.i32) : [
3505
+ ...generate(scope, decl.object),
3506
+ Opcodes.i32_to_u
3507
+ ]),
3508
+
3509
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3510
+ Opcodes.i32_from_u,
3290
3511
 
3291
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128((aotPointer ? pointer : 0)) ],
3292
- Opcodes.i32_from_u
3512
+ ...setLastType(scope, TYPES.number),
3513
+ [ Opcodes.else ],
3514
+ ...number(0),
3515
+ ...setLastType(scope, TYPES.undefined),
3516
+ [ Opcodes.end ]
3293
3517
  ];
3294
3518
  }
3295
3519
 
3520
+ // todo: generate this array procedurally during builtinFuncs creation
3521
+ if (['size', 'description'].includes(decl.property.name)) {
3522
+ const bc = {};
3523
+ const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3524
+
3525
+ if (cands.length > 0) {
3526
+ for (const x of cands) {
3527
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
3528
+ if (type == null) continue;
3529
+
3530
+ bc[type] = generateCall(scope, {
3531
+ callee: {
3532
+ type: 'Identifier',
3533
+ name: x
3534
+ },
3535
+ arguments: [ decl.object ],
3536
+ _protoInternalCall: true
3537
+ });
3538
+ }
3539
+ }
3540
+
3541
+ return typeSwitch(scope, getNodeType(scope, decl.object), {
3542
+ ...bc,
3543
+ default: withType(scope, number(0), TYPES.undefined)
3544
+ }, valtypeBinary);
3545
+ }
3546
+
3296
3547
  const object = generate(scope, decl.object);
3297
3548
  const property = generate(scope, decl.property);
3298
3549
 
@@ -3307,24 +3558,7 @@ const generateMember = (scope, decl, _global, _name) => {
3307
3558
 
3308
3559
  return typeSwitch(scope, getNodeType(scope, decl.object), {
3309
3560
  [TYPES.array]: [
3310
- // get index as valtype
3311
- ...property,
3312
-
3313
- // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
3314
- Opcodes.i32_to_u,
3315
- ...number(ValtypeSize[valtype], Valtype.i32),
3316
- [ Opcodes.i32_mul ],
3317
-
3318
- ...(aotPointer ? [] : [
3319
- ...object,
3320
- Opcodes.i32_to_u,
3321
- [ Opcodes.i32_add ]
3322
- ]),
3323
-
3324
- // read from memory
3325
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3326
-
3327
- ...number(TYPES.number, Valtype.i32),
3561
+ ...loadArray(scope, object, property, aotPointer),
3328
3562
  ...setLastType(scope)
3329
3563
  ],
3330
3564
 
@@ -3355,9 +3589,7 @@ const generateMember = (scope, decl, _global, _name) => {
3355
3589
 
3356
3590
  // return new string (page)
3357
3591
  ...number(newPointer),
3358
-
3359
- ...number(TYPES.string, Valtype.i32),
3360
- ...setLastType(scope)
3592
+ ...setLastType(scope, TYPES.string)
3361
3593
  ],
3362
3594
  [TYPES.bytestring]: [
3363
3595
  // setup new/out array
@@ -3383,9 +3615,7 @@ const generateMember = (scope, decl, _global, _name) => {
3383
3615
 
3384
3616
  // return new string (page)
3385
3617
  ...number(newPointer),
3386
-
3387
- ...number(TYPES.bytestring, Valtype.i32),
3388
- ...setLastType(scope)
3618
+ ...setLastType(scope, TYPES.bytestring)
3389
3619
  ],
3390
3620
 
3391
3621
  default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
@@ -3410,7 +3640,7 @@ const objectHack = node => {
3410
3640
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3411
3641
 
3412
3642
  // if .name or .length, give up (hack within a hack!)
3413
- if (['name', 'length'].includes(node.property.name)) {
3643
+ if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
3414
3644
  node.object = objectHack(node.object);
3415
3645
  return;
3416
3646
  }
@@ -3447,33 +3677,39 @@ const generateFunc = (scope, decl) => {
3447
3677
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3448
3678
  const params = decl.params ?? [];
3449
3679
 
3450
- // const innerScope = { ...scope };
3451
3680
  // TODO: share scope/locals between !!!
3452
- const innerScope = {
3681
+ const func = {
3453
3682
  locals: {},
3454
3683
  localInd: 0,
3455
3684
  // value, type
3456
3685
  returns: [ valtypeBinary, Valtype.i32 ],
3457
3686
  throws: false,
3458
- name
3687
+ name,
3688
+ index: currentFuncIndex++
3459
3689
  };
3460
3690
 
3461
3691
  if (typedInput && decl.returnType) {
3462
3692
  const { type } = extractTypeAnnotation(decl.returnType);
3463
- if (type != null && !Prefs.indirectCalls) {
3464
- innerScope.returnType = type;
3465
- innerScope.returns = [ valtypeBinary ];
3693
+ // if (type != null && !Prefs.indirectCalls) {
3694
+ if (type != null) {
3695
+ func.returnType = type;
3696
+ func.returns = [ valtypeBinary ];
3466
3697
  }
3467
3698
  }
3468
3699
 
3469
3700
  for (let i = 0; i < params.length; i++) {
3470
- allocVar(innerScope, params[i].name, false);
3701
+ const name = params[i].name;
3702
+ // if (name == null) return todo('non-identifier args are not supported');
3703
+
3704
+ allocVar(func, name, false);
3471
3705
 
3472
3706
  if (typedInput && params[i].typeAnnotation) {
3473
- addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
3707
+ addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
3474
3708
  }
3475
3709
  }
3476
3710
 
3711
+ func.params = Object.values(func.locals).map(x => x.type);
3712
+
3477
3713
  let body = objectHack(decl.body);
3478
3714
  if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
3479
3715
  // hack: () => 0 -> () => return 0
@@ -3483,37 +3719,23 @@ const generateFunc = (scope, decl) => {
3483
3719
  };
3484
3720
  }
3485
3721
 
3486
- const wasm = generate(innerScope, body);
3487
- const func = {
3488
- name,
3489
- params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
3490
- index: currentFuncIndex++,
3491
- ...innerScope
3492
- };
3493
3722
  funcIndex[name] = func.index;
3723
+ funcs.push(func);
3494
3724
 
3495
- if (name === 'main') func.gotLastType = true;
3725
+ const wasm = generate(func, body);
3726
+ func.wasm = wasm;
3496
3727
 
3497
- // quick hack fixes
3498
- for (const inst of wasm) {
3499
- if (inst[0] === Opcodes.call && inst[1] === -1) {
3500
- inst[1] = func.index;
3501
- }
3502
- }
3728
+ if (name === 'main') func.gotLastType = true;
3503
3729
 
3504
3730
  // add end return if not found
3505
3731
  if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
3506
3732
  wasm.push(
3507
3733
  ...number(0),
3508
- ...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3734
+ ...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3509
3735
  [ Opcodes.return ]
3510
3736
  );
3511
3737
  }
3512
3738
 
3513
- func.wasm = wasm;
3514
-
3515
- funcs.push(func);
3516
-
3517
3739
  return func;
3518
3740
  };
3519
3741
 
@@ -3617,7 +3839,7 @@ const internalConstrs = {
3617
3839
  generate: (scope, decl) => {
3618
3840
  // todo: boolean object when used as constructor
3619
3841
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3620
- return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3842
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg), false, false, 'full');
3621
3843
  },
3622
3844
  type: TYPES.boolean,
3623
3845
  length: 1
@@ -3762,9 +3984,8 @@ export default program => {
3762
3984
 
3763
3985
  if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3764
3986
 
3765
- generateFunc(scope, program);
3987
+ const main = generateFunc(scope, program);
3766
3988
 
3767
- const main = funcs[funcs.length - 1];
3768
3989
  main.export = true;
3769
3990
  main.returns = [ valtypeBinary, Valtype.i32 ];
3770
3991
 
@@ -3791,7 +4012,7 @@ export default program => {
3791
4012
  }
3792
4013
 
3793
4014
  // if blank main func and other exports, remove it
3794
- if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
4015
+ if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(main.index - importedFuncs.length, 1);
3795
4016
 
3796
4017
  return { funcs, globals, tags, exceptions, pages, data };
3797
4018
  };