porffor 0.14.0-0d97d1e6a → 0.14.0-1169185ff

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
 
@@ -675,38 +668,50 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
675
668
  ];
676
669
  };
677
670
 
678
- const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
679
- 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 [
680
673
  ...wasm,
681
674
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
682
675
  ];
683
676
  // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
684
677
 
678
+ // todo/perf: use knownType and custom bytecode here instead of typeSwitch
679
+
685
680
  const useTmp = knownType(scope, type) == null;
686
681
  const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
687
682
 
688
- const def = [
689
- // if value != 0
690
- ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
683
+ const def = (truthyMode => {
684
+ if (truthyMode === 'full') return [
685
+ // if value != 0 or NaN
686
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
687
+ ...(intIn ? [ ] : [ Opcodes.i32_to ]),
691
688
 
692
- // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
693
- ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
689
+ [ Opcodes.i32_eqz ],
690
+ [ Opcodes.i32_eqz ],
694
691
 
695
- /* Opcodes.eqz,
696
- [ Opcodes.i32_eqz ],
697
- Opcodes.i32_from */
698
- ];
692
+ ...(intOut ? [] : [ Opcodes.i32_from ]),
693
+ ];
694
+
695
+ if (truthyMode === 'no_negative') return [
696
+ // if value != 0 or NaN, non-binary output. negative numbers not truthy :/
697
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
698
+ ...(intIn ? [] : [ Opcodes.i32_to ]),
699
+ ...(intOut ? [] : [ Opcodes.i32_from ])
700
+ ];
701
+
702
+ if (truthyMode === 'no_nan_negative') return [
703
+ // simpler and faster but makes NaN truthy and negative numbers not truthy,
704
+ // plus non-binary output
705
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
706
+ ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ])
707
+ ];
708
+ })(forceTruthyMode ?? Prefs.truthy ?? 'full');
699
709
 
700
710
  return [
701
711
  ...wasm,
702
712
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
703
713
 
704
714
  ...typeSwitch(scope, type, {
705
- // [TYPES.number]: def,
706
- [TYPES.array]: [
707
- // arrays are always truthy
708
- ...number(1, intOut ? Valtype.i32 : valtypeBinary)
709
- ],
710
715
  [TYPES.string]: [
711
716
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
712
717
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -742,10 +747,6 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
742
747
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
743
748
 
744
749
  ...typeSwitch(scope, type, {
745
- [TYPES.array]: [
746
- // arrays are always truthy
747
- ...number(0, intOut ? Valtype.i32 : valtypeBinary)
748
- ],
749
750
  [TYPES.string]: [
750
751
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
751
752
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -989,7 +990,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
989
990
  // if both are true
990
991
  [ Opcodes.i32_and ],
991
992
  [ Opcodes.if, Blocktype.void ],
992
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
993
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], false),
993
994
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
994
995
  [ Opcodes.br, 1 ],
995
996
  [ Opcodes.end ],
@@ -1038,14 +1039,14 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
1038
1039
  return out;
1039
1040
  };
1040
1041
 
1041
- const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1042
- return func({ name, params, locals, returns, localInd }, {
1042
+ const asmFuncToAsm = (func, scope) => {
1043
+ return func(scope, {
1043
1044
  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];
1045
+ builtin: n => {
1046
+ let idx = funcIndex[n] ?? importedFuncs[n];
1047
+ if (idx == null && builtinFuncs[n]) {
1048
+ includeBuiltin(null, n);
1049
+ idx = funcIndex[n];
1049
1050
  }
1050
1051
 
1051
1052
  return idx;
@@ -1053,7 +1054,7 @@ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals =
1053
1054
  });
1054
1055
  };
1055
1056
 
1056
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
1057
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false }) => {
1057
1058
  const existing = funcs.find(x => x.name === name);
1058
1059
  if (existing) return existing;
1059
1060
 
@@ -1071,7 +1072,22 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1071
1072
  data.push(copy);
1072
1073
  }
1073
1074
 
1074
- if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1075
+ const func = {
1076
+ name,
1077
+ params,
1078
+ locals,
1079
+ localInd: allLocals.length,
1080
+ returns,
1081
+ returnType: returnType ?? TYPES.number,
1082
+ internal: true,
1083
+ index: currentFuncIndex++,
1084
+ table
1085
+ };
1086
+
1087
+ funcs.push(func);
1088
+ funcIndex[name] = func.index;
1089
+
1090
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, func);
1075
1091
 
1076
1092
  let baseGlobalIdx, i = 0;
1077
1093
  for (const type of globalTypes) {
@@ -1090,19 +1106,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1090
1106
  }
1091
1107
  }
1092
1108
 
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
- };
1109
+ if (table) for (const inst of wasm) {
1110
+ if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
1111
+ inst.splice(2, 99);
1112
+ inst.push(...unsignedLEB128(allocPage({}, 'func argc lut') * pageSize));
1113
+ }
1114
+ }
1103
1115
 
1104
- funcs.push(func);
1105
- funcIndex[name] = func.index;
1116
+ func.wasm = wasm;
1106
1117
 
1107
1118
  return func;
1108
1119
  };
@@ -1194,9 +1205,10 @@ const getLastType = scope => {
1194
1205
  return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1195
1206
  };
1196
1207
 
1197
- const setLastType = scope => {
1198
- return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1199
- };
1208
+ const setLastType = (scope, type = []) => [
1209
+ ...(typeof type === 'number' ? number(type, Valtype.i32) : type),
1210
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1211
+ ];
1200
1212
 
