porffor 0.14.0-33cb5e44b → 0.14.0-3905158d3

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
 
@@ -366,7 +363,7 @@ const localTmp = (scope, name, type = valtypeBinary) => {
366
363
  };
367
364
 
368
365
  const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
369
- const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
366
+ const isIntToFloatOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
370
367
 
371
368
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
372
369
  const checks = {
@@ -383,10 +380,10 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
383
380
 
384
381
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
385
382
  // (like if we are in an if condition - very common)
386
- const leftIsInt = isFloatToIntOp(left[left.length - 1]);
387
- const rightIsInt = isFloatToIntOp(right[right.length - 1]);
383
+ const leftWasInt = isIntToFloatOp(left[left.length - 1]);
384
+ const rightWasInt = isIntToFloatOp(right[right.length - 1]);
388
385
 
389
- const canInt = leftIsInt && rightIsInt;
386
+ const canInt = leftWasInt && rightWasInt;
390
387
 
391
388
  if (canInt) {
392
389
  // remove int -> float conversions from left and right
@@ -400,13 +397,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
400
397
  [ Opcodes.if, Valtype.i32 ],
401
398
  ...right,
402
399
  // note type
403
- ...rightType,
404
- ...setLastType(scope),
400
+ ...setLastType(scope, rightType),
405
401
  [ Opcodes.else ],
406
402
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
407
403
  // note type
408
- ...leftType,
409
- ...setLastType(scope),
404
+ ...setLastType(scope, leftType),
410
405
  [ Opcodes.end ],
411
406
  Opcodes.i32_from
412
407
  ];
@@ -419,13 +414,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
419
414
  [ Opcodes.if, valtypeBinary ],
420
415
  ...right,
421
416
  // note type
422
- ...rightType,
423
- ...setLastType(scope),
417
+ ...setLastType(scope, rightType),
424
418
  [ Opcodes.else ],
425
419
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
426
420
  // note type
427
- ...leftType,
428
- ...setLastType(scope),
421
+ ...setLastType(scope, leftType),
429
422
  [ Opcodes.end ]
430
423
  ];
431
424
  };
@@ -453,11 +446,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
453
446
  ...number(0, Valtype.i32), // base 0 for store later
454
447
 
455
448
  ...number(pointer, Valtype.i32),
456
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
449
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
457
450
  [ Opcodes.local_tee, leftLength ],
458
451
 
459
452
  [ Opcodes.local_get, rightPointer ],
460
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
453
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
461
454
  [ Opcodes.local_tee, rightLength ],
462
455
 
463
456
  [ Opcodes.i32_add ],
@@ -513,11 +506,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
513
506
  ...number(0, Valtype.i32), // base 0 for store later
514
507
 
515
508
  [ Opcodes.local_get, leftPointer ],
516
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
509
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
517
510
  [ Opcodes.local_tee, leftLength ],
518
511
 
519
512
  [ Opcodes.local_get, rightPointer ],
520
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
513
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
521
514
  [ Opcodes.local_tee, rightLength ],
522
515
 
523
516
  [ Opcodes.i32_add ],
@@ -595,11 +588,11 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
595
588
 
596
589
  // get lengths
597
590
  [ Opcodes.local_get, leftPointer ],
598
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
591
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
599
592
  [ Opcodes.local_tee, leftLength ],
600
593
 
601
594
  [ Opcodes.local_get, rightPointer ],
602
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
595
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
603
596
 
604
597
  // fast path: check leftLength != rightLength
605
598
  [ Opcodes.i32_ne ],
@@ -655,9 +648,9 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
655
648
  [ Opcodes.i32_add ],
656
649
  [ Opcodes.local_tee, index ],
657
650
 
658
- // if index != index end (length * sizeof valtype), loop
651
+ // if index < index end (length * sizeof valtype), loop
659
652
  [ Opcodes.local_get, indexEnd ],
660
- [ Opcodes.i32_ne ],
653
+ [ Opcodes.i32_lt_s ],
661
654
  [ Opcodes.br_if, 0 ],
662
655
  [ Opcodes.end ],
663
656
 
@@ -676,7 +669,7 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
676
669
  };
677
670
 
678
671
  const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
679
- if (isFloatToIntOp(wasm[wasm.length - 1])) return [
672
+ if (isIntToFloatOp(wasm[wasm.length - 1])) return [
680
673
  ...wasm,
681
674
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
682
675
  ];
@@ -685,17 +678,32 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
685
678
  const useTmp = knownType(scope, type) == null;
686
679
  const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
687
680
 
688
- const def = [
689
- // if value != 0
690
- ...(!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 ]),
691
686
 
692
- // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
693
- ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
687
+ [ Opcodes.i32_eqz ],
688
+ [ Opcodes.i32_eqz ],
694
689
 
695
- /* Opcodes.eqz,
696
- [ Opcodes.i32_eqz ],
697
- Opcodes.i32_from */
698
- ];
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
+ })(Prefs.truthy ?? 'full');
699
707
 