1201
1213
  const getNodeType = (scope, node) => {
1202
1214
  const ret = (() => {
@@ -1257,7 +1269,17 @@ const getNodeType = (scope, node) => {
1257
1269
 
1258
1270
  const func = spl[spl.length - 1];
1259
1271
  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;
1272
+ if (protoFuncs.length === 1) {
1273
+ if (protoFuncs[0].returnType) return protoFuncs[0].returnType;
1274
+ }
1275
+
1276
+ if (protoFuncs.length > 0) {
1277
+ if (scope.locals['#last_type']) return getLastType(scope);
1278
+
1279
+ // presume
1280
+ // todo: warn here?
1281
+ return TYPES.number;
1282
+ }
1261
1283
  }
1262
1284
 
1263
1285
  if (name.startsWith('__Porffor_wasm_')) {
@@ -1352,22 +1374,27 @@ const getNodeType = (scope, node) => {
1352
1374
  }
1353
1375
 
1354
1376
  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
- }
1377
+ const name = node.property.name;
1378
+
1379
+ if (name === 'length') {
1380
+ if (hasFuncWithName(node.object.name)) return TYPES.number;
1381
+ if (Prefs.fastLength) return TYPES.number;
1362
1382
  }
1363
1383
 
1364
- // hack: if something.length, number type
1365
- if (node.property.name === 'length') return TYPES.number;
1366
1384
 
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;
1385
+ const objectKnownType = knownType(scope, getNodeType(scope, node.object));
1386
+ if (objectKnownType != null) {
1387
+ if (name === 'length') {
1388
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(objectKnownType)) return TYPES.number;
1389
+ else return TYPES.undefined;
1390
+ }
1391
+
1392
+ if (node.computed) {
1393
+ if (objectKnownType === TYPES.string) return TYPES.string;
1394
+ if (objectKnownType === TYPES.bytestring) return TYPES.bytestring;
1395
+ if (objectKnownType === TYPES.array) return TYPES.number;
1396
+ }
1397
+ }
1371
1398
 
1372
1399
  if (scope.locals['#last_type']) return getLastType(scope);
1373
1400
 
@@ -1438,16 +1465,11 @@ const countLeftover = wasm => {
1438
1465
  else if (inst[0] === Opcodes.return) count = 0;
1439
1466
  else if (inst[0] === Opcodes.call) {
1440
1467
  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;
1468
+ if (inst[1] < importedFuncs.length) {
1469
+ func = importedFuncs[inst[1]];
1470
+ count = count - func.params + func.returns;
1446
1471
  } else {
1447
- if (func) {
1448
- count -= func.params.length;
1449
- } else count--;
1450
- if (func) count += func.returns.length;
1472
+ count = count - func.params.length + func.returns.length;
1451
1473
  }
1452
1474
  } else if (inst[0] === Opcodes.call_indirect) {
1453
1475
  count--; // funcidx
@@ -1470,7 +1492,7 @@ const disposeLeftover = wasm => {
1470
1492
  const generateExp = (scope, decl) => {
1471
1493
  const expression = decl.expression;
1472
1494
 
1473
- const out = generate(scope, expression, undefined, undefined, true);
1495
+ const out = generate(scope, expression, undefined, undefined, Prefs.optUnused);
1474
1496
  disposeLeftover(out);
1475
1497
 
1476
1498
  return out;
@@ -1561,16 +1583,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1561
1583
  out.splice(out.length - 1, 1);
1562
1584
 
1563
1585
  const finalStatement = parsed.body[parsed.body.length - 1];
1564
- out.push(
1565
- ...getNodeType(scope, finalStatement),
1566
- ...setLastType(scope)
1567
- );
1586
+ out.push(...setLastType(scope, getNodeType(scope, finalStatement)));
1568
1587
  } else if (countLeftover(out) === 0) {
1569
1588
  out.push(...number(UNDEFINED));
1570
- out.push(
1571
- ...number(TYPES.undefined, Valtype.i32),
1572
- ...setLastType(scope)
1573
- );
1589
+ out.push(...setLastType(scope, TYPES.undefined));
1574
1590
  }
1575
1591
 
1576
1592
  // if (lastInst && lastInst[0] === Opcodes.drop) {
@@ -1606,6 +1622,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1606
1622
 
1607
1623
  if (!funcIndex[rhemynName]) {
1608
1624
  const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1625
+ func.internal = true;
1609
1626
 
1610
1627
  funcIndex[func.name] = func.index;
1611
1628
  funcs.push(func);
@@ -1622,8 +1639,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1622
1639
  [ Opcodes.call, idx ],
1623
1640
  Opcodes.i32_from_u,
1624
1641
 
1625
- ...number(TYPES.boolean, Valtype.i32),
1626
- ...setLastType(scope)
1642
+ ...setLastType(scope, TYPES.boolean)
1627
1643
  ];
1628
1644
  }
1629
1645
 
@@ -1695,9 +1711,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1695
1711
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
1696
1712
  protoBC[x] = [
1697
1713
  ...RTArrayUtil.getLength(getPointer),
1698
-
1699
- ...number(TYPES.number, Valtype.i32),
1700
- ...setLastType(scope)
1714
+ ...setLastType(scope, TYPES.number)
1701
1715
  ];
1702
1716
  continue;
1703
1717
  }
@@ -1716,7 +1730,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1716
1730
  getI32: () => RTArrayUtil.getLengthI32(getPointer),
1717
1731
  set: value => RTArrayUtil.setLength(getPointer, value),
1718
1732
  setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
1719
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1733
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), getNodeType(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1720
1734
  return makeArray(scope, {
1721
1735
  rawElements: new Array(length)
1722
1736
  }, _global, _name, true, itemType);
@@ -1730,9 +1744,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1730
1744
  protoBC[x] = [
1731
1745
  [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1732
1746
  ...protoOut,
1733
-
1734
- ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1735
- ...setLastType(scope),
1747
+ ...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
1736
1748
  [ Opcodes.end ]
1737
1749
  ];
1738
1750
  }
@@ -1782,11 +1794,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1782
1794
 
1783
1795
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1784
1796
 
1785
- if (idx === undefined && name === scope.name) {
1786
- // hack: calling self, func generator will fix later
1787
- idx = -1;
1788
- }
1789
-
1790
1797
  if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1791
1798
  const wasmOps = {
1792
1799
  // pointer, align, offset
@@ -1838,36 +1845,128 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1838
1845
  const [ local, global ] = lookupName(scope, name);
1839
1846
  if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1840
1847
 
1841
- // todo: only works when:
1842
- // 1. arg count matches arg count of function
1843
- // 2. function uses typedParams and typedReturns
1848
+ // todo: only works when function uses typedParams and typedReturns
1849
+
1850
+ const indirectMode = Prefs.indirectCallMode ?? 'vararg';
1851
+ // options: vararg, strict
1852
+ // - strict: simpler, smaller size usage, no func argc lut needed.
1853
+ // ONLY works when arg count of call == arg count of function being called
1854
+ // - vararg: large size usage, cursed.
1855
+ // works when arg count of call != arg count of function being called*
1856
+ // * most of the time, some edgecases
1844
1857
 
1845
1858
  funcs.table = true;
1859
+ scope.table = true;
1846
1860
 
1847
1861
  let args = decl.arguments;
1848
- let argWasm = [];
1862
+ let out = [];
1863
+
1864
+ let locals = [];
1865
+
1866
+ if (indirectMode === 'vararg') {
1867
+ const minArgc = Prefs.indirectCallMinArgc ?? 3;
1868
+
1869
+ if (args.length < minArgc) {
1870
+ args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
1871
+ }
1872
+ }
1849
1873
 
1850
1874
  for (let i = 0; i < args.length; i++) {
1851
1875
  const arg = args[i];
1852
- argWasm = argWasm.concat(generate(scope, arg));
1876
+ out = out.concat(generate(scope, arg));
1853
1877
 
1854
1878
  if (valtypeBinary !== Valtype.i32 && (
1855
1879
  (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1856
1880
  (importedFuncs[name] && name.startsWith('profile'))
1857
1881
  )) {
1858
- argWasm.push(Opcodes.i32_to);
1882
+ out.push(Opcodes.i32_to);
1859
1883
  }
1860
1884
 
1861
- argWasm = argWasm.concat(getNodeType(scope, arg));
1885
+ out = out.concat(getNodeType(scope, arg));
1886
+
1887
+ if (indirectMode === 'vararg') {
1888
+ const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
1889
+ const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
1890
+
1891
+ locals.push([valLocal, typeLocal]);
1892
+
1893
+ out.push(
1894
+ [ Opcodes.local_set, typeLocal ],
1895
+ [ Opcodes.local_set, valLocal ]
1896
+ );
1897
+ }
1898
+ }
1899
+
1900
+ if (indirectMode === 'strict') {
1901
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1902
+ [TYPES.function]: [
1903
+ ...argWasm,
1904
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1905
+ Opcodes.i32_to_u,
1906
+ [ Opcodes.call_indirect, args.length, 0 ],
1907
+ ...setLastType(scope)
1908
+ ],
1909
+ default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1910
+ });
1911
+ }
1912
+
1913
+ // hi, I will now explain how vararg mode works:
1914
+ // wasm's indirect_call instruction requires you know the func type at compile-time
1915
+ // since we have varargs (variable argument count), we do not know it.
1916
+ // we could just store args in memory and not use wasm func args,
1917
+ // but that is slow (probably) and breaks js exports.
1918
+ // instead, we generate every* possibility of argc and use different indirect_call
1919
+ // ops for each one, with type depending on argc for that branch.
1920
+ // then we load the argc for the wanted function from a memory lut,
1921
+ // and call the branch with the matching argc we require.
1922
+ // sorry, yes it is very cursed (and size inefficient), but indirect calls
1923
+ // are kind of rare anyway (mostly callbacks) so I am not concerned atm.
1924
+ // *for argc 0-3, in future (todo:) the max number should be
1925
+ // dynamically changed to the max argc of any func in the js file.
1926
+
1927
+ const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
1928
+
1929
+ const gen = argc => {
1930
+ const out = [];
1931
+ for (let i = 0; i < argc; i++) {
1932
+ out.push(
1933
+ [ Opcodes.local_get, locals[i][0] ],
1934
+ [ Opcodes.local_get, locals[i][1] ]
1935
+ );
1936
+ }
1937
+
1938
+ out.push(
1939
+ [ Opcodes.local_get, funcLocal ],
1940
+ [ Opcodes.call_indirect, argc, 0 ],
1941
+ ...setLastType(scope)
1942
+ )
1943
+
1944
+ return out;
1945
+ };
1946
+
1947
+ const tableBc = {};
1948
+ for (let i = 0; i <= args.length; i++) {
1949
+ tableBc[i] = gen(i);
1862
1950
  }
1863
1951
 
1952
+ // todo/perf: check if we should use br_table here or just generate our own big if..elses
1953
+
1864
1954
  return typeSwitch(scope, getNodeType(scope, decl.callee), {
1865
1955
  [TYPES.function]: [
1866
- ...argWasm,
1956
+ ...out,
1957
+
1867
1958
  [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1868
1959
  Opcodes.i32_to_u,
1869
- [ Opcodes.call_indirect, args.length, 0 ],
1870
- ...setLastType(scope)
1960
+ [ Opcodes.local_set, funcLocal ],
1961
+
1962
+ ...brTable([
1963
+ // get argc of func we are calling
1964
+ [ Opcodes.local_get, funcLocal ],
1965
+ ...number(ValtypeSize.i16, Valtype.i32),
1966
+ [ Opcodes.i32_mul ],
1967
+
1968
+ [ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
1969
+ ], tableBc, valtypeBinary)
1871
1970
  ],
1872
1971
  default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1873
1972
  });
@@ -1876,11 +1975,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1876
1975
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1877
1976
  }
1878
1977
 
1879
- const func = funcs.find(x => x.index === idx);
1880
-
1881
- const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1978
+ const func = funcs[idx - importedFuncs.length]; // idx === scope.index ? scope : funcs.find(x => x.index === idx);
1979
+ const userFunc = func && !func.internal;
1882
1980
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1883
- const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1981
+ const typedReturns = (func && func.returnType == null) || builtinFuncs[name]?.typedReturns;
1884
1982
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1885
1983
 
1886
1984
  let args = decl.arguments;
@@ -1901,6 +1999,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1901
1999
  const arg = args[i];
1902
2000
  out = out.concat(generate(scope, arg));
1903
2001
 
2002
+ // todo: this should be used instead of the too many args thing above (by removing that)
1904
2003
  if (i >= paramCount) {
1905
2004
  // over param count of func, drop arg
1906
2005
  out.push([ Opcodes.drop ]);
@@ -1985,8 +2084,11 @@ const knownType = (scope, type) => {
1985
2084
  const idx = type[0][1];
1986
2085
 
1987
2086
  // 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;
2087
+ const name = Object.values(scope.locals).find(x => x.idx === idx)?.name;
2088
+ if (name) {
2089
+ const local = scope.locals[name];
2090
+ if (local.metadata?.type != null) return v.metadata.type;
2091
+ }
1990
2092
  }
1991
2093
 
1992
2094
  return null;
@@ -2021,16 +2123,17 @@ const brTable = (input, bc, returns) => {
2021
2123
  }
2022
2124
 
2023
2125
  for (let i = 0; i < count; i++) {
2024
- if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2126
+ // if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2127
+ if (i === 0) out.push([ Opcodes.block, returns ]);
2025
2128
  else out.push([ Opcodes.block, Blocktype.void ]);
2026
2129
  }
2027
2130
 
2028
- const nums = keys.filter(x => +x);
2131
+ const nums = keys.filter(x => +x >= 0);
2029
2132
  const offset = Math.min(...nums);
2030
2133
  const max = Math.max(...nums);
2031
2134
 
2032
2135
  const table = [];
2033
- let br = 1;
2136
+ let br = 0;
2034
2137
 
2035
2138
  for (let i = offset; i <= max; i++) {
2036
2139
  // if branch for this num, go to that block
@@ -2070,10 +2173,9 @@ const brTable = (input, bc, returns) => {
2070
2173
  br--;
2071
2174
  }
2072
2175
 
2073
- return [
2074
- ...out,
2075
- [ Opcodes.end, 'br table end' ]
2076
- ];
2176
+ out.push([ Opcodes.end ]);
2177
+
2178
+ return out;
2077
2179
  };
2078
2180
 
2079
2181
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
@@ -2117,6 +2219,17 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2117
2219
  return out;
2118
2220
  };
2119
2221
 
2222
+ const typeIsOneOf = (type, types, valtype = Valtype.i32) => {
2223
+ const out = [];
2224
+
2225
+ for (let i = 0; i < types.length; i++) {
2226
+ out.push(...type, ...number(types[i], valtype), valtype === Valtype.f64 ? [ Opcodes.f64_eq ] : [ Opcodes.i32_eq ]);
2227
+ if (i !== 0) out.push([ Opcodes.i32_or ]);
2228
+ }
2229
+
2230
+ return out;
2231
+ };
2232
+
2120
2233
  const allocVar = (scope, name, global = false, type = true) => {
2121
2234
  const target = global ? globals : scope.locals;
2122
2235
 
@@ -2133,7 +2246,7 @@ const allocVar = (scope, name, global = false, type = true) => {
2133
2246
 
2134
2247
  if (type) {
2135
2248
  let typeIdx = global ? globalInd++ : scope.localInd++;
2136
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2249
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32, name };
2137
2250
  }
2138
2251
 
2139
2252
  return idx;
@@ -2346,18 +2459,21 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2346
2459
  Opcodes.i32_to_u,
2347
2460
 
2348
2461
  // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2349
- ...number(ValtypeSize[valtype], Valtype.i32),
2462
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2350
2463
  [ Opcodes.i32_mul ],
2351
2464
  ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
2352
2465
  ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2353
2466
 
2354
2467
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2355
2468
  [ 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)),
2469
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2470
+ ], generate(scope, decl.right), [
2471
+ [ Opcodes.local_get, pointerTmp ],
2472
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
2473
+ ], getNodeType(scope, decl.right), false, name, true)),
2358
2474
  [ Opcodes.local_tee, newValueTmp ],
2359
2475
 
2360
- [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2476
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2361
2477
  ],
2362
2478
 
2363
2479
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
@@ -2465,6 +2581,11 @@ const generateUnary = (scope, decl) => {
2465
2581
  ];
2466
2582
 
2467
2583
  case '!':
2584
+ const arg = decl.argument;
2585
+ if (arg.type === "UnaryExpression" && arg.operator === "!") {
2586
+ // !!x -> is x truthy
2587
+ return truthy(scope, generate(scope, arg.argument), getNodeType(scope, arg.argument), false, false);
2588
+ }
2468
2589
  // !=
2469
2590
  return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
2470
2591
 
@@ -2611,21 +2732,16 @@ const generateConditional = (scope, decl) => {
2611
2732
  out.push([ Opcodes.if, valtypeBinary ]);
2612
2733
  depth.push('if');
2613
2734
 
2614
- out.push(...generate(scope, decl.consequent));
2615
-
2616
- // note type
2617
2735
  out.push(
2618
- ...getNodeType(scope, decl.consequent),
2619
- ...setLastType(scope)
2736
+ ...generate(scope, decl.consequent),
2737
+ ...setLastType(scope, getNodeType(scope, decl.consequent))
2620
2738
  );
2621
2739
 
2622
2740
  out.push([ Opcodes.else ]);
2623
- out.push(...generate(scope, decl.alternate));
2624
2741
 
2625
- // note type
2626
2742
  out.push(
2627
- ...getNodeType(scope, decl.alternate),
2628
- ...setLastType(scope)
2743
+ ...generate(scope, decl.alternate),
2744
+ ...setLastType(scope, getNodeType(scope, decl.alternate))
2629
2745
  );
2630
2746
 
2631
2747
  out.push([ Opcodes.end ]);
@@ -2773,12 +2889,15 @@ const generateForOf = (scope, decl) => {
2773
2889
  // todo: optimize away counter and use end pointer
2774
2890
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2775
2891
  [TYPES.array]: [
2776
- ...setType(scope, leftName, TYPES.number),
2777
-
2778
2892
  [ Opcodes.loop, Blocktype.void ],
2779
2893
 
2780
2894
  [ Opcodes.local_get, pointer ],
2781
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2895
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2896
+
2897
+ ...setType(scope, leftName, [
2898
+ [ Opcodes.local_get, pointer ],
2899
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
2900
+ ]),
2782
2901
 
2783
2902
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2784
2903
 
@@ -2787,9 +2906,9 @@ const generateForOf = (scope, decl) => {
2787
2906
  ...generate(scope, decl.body),
2788
2907
  [ Opcodes.end ],
2789
2908
 
2790
- // increment iter pointer by valtype size
2909
+ // increment iter pointer by valtype size + 1
2791
2910
  [ Opcodes.local_get, pointer ],
2792
- ...number(ValtypeSize[valtype], Valtype.i32),
2911
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2793
2912
  [ Opcodes.i32_add ],
2794
2913
  [ Opcodes.local_set, pointer ],
2795
2914
 
@@ -2905,6 +3024,44 @@ const generateForOf = (scope, decl) => {
2905
3024
  [ Opcodes.end ],
2906
3025
  [ Opcodes.end ]
2907
3026
  ],
3027
+ [TYPES.set]: [
3028
+ [ Opcodes.loop, Blocktype.void ],
3029
+
3030
+ [ Opcodes.local_get, pointer ],
3031
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
3032
+
3033
+ ...setType(scope, leftName, [
3034
+ [ Opcodes.local_get, pointer ],
3035
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
3036
+ ]),
3037
+
3038
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
3039
+
3040
+ [ Opcodes.block, Blocktype.void ],
3041
+ [ Opcodes.block, Blocktype.void ],
3042
+ ...generate(scope, decl.body),
3043
+ [ Opcodes.end ],
3044
+
3045
+ // increment iter pointer by valtype size + 1
3046
+ [ Opcodes.local_get, pointer ],
3047
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3048
+ [ Opcodes.i32_add ],
3049
+ [ Opcodes.local_set, pointer ],
3050
+
3051
+ // increment counter by 1
3052
+ [ Opcodes.local_get, counter ],
3053
+ ...number(1, Valtype.i32),
3054
+ [ Opcodes.i32_add ],
3055
+ [ Opcodes.local_tee, counter ],
3056
+
3057
+ // loop if counter != length
3058
+ [ Opcodes.local_get, length ],
3059
+ [ Opcodes.i32_ne ],
3060
+ [ Opcodes.br_if, 1 ],
3061
+
3062
+ [ Opcodes.end ],
3063
+ [ Opcodes.end ]
3064
+ ],
2908
3065
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2909
3066
  }, Blocktype.void));
2910
3067
 
@@ -3006,14 +3163,18 @@ const generateThrow = (scope, decl) => {
3006
3163
  };
3007
3164
 
3008
3165
  const generateTry = (scope, decl) => {
3009
- if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
3166
+ // todo: handle control-flow pre-exit for finally
3167
+ // "Immediately before a control-flow statement (return, throw, break, continue) is executed in the try block or catch block."
3010
3168
 
3011
3169
  const out = [];
3012
3170
 
3171
+ const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
3172
+
3013
3173
  out.push([ Opcodes.try, Blocktype.void ]);
3014
3174
  depth.push('try');
3015
3175
 
3016
3176
  out.push(...generate(scope, decl.block));
3177
+ out.push(...finalizer);
3017
3178
 
3018
3179
  if (decl.handler) {
3019
3180
  depth.pop();
@@ -3021,6 +3182,7 @@ const generateTry = (scope, decl) => {
3021
3182
 
3022
3183
  out.push([ Opcodes.catch_all ]);
3023
3184
  out.push(...generate(scope, decl.handler.body));
3185
+ out.push(...finalizer);
3024
3186
  }
3025
3187
 
3026
3188
  out.push([ Opcodes.end ]);
@@ -3106,7 +3268,7 @@ const getAllocType = itemType => {
3106
3268
  }
3107
3269
  };
3108
3270
 
3109
- const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
3271
+ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype, typed = false) => {
3110
3272
  const out = [];
3111
3273
 
3112
3274
  scope.arrays ??= new Map();
@@ -3118,8 +3280,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3118
3280
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
3119
3281
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
3120
3282
 
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);
3283
+ let page;
3284
+ if (Prefs.scopedPageNames) page = allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType);
3285
+ else page = allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType);
3286
+
3287
+ // hack: use 1 for page 0 pointer for fast truthiness
3288
+ const ptr = page === 0 ? 1 : (page * pageSize);
3289
+ scope.arrays.set(name, ptr);
3123
3290
  }
3124
3291
 
3125
3292
  const pointer = scope.arrays.get(name);
@@ -3169,7 +3336,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3169
3336
 
3170
3337
  const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3171
3338
 
3172
- // store length as 0th array
3339
+ // store length
3173
3340
  out.push(
3174
3341
  ...pointerWasm,
3175
3342
  ...number(length, Valtype.i32),
@@ -3177,14 +3344,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3177
3344
  );
3178
3345
 
3179
3346
  const storeOp = StoreOps[itemType];
3180
-
3347
+ const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
3181
3348
  if (!initEmpty) for (let i = 0; i < length; i++) {
3182
3349
  if (elements[i] == null) continue;
3183
3350
 
3351
+ const offset = ValtypeSize.i32 + i * sizePerEl;
3184
3352
  out.push(
3185
3353
  ...pointerWasm,
3186
3354
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
3187
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3355
+ [ storeOp, 0, ...unsignedLEB128(offset) ],
3356
+ ...(!typed ? [] : [ // typed presumes !useRawElements
3357
+ ...pointerWasm,
3358
+ ...getNodeType(scope, elements[i]),
3359
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(offset + ValtypeSize[itemType]) ]
3360
+ ])
3188
3361
  );
3189
3362
  }
3190
3363
 
@@ -3194,6 +3367,65 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3194
3367
  return [ out, pointer ];