700
708
  return [
701
709
  ...wasm,
@@ -703,10 +711,10 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
703
711
 
704
712
  ...typeSwitch(scope, type, {
705
713
  // [TYPES.number]: def,
706
- [TYPES.array]: [
707
- // arrays are always truthy
708
- ...number(1, intOut ? Valtype.i32 : valtypeBinary)
709
- ],
714
+ // [TYPES.array]: [
715
+ // // arrays are always truthy
716
+ // ...number(1, intOut ? Valtype.i32 : valtypeBinary)
717
+ // ],
710
718
  [TYPES.string]: [
711
719
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
712
720
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -742,10 +750,10 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
742
750
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
743
751
 
744
752
  ...typeSwitch(scope, type, {
745
- [TYPES.array]: [
746
- // arrays are always truthy
747
- ...number(0, intOut ? Valtype.i32 : valtypeBinary)
748
- ],
753
+ // [TYPES.array]: [
754
+ // // arrays are always truthy
755
+ // ...number(0, intOut ? Valtype.i32 : valtypeBinary)
756
+ // ],
749
757
  [TYPES.string]: [
750
758
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
751
759
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -989,7 +997,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
989
997
  // if both are true
990
998
  [ Opcodes.i32_and ],
991
999
  [ Opcodes.if, Blocktype.void ],
992
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1000
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], false),
993
1001
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
994
1002
  [ Opcodes.br, 1 ],
995
1003
  [ Opcodes.end ],
@@ -1038,14 +1046,14 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
1038
1046
  return out;
1039
1047
  };
1040
1048
 
1041
- const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1042
- return func({ name, params, locals, returns, localInd }, {
1049
+ const asmFuncToAsm = (func, scope) => {
1050
+ return func(scope, {
1043
1051
  TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
1044
- builtin: name => {
1045
- let idx = funcIndex[name] ?? importedFuncs[name];
1046
- if (idx === undefined && builtinFuncs[name]) {
1047
- includeBuiltin(null, name);
1048
- idx = funcIndex[name];
1052
+ builtin: n => {
1053
+ let idx = funcIndex[n] ?? importedFuncs[n];
1054
+ if (idx == null && builtinFuncs[n]) {
1055
+ includeBuiltin(null, n);
1056
+ idx = funcIndex[n];
1049
1057
  }
1050
1058
 
1051
1059
  return idx;
@@ -1053,7 +1061,7 @@ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals =
1053
1061
  });
1054
1062
  };
1055
1063
 
1056
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
1064
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false }) => {
1057
1065
  const existing = funcs.find(x => x.name === name);
1058
1066
  if (existing) return existing;
1059
1067
 
@@ -1071,7 +1079,22 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1071
1079
  data.push(copy);
1072
1080
  }
1073
1081
 
1074
- if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1082
+ const func = {
1083
+ name,
1084
+ params,
1085
+ locals,
1086
+ localInd: allLocals.length,
1087
+ returns,
1088
+ returnType: returnType ?? TYPES.number,
1089
+ internal: true,
1090
+ index: currentFuncIndex++,
1091
+ table
1092
+ };
1093
+
1094
+ funcs.push(func);
1095
+ funcIndex[name] = func.index;
1096
+
1097
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, func);
1075
1098
 
1076
1099
  let baseGlobalIdx, i = 0;
1077
1100
  for (const type of globalTypes) {
@@ -1090,19 +1113,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1090
1113
  }
1091
1114
  }
1092
1115
 
1093
- const func = {
1094
- name,
1095
- params,
1096
- locals,
1097
- returns,
1098
- returnType: returnType ?? TYPES.number,
1099
- wasm,
1100
- internal: true,
1101
- index: currentFuncIndex++
1102
- };
1116
+ if (table) for (const inst of wasm) {
1117
+ if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
1118
+ inst.splice(2, 99);
1119
+ inst.push(...unsignedLEB128(allocPage({}, 'func argc lut') * pageSize));
1120
+ }
1121
+ }
1103
1122
 
1104
- funcs.push(func);
1105
- funcIndex[name] = func.index;
1123
+ func.wasm = wasm;
1106
1124
 
1107
1125
  return func;
1108
1126
  };
@@ -1194,9 +1212,10 @@ const getLastType = scope => {
1194
1212
  return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1195
1213
  };
1196
1214
 
1197
- const setLastType = scope => {
1198
- return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1199
- };
1215
+ const setLastType = (scope, type = []) => [
1216
+ ...(typeof type === 'number' ? number(type, Valtype.i32) : type),
1217
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1218
+ ];
1200
1219
 
1201
1220
  const getNodeType = (scope, node) => {
1202
1221
  const ret = (() => {
@@ -1257,7 +1276,17 @@ const getNodeType = (scope, node) => {
1257
1276
 
1258
1277
  const func = spl[spl.length - 1];
1259
1278
  const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
1260
- if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1279
+ if (protoFuncs.length === 1) {
1280
+ if (protoFuncs[0].returnType) return protoFuncs[0].returnType;
1281
+ }
1282
+
1283
+ if (protoFuncs.length > 0) {
1284
+ if (scope.locals['#last_type']) return getLastType(scope);
1285
+
1286
+ // presume
1287
+ // todo: warn here?
1288
+ return TYPES.number;
1289
+ }
1261
1290
  }
1262
1291
 
1263
1292
  if (name.startsWith('__Porffor_wasm_')) {
@@ -1352,22 +1381,27 @@ const getNodeType = (scope, node) => {
1352
1381
  }
1353
1382
 
1354
1383
  if (node.type === 'MemberExpression') {
1355
- // hack: if something.name, string type
1356
- if (node.property.name === 'name') {
1357
- if (hasFuncWithName(node.object.name)) {
1358
- return TYPES.bytestring;
1359
- } else {
1360
- return TYPES.undefined;
1361
- }
1384
+ const name = node.property.name;
1385
+
1386
+ if (name === 'length') {
1387
+ if (hasFuncWithName(node.object.name)) return TYPES.number;
1388
+ if (Prefs.fastLength) return TYPES.number;
1362
1389
  }
1363
1390
 
1364
- // hack: if something.length, number type
1365
- if (node.property.name === 'length') return TYPES.number;
1366
1391
 
1367
- // ts hack
1368
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1369
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.bytestring) return TYPES.bytestring;
1370
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.array) return TYPES.number;
1392
+ const objectKnownType = knownType(scope, getNodeType(scope, node.object));
1393
+ if (objectKnownType != null) {
1394
+ if (name === 'length') {
1395
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(objectKnownType)) return TYPES.number;
1396
+ else return TYPES.undefined;
1397
+ }
1398
+
1399
+ if (node.computed) {
1400
+ if (objectKnownType === TYPES.string) return TYPES.string;
1401
+ if (objectKnownType === TYPES.bytestring) return TYPES.bytestring;
1402
+ if (objectKnownType === TYPES.array) return TYPES.number;
1403
+ }
1404
+ }
1371
1405
 
1372
1406
  if (scope.locals['#last_type']) return getLastType(scope);
1373
1407
 
@@ -1438,16 +1472,11 @@ const countLeftover = wasm => {
1438
1472
  else if (inst[0] === Opcodes.return) count = 0;
1439
1473
  else if (inst[0] === Opcodes.call) {
1440
1474
  let func = funcs.find(x => x.index === inst[1]);
1441
- if (inst[1] === -1) {
1442
- // todo: count for calling self
1443
- } else if (!func && inst[1] < importedFuncs.length) {
1444
- count -= importedFuncs[inst[1]].params;
1445
- count += importedFuncs[inst[1]].returns;
1475
+ if (inst[1] < importedFuncs.length) {
1476
+ func = importedFuncs[inst[1]];
1477
+ count = count - func.params + func.returns;
1446
1478
  } else {
1447
- if (func) {
1448
- count -= func.params.length;
1449
- } else count--;
1450
- if (func) count += func.returns.length;
1479
+ count = count - func.params.length + func.returns.length;
1451
1480
  }
1452
1481
  } else if (inst[0] === Opcodes.call_indirect) {
1453
1482
  count--; // funcidx
@@ -1470,7 +1499,7 @@ const disposeLeftover = wasm => {
1470
1499
  const generateExp = (scope, decl) => {
1471
1500
  const expression = decl.expression;
1472
1501
 
1473
- const out = generate(scope, expression, undefined, undefined, true);
1502
+ const out = generate(scope, expression, undefined, undefined, Prefs.optUnused);
1474
1503
  disposeLeftover(out);
1475
1504
 
1476
1505
  return out;
@@ -1561,16 +1590,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1561
1590
  out.splice(out.length - 1, 1);
1562
1591
 
1563
1592
  const finalStatement = parsed.body[parsed.body.length - 1];
1564
- out.push(
1565
- ...getNodeType(scope, finalStatement),
1566
- ...setLastType(scope)
1567
- );
1593
+ out.push(...setLastType(scope, getNodeType(scope, finalStatement)));
1568
1594
  } else if (countLeftover(out) === 0) {
1569
1595
  out.push(...number(UNDEFINED));
1570
- out.push(
1571
- ...number(TYPES.undefined, Valtype.i32),
1572
- ...setLastType(scope)
1573
- );
1596
+ out.push(...setLastType(scope, TYPES.undefined));
1574
1597
  }
1575
1598
 
1576
1599
  // if (lastInst && lastInst[0] === Opcodes.drop) {
@@ -1606,6 +1629,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1606
1629
 
1607
1630
  if (!funcIndex[rhemynName]) {
1608
1631
  const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1632
+ func.internal = true;
1609
1633
 
1610
1634
  funcIndex[func.name] = func.index;
1611
1635
  funcs.push(func);
@@ -1622,8 +1646,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1622
1646
  [ Opcodes.call, idx ],
1623
1647
  Opcodes.i32_from_u,
1624
1648
 
1625
- ...number(TYPES.boolean, Valtype.i32),
1626
- ...setLastType(scope)
1649
+ ...setLastType(scope, TYPES.boolean)
1627
1650
  ];
1628
1651
  }
1629
1652
 
@@ -1695,9 +1718,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1695
1718
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
1696
1719
  protoBC[x] = [
1697
1720
  ...RTArrayUtil.getLength(getPointer),
1698
-
1699
- ...number(TYPES.number, Valtype.i32),
1700
- ...setLastType(scope)
1721
+ ...setLastType(scope, TYPES.number)
1701
1722
  ];
1702
1723
  continue;
1703
1724
  }
@@ -1716,7 +1737,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1716
1737
  getI32: () => RTArrayUtil.getLengthI32(getPointer),
1717
1738
  set: value => RTArrayUtil.setLength(getPointer, value),
1718
1739
  setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
1719
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1740
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), getNodeType(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1720
1741
  return makeArray(scope, {
1721
1742
  rawElements: new Array(length)
1722
1743
  }, _global, _name, true, itemType);
@@ -1730,9 +1751,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1730
1751
  protoBC[x] = [
1731
1752
  [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1732
1753
  ...protoOut,
1733
-
1734
- ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1735
- ...setLastType(scope),
1754
+ ...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
1736
1755
  [ Opcodes.end ]
1737
1756
  ];
1738
1757
  }
@@ -1782,11 +1801,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1782
1801
 
1783
1802
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1784
1803
 
1785
- if (idx === undefined && name === scope.name) {
1786
- // hack: calling self, func generator will fix later
1787
- idx = -1;
1788
- }
1789
-
1790
1804
  if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1791
1805
  const wasmOps = {
1792
1806
  // pointer, align, offset
@@ -1838,36 +1852,128 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1838
1852
  const [ local, global ] = lookupName(scope, name);
1839
1853
  if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1840
1854
 
1841
- // todo: only works when:
1842
- // 1. arg count matches arg count of function
1843
- // 2. function uses typedParams and typedReturns
1855
+ // todo: only works when function uses typedParams and typedReturns
1856
+
1857
+ const indirectMode = Prefs.indirectCallMode ?? 'vararg';
1858
+ // options: vararg, strict
1859
+ // - strict: simpler, smaller size usage, no func argc lut needed.
1860
+ // ONLY works when arg count of call == arg count of function being called
1861
+ // - vararg: large size usage, cursed.
1862
+ // works when arg count of call != arg count of function being called*
1863
+ // * most of the time, some edgecases
1844
1864
 
1845
1865
  funcs.table = true;
1866
+ scope.table = true;
1846
1867
 
1847
1868
  let args = decl.arguments;
1848
- let argWasm = [];
1869
+ let out = [];
1870
+
1871
+ let locals = [];
1872
+
1873
+ if (indirectMode === 'vararg') {
1874
+ const minArgc = Prefs.indirectCallMinArgc ?? 3;
1875
+
1876
+ if (args.length < minArgc) {
1877
+ args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
1878
+ }
1879
+ }
1849
1880
 
1850
1881
  for (let i = 0; i < args.length; i++) {
1851
1882
  const arg = args[i];
1852
- argWasm = argWasm.concat(generate(scope, arg));
1883
+ out = out.concat(generate(scope, arg));
1853
1884
 
1854
1885
  if (valtypeBinary !== Valtype.i32 && (
1855
1886
  (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1856
1887
  (importedFuncs[name] && name.startsWith('profile'))
1857
1888
  )) {
1858
- argWasm.push(Opcodes.i32_to);
1889
+ out.push(Opcodes.i32_to);
1859
1890
  }
1860
1891
 
1861
- argWasm = argWasm.concat(getNodeType(scope, arg));
1892
+ out = out.concat(getNodeType(scope, arg));
1893
+
1894
+ if (indirectMode === 'vararg') {
1895
+ const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
1896
+ const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
1897
+
1898
+ locals.push([valLocal, typeLocal]);
1899
+
1900
+ out.push(
1901
+ [ Opcodes.local_set, typeLocal ],
1902
+ [ Opcodes.local_set, valLocal ]
1903
+ );
1904
+ }
1862
1905
  }
1863
1906
 
1907
+ if (indirectMode === 'strict') {
1908
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1909
+ [TYPES.function]: [
1910
+ ...argWasm,
1911
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1912
+ Opcodes.i32_to_u,
1913
+ [ Opcodes.call_indirect, args.length, 0 ],
1914
+ ...setLastType(scope)
1915
+ ],
1916
+ default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1917
+ });
1918
+ }
1919
+
1920
+ // hi, I will now explain how vararg mode works:
1921
+ // wasm's indirect_call instruction requires you know the func type at compile-time
1922
+ // since we have varargs (variable argument count), we do not know it.
1923
+ // we could just store args in memory and not use wasm func args,
1924
+ // but that is slow (probably) and breaks js exports.
1925
+ // instead, we generate every* possibility of argc and use different indirect_call
1926
+ // ops for each one, with type depending on argc for that branch.
1927
+ // then we load the argc for the wanted function from a memory lut,
1928
+ // and call the branch with the matching argc we require.
1929
+ // sorry, yes it is very cursed (and size inefficient), but indirect calls
1930
+ // are kind of rare anyway (mostly callbacks) so I am not concerned atm.
1931
+ // *for argc 0-3, in future (todo:) the max number should be
1932
+ // dynamically changed to the max argc of any func in the js file.
1933
+
1934
+ const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
1935
+
1936
+ const gen = argc => {
1937
+ const out = [];
1938
+ for (let i = 0; i < argc; i++) {
1939
+ out.push(
1940
+ [ Opcodes.local_get, locals[i][0] ],
1941
+ [ Opcodes.local_get, locals[i][1] ]
1942
+ );
1943
+ }
1944
+
1945
+ out.push(
1946
+ [ Opcodes.local_get, funcLocal ],
1947
+ [ Opcodes.call_indirect, argc, 0 ],
1948
+ ...setLastType(scope)
1949
+ )
1950
+
1951
+ return out;
1952
+ };
1953
+
1954
+ const tableBc = {};
1955
+ for (let i = 0; i <= args.length; i++) {
1956
+ tableBc[i] = gen(i);
1957
+ }
1958
+
1959
+ // todo/perf: check if we should use br_table here or just generate our own big if..elses
1960
+
1864
1961
  return typeSwitch(scope, getNodeType(scope, decl.callee), {
1865
1962
  [TYPES.function]: [
1866
- ...argWasm,
1963
+ ...out,
1964
+
1867
1965
  [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1868
1966
  Opcodes.i32_to_u,
1869
- [ Opcodes.call_indirect, args.length, 0 ],
1870
- ...setLastType(scope)
1967
+ [ Opcodes.local_set, funcLocal ],
1968
+
1969
+ ...brTable([
1970
+ // get argc of func we are calling
1971
+ [ Opcodes.local_get, funcLocal ],
1972
+ ...number(ValtypeSize.i16, Valtype.i32),
1973
+ [ Opcodes.i32_mul ],
1974
+
1975
+ [ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
1976
+ ], tableBc, valtypeBinary)
1871
1977
  ],
1872
1978
  default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1873
1979
  });
@@ -1876,11 +1982,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1876
1982
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1877
1983
  }
1878
1984
 
1879
- const func = funcs.find(x => x.index === idx);
1880
-
1881
- const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1985
+ const func = funcs[idx - importedFuncs.length]; // idx === scope.index ? scope : funcs.find(x => x.index === idx);
1986
+ const userFunc = func && !func.internal;
1882
1987
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1883
- const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1988
+ const typedReturns = (func && func.returnType == null) || builtinFuncs[name]?.typedReturns;
1884
1989
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1885
1990
 
1886
1991
  let args = decl.arguments;
@@ -1901,6 +2006,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1901
2006
  const arg = args[i];
1902
2007
  out = out.concat(generate(scope, arg));
1903
2008
 
2009
+ // todo: this should be used instead of the too many args thing above (by removing that)
1904
2010
  if (i >= paramCount) {
1905
2011
  // over param count of func, drop arg
1906
2012
  out.push([ Opcodes.drop ]);
@@ -1985,8 +2091,11 @@ const knownType = (scope, type) => {
1985
2091
  const idx = type[0][1];
1986
2092
 
1987
2093
  // type idx = var idx + 1
1988
- const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
1989
- if (v.metadata?.type != null) return v.metadata.type;
2094
+ const name = Object.values(scope.locals).find(x => x.idx === idx)?.name;
2095
+ if (name) {
2096
+ const local = scope.locals[name];
2097
+ if (local.metadata?.type != null) return v.metadata.type;
2098
+ }
1990
2099
  }
1991
2100
 
1992
2101
  return null;
@@ -2021,16 +2130,17 @@ const brTable = (input, bc, returns) => {
2021
2130
  }
2022
2131
 
2023
2132
  for (let i = 0; i < count; i++) {
2024
- if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2133
+ // if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2134
+ if (i === 0) out.push([ Opcodes.block, returns ]);
2025
2135
  else out.push([ Opcodes.block, Blocktype.void ]);
2026
2136
  }
2027
2137
 
2028
- const nums = keys.filter(x => +x);
2138
+ const nums = keys.filter(x => +x >= 0);
2029
2139
  const offset = Math.min(...nums);
2030
2140
  const max = Math.max(...nums);
2031
2141
 
2032
2142
  const table = [];
2033
- let br = 1;
2143
+ let br = 0;
2034
2144
 
2035
2145
  for (let i = offset; i <= max; i++) {
2036
2146
  // if branch for this num, go to that block
@@ -2070,10 +2180,9 @@ const brTable = (input, bc, returns) => {
2070
2180
  br--;
2071
2181
  }
2072
2182
 
2073
- return [
2074
- ...out,
2075
- [ Opcodes.end, 'br table end' ]
2076
- ];
2183
+ out.push([ Opcodes.end ]);
2184
+
2185
+ return out;
2077
2186
  };
2078
2187
 
2079
2188
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
@@ -2117,6 +2226,17 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2117
2226
  return out;
2118
2227
  };
2119
2228
 
2229
+ const typeIsOneOf = (type, types, valtype = Valtype.i32) => {
2230
+ const out = [];
2231
+
2232
+ for (let i = 0; i < types.length; i++) {
2233
+ out.push(...type, ...number(types[i], valtype), valtype === Valtype.f64 ? [ Opcodes.f64_eq ] : [ Opcodes.i32_eq ]);
2234
+ if (i !== 0) out.push([ Opcodes.i32_or ]);
2235
+ }
2236
+
2237
+ return out;
2238
+ };
2239
+
2120
2240
  const allocVar = (scope, name, global = false, type = true) => {
2121
2241
  const target = global ? globals : scope.locals;
2122
2242
 
@@ -2133,7 +2253,7 @@ const allocVar = (scope, name, global = false, type = true) => {
2133
2253
 
2134
2254
  if (type) {
2135
2255
  let typeIdx = global ? globalInd++ : scope.localInd++;
2136
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2256
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32, name };
2137
2257
  }
2138
2258
 
2139
2259
  return idx;
@@ -2346,18 +2466,21 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2346
2466
  Opcodes.i32_to_u,
2347
2467
 
2348
2468
  // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2349
- ...number(ValtypeSize[valtype], Valtype.i32),
2469
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2350
2470
  [ Opcodes.i32_mul ],
2351
2471
  ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
2352
2472
  ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2353
2473
 
2354
2474
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2355
2475
  [ Opcodes.local_get, pointerTmp ],
2356
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2357
- ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2476
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2477
+ ], generate(scope, decl.right), [
2478
+ [ Opcodes.local_get, pointerTmp ],
2479
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
2480
+ ], getNodeType(scope, decl.right), false, name, true)),
2358
2481
  [ Opcodes.local_tee, newValueTmp ],
2359
2482
 
2360
- [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2483
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2361
2484
  ],
2362
2485
 
2363
2486
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
@@ -2611,21 +2734,16 @@ const generateConditional = (scope, decl) => {
2611
2734
  out.push([ Opcodes.if, valtypeBinary ]);
2612
2735
  depth.push('if');
2613
2736
 
2614
- out.push(...generate(scope, decl.consequent));
2615
-
2616
- // note type
2617
2737
  out.push(
2618
- ...getNodeType(scope, decl.consequent),
2619
- ...setLastType(scope)
2738
+ ...generate(scope, decl.consequent),
2739
+ ...setLastType(scope, getNodeType(scope, decl.consequent))
2620
2740
  );
2621
2741
 
2622
2742
  out.push([ Opcodes.else ]);
2623
- out.push(...generate(scope, decl.alternate));
2624
2743
 
2625
- // note type
2626
2744
  out.push(
2627
- ...getNodeType(scope, decl.alternate),
2628
- ...setLastType(scope)
2745
+ ...generate(scope, decl.alternate),
2746
+ ...setLastType(scope, getNodeType(scope, decl.alternate))
2629
2747
  );
2630
2748
 
2631
2749
  out.push([ Opcodes.end ]);
@@ -2773,12 +2891,15 @@ const generateForOf = (scope, decl) => {
2773
2891
  // todo: optimize away counter and use end pointer
2774
2892
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2775
2893
  [TYPES.array]: [
2776
- ...setType(scope, leftName, TYPES.number),
2777
-
2778
2894
  [ Opcodes.loop, Blocktype.void ],
2779
2895
 
2780
2896
  [ Opcodes.local_get, pointer ],
2781
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2897
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2898
+
2899
+ ...setType(scope, leftName, [
2900
+ [ Opcodes.local_get, pointer ],
2901
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
2902
+ ]),
2782
2903
 
2783
2904
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2784
2905
 
@@ -2787,9 +2908,9 @@ const generateForOf = (scope, decl) => {
2787
2908
  ...generate(scope, decl.body),
2788
2909
  [ Opcodes.end ],
2789
2910
 
2790
- // increment iter pointer by valtype size
2911
+ // increment iter pointer by valtype size + 1
2791
2912
  [ Opcodes.local_get, pointer ],
2792
- ...number(ValtypeSize[valtype], Valtype.i32),
2913
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2793
2914
  [ Opcodes.i32_add ],
2794
2915
  [ Opcodes.local_set, pointer ],
2795
2916
 
@@ -3006,14 +3127,18 @@ const generateThrow = (scope, decl) => {
3006
3127
  };
3007
3128
 
3008
3129
  const generateTry = (scope, decl) => {
3009
- if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
3130
+ // todo: handle control-flow pre-exit for finally
3131
+ // "Immediately before a control-flow statement (return, throw, break, continue) is executed in the try block or catch block."
3010
3132
 
3011
3133
  const out = [];
3012
3134
 
3135
+ const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
3136
+
3013
3137
  out.push([ Opcodes.try, Blocktype.void ]);
3014
3138
  depth.push('try');
3015
3139
 
3016
3140
  out.push(...generate(scope, decl.block));
3141
+ out.push(...finalizer);
3017
3142
 
3018
3143
  if (decl.handler) {
3019
3144
  depth.pop();
@@ -3021,6 +3146,7 @@ const generateTry = (scope, decl) => {
3021
3146
 
3022
3147
  out.push([ Opcodes.catch_all ]);
3023
3148
  out.push(...generate(scope, decl.handler.body));
3149
+ out.push(...finalizer);
3024
3150
  }
3025
3151
 
3026
3152
  out.push([ Opcodes.end ]);
@@ -3106,7 +3232,7 @@ const getAllocType = itemType => {
3106
3232
  }
3107
3233
  };
3108
3234
 
3109
- const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
3235
+ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype, typed = false) => {
3110
3236
  const out = [];
3111
3237
 
3112
3238
  scope.arrays ??= new Map();
@@ -3118,8 +3244,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3118
3244
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
3119
3245
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
3120
3246
 
3121
- if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
3122
- else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3247
+ let page;
3248
+ if (Prefs.scopedPageNames) page = allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType);
3249
+ else page = allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType);
3250
+
3251
+ // hack: use 1 for page 0 pointer for fast truthiness
3252
+ const ptr = page === 0 ? 1 : (page * pageSize);
3253
+ scope.arrays.set(name, ptr);
3123
3254
  }
3124
3255
 
3125
3256
  const pointer = scope.arrays.get(name);
@@ -3169,7 +3300,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3169
3300
 
3170
3301
  const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3171
3302
 
3172
- // store length as 0th array
3303
+ // store length
3173
3304
  out.push(
3174
3305
  ...pointerWasm,
3175
3306
  ...number(length, Valtype.i32),
@@ -3177,14 +3308,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3177
3308
  );
3178
3309
 
3179
3310
  const storeOp = StoreOps[itemType];
3180
-
3311
+ const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
3181
3312
  if (!initEmpty) for (let i = 0; i < length; i++) {
3182
3313
  if (elements[i] == null) continue;
3183
3314
 
3315
+ const offset = ValtypeSize.i32 + i * sizePerEl;
3184
3316
  out.push(
3185
3317
  ...pointerWasm,
3186
3318
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
3187
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3319
+ [ storeOp, 0, ...unsignedLEB128(offset) ],
3320
+ ...(!typed ? [] : [ // typed presumes !useRawElements
3321
+ ...pointerWasm,
3322
+ ...getNodeType(scope, elements[i]),
3323
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(offset + ValtypeSize[itemType]) ]
3324
+ ])
3188
3325
  );
3189
3326
  }
3190
3327
 
@@ -3194,6 +3331,65 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3194
3331
  return [ out, pointer ];
3195
3332
  };
3196
3333
 
3334
+ const storeArray = (scope, array, index, element, aotPointer = null) => {
3335
+ if (!Array.isArray(element)) element = generate(scope, element);
3336
+ if (typeof index === 'number') index = number(index);
3337
+
3338
+ const offset = localTmp(scope, '#storeArray_offset', Valtype.i32);
3339
+
3340
+ return [
3341
+ // calculate offset
3342
+ ...index,
3343
+ Opcodes.i32_to_u,
3344
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3345
+ [ Opcodes.i32_mul ],
3346
+ ...(aotPointer ? [] : [
3347
+ ...array,
3348
+ Opcodes.i32_to_u,
3349
+ [ Opcodes.i32_add ],
3350
+ ]),
3351
+ [ Opcodes.local_set, offset ],
3352
+
3353
+ // store value
3354
+ [ Opcodes.local_get, offset ],
3355
+ ...generate(scope, element),
3356
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3357
+
3358
+ // store type
3359
+ [ Opcodes.local_get, offset ],
3360
+ ...getNodeType(scope, element),
3361
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3362
+ ];
3363
+ };
3364
+
3365
+ const loadArray = (scope, array, index, aotPointer = null) => {
3366
+ if (typeof index === 'number') index = number(index);
3367
+
3368
+ const offset = localTmp(scope, '#loadArray_offset', Valtype.i32);
3369
+
3370
+ return [
3371
+ // calculate offset
3372
+ ...index,
3373
+ Opcodes.i32_to_u,
3374
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3375
+ [ Opcodes.i32_mul ],
3376
+ ...(aotPointer ? [] : [
3377
+ ...array,
3378
+ Opcodes.i32_to_u,
3379
+ [ Opcodes.i32_add ],
3380
+ ]),
3381
+ [ Opcodes.local_set, offset ],
3382
+
3383
+ // load value
3384
+ [ Opcodes.local_get, offset ],
3385
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3386
+
3387
+ // load type
3388
+ [ Opcodes.local_get, offset ],
3389
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3390
+ ];
3391
+ };
3392
+
3197
3393
  const byteStringable = str => {
3198
3394
  if (!Prefs.bytestring) return false;
3199
3395
 
@@ -3222,14 +3418,28 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
3222
3418
  };
3223
3419
 
3224
3420
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
3225
- return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
3421
+ return makeArray(scope, decl, global, name, initEmpty, valtype, true)[0];
3226
3422
  };
3227
3423
 
3228
- export const generateMember = (scope, decl, _global, _name) => {
3424
+ const generateObject = (scope, decl, global = false, name = '$undeclared') => {
3425
+ if (decl.properties.length > 0) return todo(scope, 'objects are not supported yet', true);
3426
+
3427
+ return [
3428
+ ...number(1),
3429
+ ...setLastType(scope, TYPES.object)
3430
+ ];
3431
+ };
3432
+
3433
+ const withType = (scope, wasm, type) => [
3434
+ ...wasm,
3435
+ ...setLastType(scope, type)
3436
+ ];
3437
+
3438
+ const generateMember = (scope, decl, _global, _name) => {
3229
3439
  const name = decl.object.name;
3230
3440
  const pointer = scope.arrays?.get(name);
3231
3441
 
3232
- const aotPointer = Prefs.aotPointerOpt && pointer != null;
3442
+ const aotPointer = Prefs.aotPointerOpt && pointer;
3233
3443
 
3234
3444
  // hack: .name
3235
3445
  if (decl.property.name === 'name') {
@@ -3239,9 +3449,9 @@ export const generateMember = (scope, decl, _global, _name) => {
3239
3449
  // eg: __String_prototype_toLowerCase -> toLowerCase
3240
3450
  if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
3241
3451
 
3242
- return makeString(scope, nameProp, _global, _name, true);
3452
+ return withType(scope, makeString(scope, nameProp, _global, _name, true), TYPES.bytestring);
3243
3453
  } else {
3244
- return generate(scope, DEFAULT_VALUE);
3454
+ return withType(scope, number(0), TYPES.undefined);
3245
3455
  }
3246
3456
  }
3247
3457
 
@@ -3249,9 +3459,8 @@ export const generateMember = (scope, decl, _global, _name) => {
3249
3459
  if (decl.property.name === 'length') {
3250
3460
  const func = funcs.find(x => x.name === name);
3251
3461
  if (func) {
3252
- const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3253
- const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3254
- return number(typedParams ? func.params.length / 2 : func.params.length);
3462
+ const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
3463
+ return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3255
3464
  }
3256
3465
 
3257
3466
  if (builtinFuncs[name + '$constructor']) {
@@ -3261,24 +3470,88 @@ export const generateMember = (scope, decl, _global, _name) => {
3261
3470
  const constructorFunc = builtinFuncs[name + '$constructor'];
3262
3471
  const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3263
3472
 
3264
- return number(Math.max(regularParams, constructorParams));
3473
+ return withType(scope, number(Math.max(regularParams, constructorParams)), TYPES.number);
3265
3474
  }
3266
3475
 
3267
- if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3268
- if (importedFuncs[name]) return number(importedFuncs[name].params);
3269
- if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3476
+ if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
3477
+ if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params), TYPES.number);
3478
+ if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
3479
+
3480
+ if (Prefs.fastLength) {
3481
+ // presume valid length object
3482
+ return [
3483
+ ...(aotPointer ? number(0, Valtype.i32) : [
3484
+ ...generate(scope, decl.object),
3485
+ Opcodes.i32_to_u
3486
+ ]),
3487
+
3488
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3489
+ Opcodes.i32_from_u
3490
+ ];
3491
+ }
3492
+
3493
+ const type = getNodeType(scope, decl.object);
3494
+ const known = knownType(scope, type);
3495
+ if (known != null) {
3496
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
3497
+ ...(aotPointer ? number(0, Valtype.i32) : [
3498
+ ...generate(scope, decl.object),
3499
+ Opcodes.i32_to_u
3500
+ ]),
3501
+
3502
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3503
+ Opcodes.i32_from_u
3504
+ ];
3505
+
3506
+ return number(0);
3507
+ }
3270
3508
 
3271
3509
  return [
3272
- ...(aotPointer ? number(0, Valtype.i32) : [
3273
- ...generate(scope, decl.object),
3274
- Opcodes.i32_to_u
3275
- ]),
3510
+ ...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
3511
+ [ Opcodes.if, valtypeBinary ],
3512
+ ...(aotPointer ? number(0, Valtype.i32) : [
3513
+ ...generate(scope, decl.object),
3514
+ Opcodes.i32_to_u
3515
+ ]),
3516
+
3517
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3518
+ Opcodes.i32_from_u,
3276
3519
 
3277
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128((aotPointer ? pointer : 0)) ],
3278
- Opcodes.i32_from_u
3520
+ ...setLastType(scope, TYPES.number),
3521
+ [ Opcodes.else ],
3522
+ ...number(0),
3523
+ ...setLastType(scope, TYPES.undefined),
3524
+ [ Opcodes.end ]
3279
3525
  ];
3280
3526
  }
3281
3527
 
3528
+ // todo: generate this array procedurally during builtinFuncs creation
3529
+ if (['size', 'description'].includes(decl.property.name)) {
3530
+ const bc = {};
3531
+ const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3532
+
3533
+ if (cands.length > 0) {
3534
+ for (const x of cands) {
3535
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
3536
+ if (type == null) continue;
3537
+
3538
+ bc[type] = generateCall(scope, {
3539
+ callee: {
3540
+ type: 'Identifier',
3541
+ name: x
3542
+ },
3543
+ arguments: [ decl.object ],
3544
+ _protoInternalCall: true
3545
+ });
3546
+ }
3547
+ }
3548
+
3549
+ return typeSwitch(scope, getNodeType(scope, decl.object), {
3550
+ ...bc,
3551
+ default: withType(scope, number(0), TYPES.undefined)
3552
+ }, valtypeBinary);
3553
+ }
3554
+
3282
3555
  const object = generate(scope, decl.object);
3283
3556
  const property = generate(scope, decl.property);
3284
3557
 
@@ -3293,24 +3566,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3293
3566
 
3294
3567
  return typeSwitch(scope, getNodeType(scope, decl.object), {
3295
3568
  [TYPES.array]: [
3296
- // get index as valtype
3297
- ...property,
3298
-
3299
- // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
3300
- Opcodes.i32_to_u,
3301
- ...number(ValtypeSize[valtype], Valtype.i32),
3302
- [ Opcodes.i32_mul ],
3303
-
3304
- ...(aotPointer ? [] : [
3305
- ...object,
3306
- Opcodes.i32_to_u,
3307
- [ Opcodes.i32_add ]
3308
- ]),
3309
-
3310
- // read from memory
3311
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3312
-
3313
- ...number(TYPES.number, Valtype.i32),
3569
+ ...loadArray(scope, object, property, aotPointer),
3314
3570
  ...setLastType(scope)
3315
3571
  ],
3316
3572
 
@@ -3341,9 +3597,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3341
3597
 
3342
3598
  // return new string (page)
3343
3599
  ...number(newPointer),
3344
-
3345
- ...number(TYPES.string, Valtype.i32),
3346
- ...setLastType(scope)
3600
+ ...setLastType(scope, TYPES.string)
3347
3601
  ],
3348
3602
  [TYPES.bytestring]: [
3349
3603
  // setup new/out array
@@ -3369,9 +3623,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3369
3623
 
3370
3624
  // return new string (page)
3371
3625
  ...number(newPointer),
3372
-
3373
- ...number(TYPES.bytestring, Valtype.i32),
3374
- ...setLastType(scope)
3626
+ ...setLastType(scope, TYPES.bytestring)
3375
3627
  ],
3376
3628
 
3377
3629
  default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
@@ -3396,7 +3648,7 @@ const objectHack = node => {
3396
3648
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3397
3649
 
3398
3650
  // if .name or .length, give up (hack within a hack!)
3399
- if (['name', 'length'].includes(node.property.name)) {
3651
+ if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
3400
3652
  node.object = objectHack(node.object);
3401
3653
  return;
3402
3654
  }
@@ -3433,33 +3685,39 @@ const generateFunc = (scope, decl) => {
3433
3685
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3434
3686
  const params = decl.params ?? [];
3435
3687
 
3436
- // const innerScope = { ...scope };
3437
3688
  // TODO: share scope/locals between !!!
3438
- const innerScope = {
3689
+ const func = {
3439
3690
  locals: {},
3440
3691
  localInd: 0,
3441
3692
  // value, type
3442
3693
  returns: [ valtypeBinary, Valtype.i32 ],
3443
3694
  throws: false,
3444
- name
3695
+ name,
3696
+ index: currentFuncIndex++
3445
3697
  };
3446
3698
 
3447
3699
  if (typedInput && decl.returnType) {
3448
3700
  const { type } = extractTypeAnnotation(decl.returnType);
3449
- if (type != null && !Prefs.indirectCalls) {
3450
- innerScope.returnType = type;
3451
- innerScope.returns = [ valtypeBinary ];
3701
+ // if (type != null && !Prefs.indirectCalls) {
3702
+ if (type != null) {
3703
+ func.returnType = type;
3704
+ func.returns = [ valtypeBinary ];
3452
3705
  }
3453
3706
  }
3454
3707
 
3455
3708
  for (let i = 0; i < params.length; i++) {
3456
- allocVar(innerScope, params[i].name, false);
3709
+ const name = params[i].name;
3710
+ // if (name == null) return todo('non-identifier args are not supported');
3711
+
3712
+ allocVar(func, name, false);
3457
3713
 
3458
3714
  if (typedInput && params[i].typeAnnotation) {
3459
- addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
3715
+ addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
3460
3716
  }
3461
3717
  }
3462
3718
 
3719
+ func.params = Object.values(func.locals).map(x => x.type);
3720
+
3463
3721
  let body = objectHack(decl.body);
3464
3722
  if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
3465
3723
  // hack: () => 0 -> () => return 0
@@ -3469,37 +3727,23 @@ const generateFunc = (scope, decl) => {
3469
3727
  };
3470
3728
  }
3471
3729
 
3472
- const wasm = generate(innerScope, body);
3473
- const func = {
3474
- name,
3475
- params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
3476
- index: currentFuncIndex++,
3477
- ...innerScope
3478
- };
3479
3730
  funcIndex[name] = func.index;
3731
+ funcs.push(func);
3480
3732
 
3481
- if (name === 'main') func.gotLastType = true;
3733
+ const wasm = generate(func, body);
3734
+ func.wasm = wasm;
3482
3735
 
3483
- // quick hack fixes
3484
- for (const inst of wasm) {
3485
- if (inst[0] === Opcodes.call && inst[1] === -1) {
3486
- inst[1] = func.index;
3487
- }
3488
- }
3736
+ if (name === 'main') func.gotLastType = true;
3489
3737
 
3490
3738
  // add end return if not found
3491
3739
  if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
3492
3740
  wasm.push(
3493
3741
  ...number(0),
3494
- ...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3742
+ ...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3495
3743
  [ Opcodes.return ]
3496
3744
  );
3497
3745
  }
3498
3746
 
3499
- func.wasm = wasm;
3500
-
3501
- funcs.push(func);
3502
-
3503
3747
  return func;
3504
3748
  };
3505
3749
 
@@ -3748,9 +3992,8 @@ export default program => {
3748
3992
 
3749
3993
  if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3750
3994
 
3751
- generateFunc(scope, program);
3995
+ const main = generateFunc(scope, program);
3752
3996
 
3753
- const main = funcs[funcs.length - 1];
3754
3997
  main.export = true;
3755
3998
  main.returns = [ valtypeBinary, Valtype.i32 ];
3756
3999
 
@@ -3777,7 +4020,7 @@ export default program => {
3777
4020
  }
3778
4021
 
3779
4022
  // if blank main func and other exports, remove it
3780
- if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
4023
+ if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(main.index - importedFuncs.length, 1);
3781
4024
 
3782
4025
  return { funcs, globals, tags, exceptions, pages, data };
3783
4026
  };