3195
3368
  };
3196
3369
 
3370
+ const storeArray = (scope, array, index, element, aotPointer = null) => {
3371
+ if (!Array.isArray(element)) element = generate(scope, element);
3372
+ if (typeof index === 'number') index = number(index);
3373
+
3374
+ const offset = localTmp(scope, '#storeArray_offset', Valtype.i32);
3375
+
3376
+ return [
3377
+ // calculate offset
3378
+ ...index,
3379
+ Opcodes.i32_to_u,
3380
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3381
+ [ Opcodes.i32_mul ],
3382
+ ...(aotPointer ? [] : [
3383
+ ...array,
3384
+ Opcodes.i32_to_u,
3385
+ [ Opcodes.i32_add ],
3386
+ ]),
3387
+ [ Opcodes.local_set, offset ],
3388
+
3389
+ // store value
3390
+ [ Opcodes.local_get, offset ],
3391
+ ...generate(scope, element),
3392
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3393
+
3394
+ // store type
3395
+ [ Opcodes.local_get, offset ],
3396
+ ...getNodeType(scope, element),
3397
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3398
+ ];
3399
+ };
3400
+
3401
+ const loadArray = (scope, array, index, aotPointer = null) => {
3402
+ if (typeof index === 'number') index = number(index);
3403
+
3404
+ const offset = localTmp(scope, '#loadArray_offset', Valtype.i32);
3405
+
3406
+ return [
3407
+ // calculate offset
3408
+ ...index,
3409
+ Opcodes.i32_to_u,
3410
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3411
+ [ Opcodes.i32_mul ],
3412
+ ...(aotPointer ? [] : [
3413
+ ...array,
3414
+ Opcodes.i32_to_u,
3415
+ [ Opcodes.i32_add ],
3416
+ ]),
3417
+ [ Opcodes.local_set, offset ],
3418
+
3419
+ // load value
3420
+ [ Opcodes.local_get, offset ],
3421
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3422
+
3423
+ // load type
3424
+ [ Opcodes.local_get, offset ],
3425
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3426
+ ];
3427
+ };
3428
+
3197
3429
  const byteStringable = str => {
3198
3430
  if (!Prefs.bytestring) return false;
3199
3431
 
@@ -3222,14 +3454,28 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
3222
3454
  };
3223
3455
 
3224
3456
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
3225
- return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
3457
+ return makeArray(scope, decl, global, name, initEmpty, valtype, true)[0];
3226
3458
  };
3227
3459
 
3228
- export const generateMember = (scope, decl, _global, _name) => {
3460
+ const generateObject = (scope, decl, global = false, name = '$undeclared') => {
3461
+ if (decl.properties.length > 0) return todo(scope, 'objects are not supported yet', true);
3462
+
3463
+ return [
3464
+ ...number(1),
3465
+ ...setLastType(scope, TYPES.object)
3466
+ ];
3467
+ };
3468
+
3469
+ const withType = (scope, wasm, type) => [
3470
+ ...wasm,
3471
+ ...setLastType(scope, type)
3472
+ ];
3473
+
3474
+ const generateMember = (scope, decl, _global, _name) => {
3229
3475
  const name = decl.object.name;
3230
3476
  const pointer = scope.arrays?.get(name);
3231
3477
 
3232
- const aotPointer = Prefs.aotPointerOpt && pointer != null;
3478
+ const aotPointer = Prefs.aotPointerOpt && pointer;
3233
3479
 
3234
3480
  // hack: .name
3235
3481
  if (decl.property.name === 'name') {
@@ -3239,9 +3485,9 @@ export const generateMember = (scope, decl, _global, _name) => {
3239
3485
  // eg: __String_prototype_toLowerCase -> toLowerCase
3240
3486
  if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
3241
3487
 
3242
- return makeString(scope, nameProp, _global, _name, true);
3488
+ return withType(scope, makeString(scope, nameProp, _global, _name, true), TYPES.bytestring);
3243
3489
  } else {
3244
- return generate(scope, DEFAULT_VALUE);
3490
+ return withType(scope, number(0), TYPES.undefined);
3245
3491
  }
3246
3492
  }
3247
3493
 
@@ -3249,9 +3495,8 @@ export const generateMember = (scope, decl, _global, _name) => {
3249
3495
  if (decl.property.name === 'length') {
3250
3496
  const func = funcs.find(x => x.name === name);
3251
3497
  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);
3498
+ const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
3499
+ return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3255
3500
  }
3256
3501
 
3257
3502
  if (builtinFuncs[name + '$constructor']) {
@@ -3261,24 +3506,88 @@ export const generateMember = (scope, decl, _global, _name) => {
3261
3506
  const constructorFunc = builtinFuncs[name + '$constructor'];
3262
3507
  const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3263
3508
 
3264
- return number(Math.max(regularParams, constructorParams));
3509
+ return withType(scope, number(Math.max(regularParams, constructorParams)), TYPES.number);
3265
3510
  }
3266
3511
 
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);
3512
+ if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
3513
+ if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params), TYPES.number);
3514
+ if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
3515
+
3516
+ if (Prefs.fastLength) {
3517
+ // presume valid length object
3518
+ return [
3519
+ ...(aotPointer ? number(0, Valtype.i32) : [
3520
+ ...generate(scope, decl.object),
3521
+ Opcodes.i32_to_u
3522
+ ]),
3523
+
3524
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3525
+ Opcodes.i32_from_u
3526
+ ];
3527
+ }
3528
+
3529
+ const type = getNodeType(scope, decl.object);
3530
+ const known = knownType(scope, type);
3531
+ if (known != null) {
3532
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
3533
+ ...(aotPointer ? number(0, Valtype.i32) : [
3534
+ ...generate(scope, decl.object),
3535
+ Opcodes.i32_to_u
3536
+ ]),
3537
+
3538
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3539
+ Opcodes.i32_from_u
3540
+ ];
3541
+
3542
+ return number(0);
3543
+ }
3270
3544
 
3271
3545
  return [
3272
- ...(aotPointer ? number(0, Valtype.i32) : [
3273
- ...generate(scope, decl.object),
3274
- Opcodes.i32_to_u
3275
- ]),
3546
+ ...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
3547
+ [ Opcodes.if, valtypeBinary ],
3548
+ ...(aotPointer ? number(0, Valtype.i32) : [
3549
+ ...generate(scope, decl.object),
3550
+ Opcodes.i32_to_u
3551
+ ]),
3552
+
3553
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3554
+ Opcodes.i32_from_u,
3276
3555
 
3277
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128((aotPointer ? pointer : 0)) ],
3278
- Opcodes.i32_from_u
3556
+ ...setLastType(scope, TYPES.number),
3557
+ [ Opcodes.else ],
3558
+ ...number(0),
3559
+ ...setLastType(scope, TYPES.undefined),
3560
+ [ Opcodes.end ]
3279
3561
  ];
3280
3562
  }
3281
3563
 
3564
+ // todo: generate this array procedurally during builtinFuncs creation
3565
+ if (['size', 'description'].includes(decl.property.name)) {
3566
+ const bc = {};
3567
+ const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3568
+
3569
+ if (cands.length > 0) {
3570
+ for (const x of cands) {
3571
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
3572
+ if (type == null) continue;
3573
+
3574
+ bc[type] = generateCall(scope, {
3575
+ callee: {
3576
+ type: 'Identifier',
3577
+ name: x
3578
+ },
3579
+ arguments: [ decl.object ],
3580
+ _protoInternalCall: true
3581
+ });
3582
+ }
3583
+ }
3584
+
3585
+ return typeSwitch(scope, getNodeType(scope, decl.object), {
3586
+ ...bc,
3587
+ default: withType(scope, number(0), TYPES.undefined)
3588
+ }, valtypeBinary);
3589
+ }
3590
+
3282
3591
  const object = generate(scope, decl.object);
3283
3592
  const property = generate(scope, decl.property);
3284
3593
 
@@ -3293,24 +3602,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3293
3602
 
3294
3603
  return typeSwitch(scope, getNodeType(scope, decl.object), {
3295
3604
  [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),
3605
+ ...loadArray(scope, object, property, aotPointer),
3314
3606
  ...setLastType(scope)
3315
3607
  ],
3316
3608
 
@@ -3341,9 +3633,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3341
3633
 
3342
3634
  // return new string (page)
3343
3635
  ...number(newPointer),
3344
-
3345
- ...number(TYPES.string, Valtype.i32),
3346
- ...setLastType(scope)
3636
+ ...setLastType(scope, TYPES.string)
3347
3637
  ],
3348
3638
  [TYPES.bytestring]: [
3349
3639
  // setup new/out array
@@ -3369,9 +3659,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3369
3659
 
3370
3660
  // return new string (page)
3371
3661
  ...number(newPointer),
3372
-
3373
- ...number(TYPES.bytestring, Valtype.i32),
3374
- ...setLastType(scope)
3662
+ ...setLastType(scope, TYPES.bytestring)
3375
3663
  ],
3376
3664
 
3377
3665
  default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
@@ -3396,7 +3684,7 @@ const objectHack = node => {
3396
3684
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3397
3685
 
3398
3686
  // if .name or .length, give up (hack within a hack!)
3399
- if (['name', 'length'].includes(node.property.name)) {
3687
+ if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
3400
3688
  node.object = objectHack(node.object);
3401
3689
  return;
3402
3690
  }
@@ -3433,33 +3721,39 @@ const generateFunc = (scope, decl) => {
3433
3721
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3434
3722
  const params = decl.params ?? [];
3435
3723
 
3436
- // const innerScope = { ...scope };
3437
3724
  // TODO: share scope/locals between !!!
3438
- const innerScope = {
3725
+ const func = {
3439
3726
  locals: {},
3440
3727
  localInd: 0,
3441
3728
  // value, type
3442
3729
  returns: [ valtypeBinary, Valtype.i32 ],
3443
3730
  throws: false,
3444
- name
3731
+ name,
3732
+ index: currentFuncIndex++
3445
3733
  };
3446
3734
 
3447
3735
  if (typedInput && decl.returnType) {
3448
3736
  const { type } = extractTypeAnnotation(decl.returnType);
3449
- if (type != null && !Prefs.indirectCalls) {
3450
- innerScope.returnType = type;
3451
- innerScope.returns = [ valtypeBinary ];
3737
+ // if (type != null && !Prefs.indirectCalls) {
3738
+ if (type != null) {
3739
+ func.returnType = type;
3740
+ func.returns = [ valtypeBinary ];
3452
3741
  }
3453
3742
  }
3454
3743
 
3455
3744
  for (let i = 0; i < params.length; i++) {
3456
- allocVar(innerScope, params[i].name, false);
3745
+ const name = params[i].name;
3746
+ // if (name == null) return todo('non-identifier args are not supported');
3747
+
3748
+ allocVar(func, name, false);
3457
3749
 
3458
3750
  if (typedInput && params[i].typeAnnotation) {
3459
- addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
3751
+ addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
3460
3752
  }
3461
3753
  }
3462
3754
 
3755
+ func.params = Object.values(func.locals).map(x => x.type);
3756
+
3463
3757
  let body = objectHack(decl.body);
3464
3758
  if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
3465
3759
  // hack: () => 0 -> () => return 0
@@ -3469,37 +3763,23 @@ const generateFunc = (scope, decl) => {
3469
3763
  };
3470
3764
  }
3471
3765
 
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
3766
  funcIndex[name] = func.index;
3767
+ funcs.push(func);
3480
3768
 
3481
- if (name === 'main') func.gotLastType = true;
3769
+ const wasm = generate(func, body);
3770
+ func.wasm = wasm;
3482
3771
 
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
- }
3772
+ if (name === 'main') func.gotLastType = true;
3489
3773
 
3490
3774
  // add end return if not found
3491
3775
  if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
3492
3776
  wasm.push(
3493
3777
  ...number(0),
3494
- ...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3778
+ ...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3495
3779
  [ Opcodes.return ]
3496
3780
  );
3497
3781
  }
3498
3782
 
3499
- func.wasm = wasm;
3500
-
3501
- funcs.push(func);
3502
-
3503
3783
  return func;
3504
3784
  };
3505
3785
 
@@ -3603,7 +3883,7 @@ const internalConstrs = {
3603
3883
  generate: (scope, decl) => {
3604
3884
  // todo: boolean object when used as constructor
3605
3885
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3606
- return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3886
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg), false, false, 'full');
3607
3887
  },
3608
3888
  type: TYPES.boolean,
3609
3889
  length: 1
@@ -3748,9 +4028,8 @@ export default program => {
3748
4028
 
3749
4029
  if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3750
4030
 
3751
- generateFunc(scope, program);
4031
+ const main = generateFunc(scope, program);
3752
4032
 
3753
- const main = funcs[funcs.length - 1];
3754
4033
  main.export = true;
3755
4034
  main.returns = [ valtypeBinary, Valtype.i32 ];
3756
4035
 
@@ -3777,7 +4056,7 @@ export default program => {
3777
4056
  }
3778
4057
 
3779
4058
  // 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);
4059
+ if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(main.index - importedFuncs.length, 1);
3781
4060
 
3782
4061
  return { funcs, globals, tags, exceptions, pages, data };
3783
4062
  };