porffor 0.14.0-cdebd5442 → 0.14.0-db4b90996

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.
@@ -140,6 +140,9 @@ 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
 
@@ -200,19 +203,11 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
200
203
 
201
204
  __Porffor_bs: str => [
202
205
  ...makeString(scope, str, global, name, true),
203
-
204
- ...(name ? setType(scope, name, TYPES.bytestring) : [
205
- ...number(TYPES.bytestring, Valtype.i32),
206
- ...setLastType(scope)
207
- ])
206
+ ...(name ? setType(scope, name, TYPES.bytestring) : setLastType(scope, TYPES.bytestring))
208
207
  ],
209
208
  __Porffor_s: str => [
210
209
  ...makeString(scope, str, global, name, false),
211
-
212
- ...(name ? setType(scope, name, TYPES.string) : [
213
- ...number(TYPES.string, Valtype.i32),
214
- ...setLastType(scope)
215
- ])
210
+ ...(name ? setType(scope, name, TYPES.string) : setLastType(scope, TYPES.string))
216
211
  ],
217
212
  };
218
213
 
@@ -400,13 +395,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
400
395
  [ Opcodes.if, Valtype.i32 ],
401
396
  ...right,
402
397
  // note type
403
- ...rightType,
404
- ...setLastType(scope),
398
+ ...setLastType(scope, rightType),
405
399
  [ Opcodes.else ],
406
400
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
407
401
  // note type
408
- ...leftType,
409
- ...setLastType(scope),
402
+ ...setLastType(scope, leftType),
410
403
  [ Opcodes.end ],
411
404
  Opcodes.i32_from
412
405
  ];
@@ -419,13 +412,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
419
412
  [ Opcodes.if, valtypeBinary ],
420
413
  ...right,
421
414
  // note type
422
- ...rightType,
423
- ...setLastType(scope),
415
+ ...setLastType(scope, rightType),
424
416
  [ Opcodes.else ],
425
417
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
426
418
  // note type
427
- ...leftType,
428
- ...setLastType(scope),
419
+ ...setLastType(scope, leftType),
429
420
  [ Opcodes.end ]
430
421
  ];
431
422
  };
@@ -453,11 +444,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
453
444
  ...number(0, Valtype.i32), // base 0 for store later
454
445
 
455
446
  ...number(pointer, Valtype.i32),
456
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
447
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
457
448
  [ Opcodes.local_tee, leftLength ],
458
449
 
459
450
  [ Opcodes.local_get, rightPointer ],
460
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
451
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
461
452
  [ Opcodes.local_tee, rightLength ],
462
453
 
463
454
  [ Opcodes.i32_add ],
@@ -513,11 +504,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
513
504
  ...number(0, Valtype.i32), // base 0 for store later
514
505
 
515
506
  [ Opcodes.local_get, leftPointer ],
516
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
507
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
517
508
  [ Opcodes.local_tee, leftLength ],
518
509
 
519
510
  [ Opcodes.local_get, rightPointer ],
520
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
511
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
521
512
  [ Opcodes.local_tee, rightLength ],
522
513
 
523
514
  [ Opcodes.i32_add ],
@@ -595,11 +586,11 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
595
586
 
596
587
  // get lengths
597
588
  [ Opcodes.local_get, leftPointer ],
598
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
589
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
599
590
  [ Opcodes.local_tee, leftLength ],
600
591
 
601
592
  [ Opcodes.local_get, rightPointer ],
602
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
593
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
603
594
 
604
595
  // fast path: check leftLength != rightLength
605
596
  [ Opcodes.i32_ne ],
@@ -690,6 +681,25 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
690
681
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
691
682
 
692
683
  // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
684
+ // [ Opcodes.i32_eqz ],
685
+
686
+ // ...(intIn ? [
687
+ // ...number(0, Valtype.i32),
688
+ // [ Opcodes.i32_gt_s ]
689
+ // ] : [
690
+ // ...number(0),
691
+ // [ Opcodes.f64_gt ]
692
+ // ]),
693
+
694
+ // ...(intOut ? [] : [ Opcodes.i32_from ]),
695
+
696
+ // ...(intIn ? [] : [ Opcodes.i32_to ]),
697
+ // [ Opcodes.if, intOut ? Valtype.i32 : valtypeBinary ],
698
+ // ...number(1, intOut ? Valtype.i32 : valtypeBinary),
699
+ // [ Opcodes.else ],
700
+ // ...number(0, intOut ? Valtype.i32 : valtypeBinary),
701
+ // [ Opcodes.end ],
702
+
693
703
  ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
694
704
 
695
705
  /* Opcodes.eqz,
@@ -1053,7 +1063,7 @@ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals =
1053
1063
  });
1054
1064
  };
1055
1065
 
1056
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
1066
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false, callsSelf = false }) => {
1057
1067
  const existing = funcs.find(x => x.name === name);
1058
1068
  if (existing) return existing;
1059
1069
 
@@ -1101,9 +1111,24 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1101
1111
  index: currentFuncIndex++
1102
1112
  };
1103
1113
 
1114
+ if (callsSelf) for (const inst of wasm) {
1115
+ if (inst[0] === Opcodes.call && inst[1] === -1) {
1116
+ inst[1] = func.index;
1117
+ }
1118
+ }
1119
+
1120
+ if (table) for (const inst of wasm) {
1121
+ if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
1122
+ inst.splice(2, 99);
1123
+ inst.push(...unsignedLEB128(allocPage({}, 'func argc lut') * pageSize));
1124
+ }
1125
+ }
1126
+
1104
1127
  funcs.push(func);
1105
1128
  funcIndex[name] = func.index;
1106
1129
 
1130
+ if (table) funcs.table = true;
1131
+
1107
1132
  return func;
1108
1133
  };
1109
1134
 
@@ -1194,9 +1219,10 @@ const getLastType = scope => {
1194
1219
  return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1195
1220
  };
1196
1221
 
1197
- const setLastType = scope => {
1198
- return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1199
- };
1222
+ const setLastType = (scope, type = []) => [
1223
+ ...(typeof type === 'number' ? number(type, Valtype.i32) : type),
1224
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1225
+ ];
1200
1226
 
1201
1227
  const getNodeType = (scope, node) => {
1202
1228
  const ret = (() => {
@@ -1257,7 +1283,17 @@ const getNodeType = (scope, node) => {
1257
1283
 
1258
1284
  const func = spl[spl.length - 1];
1259
1285
  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;
1286
+ if (protoFuncs.length === 1) {
1287
+ if (protoFuncs[0].returnType) return protoFuncs[0].returnType;
1288
+ }
1289
+
1290
+ if (protoFuncs.length > 0) {
1291
+ if (scope.locals['#last_type']) return getLastType(scope);
1292
+
1293
+ // presume
1294
+ // todo: warn here?
1295
+ return TYPES.number;
1296
+ }
1261
1297
  }
1262
1298
 
1263
1299
  if (name.startsWith('__Porffor_wasm_')) {
@@ -1352,22 +1388,27 @@ const getNodeType = (scope, node) => {
1352
1388
  }
1353
1389
 
1354
1390
  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
- }
1391
+ const name = node.property.name;
1392
+
1393
+ if (name === 'length') {
1394
+ if (hasFuncWithName(node.object.name)) return TYPES.number;
1395
+ if (Prefs.fastLength) return TYPES.number;
1362
1396
  }
1363
1397
 
1364
- // hack: if something.length, number type
1365
- if (node.property.name === 'length') return TYPES.number;
1366
1398
 
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;
1399
+ const objectKnownType = knownType(scope, getNodeType(scope, node.object));
1400
+ if (objectKnownType != null) {
1401
+ if (name === 'length') {
1402
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(objectKnownType)) return TYPES.number;
1403
+ else return TYPES.undefined;
1404
+ }
1405
+
1406
+ if (node.computed) {
1407
+ if (objectKnownType === TYPES.string) return TYPES.string;
1408
+ if (objectKnownType === TYPES.bytestring) return TYPES.bytestring;
1409
+ if (objectKnownType === TYPES.array) return TYPES.number;
1410
+ }
1411
+ }
1371
1412
 
1372
1413
  if (scope.locals['#last_type']) return getLastType(scope);
1373
1414
 
@@ -1470,7 +1511,7 @@ const disposeLeftover = wasm => {
1470
1511
  const generateExp = (scope, decl) => {
1471
1512
  const expression = decl.expression;
1472
1513
 
1473
- const out = generate(scope, expression, undefined, undefined, true);
1514
+ const out = generate(scope, expression, undefined, undefined, Prefs.optUnused);
1474
1515
  disposeLeftover(out);
1475
1516
 
1476
1517
  return out;
@@ -1561,16 +1602,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1561
1602
  out.splice(out.length - 1, 1);
1562
1603
 
1563
1604
  const finalStatement = parsed.body[parsed.body.length - 1];
1564
- out.push(
1565
- ...getNodeType(scope, finalStatement),
1566
- ...setLastType(scope)
1567
- );
1605
+ out.push(...setLastType(scope, getNodeType(scope, finalStatement)));
1568
1606
  } else if (countLeftover(out) === 0) {
1569
1607
  out.push(...number(UNDEFINED));
1570
- out.push(
1571
- ...number(TYPES.undefined, Valtype.i32),
1572
- ...setLastType(scope)
1573
- );
1608
+ out.push(...setLastType(scope, TYPES.undefined));
1574
1609
  }
1575
1610
 
1576
1611
  // if (lastInst && lastInst[0] === Opcodes.drop) {
@@ -1622,8 +1657,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1622
1657
  [ Opcodes.call, idx ],
1623
1658
  Opcodes.i32_from_u,
1624
1659
 
1625
- ...number(TYPES.boolean, Valtype.i32),
1626
- ...setLastType(scope)
1660
+ ...setLastType(scope, TYPES.boolean)
1627
1661
  ];
1628
1662
  }
1629
1663
 
@@ -1695,9 +1729,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1695
1729
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
1696
1730
  protoBC[x] = [
1697
1731
  ...RTArrayUtil.getLength(getPointer),
1698
-
1699
- ...number(TYPES.number, Valtype.i32),
1700
- ...setLastType(scope)
1732
+ ...setLastType(scope, TYPES.number)
1701
1733
  ];
1702
1734
  continue;
1703
1735
  }
@@ -1716,7 +1748,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1716
1748
  getI32: () => RTArrayUtil.getLengthI32(getPointer),
1717
1749
  set: value => RTArrayUtil.setLength(getPointer, value),
1718
1750
  setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
1719
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1751
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), getNodeType(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1720
1752
  return makeArray(scope, {
1721
1753
  rawElements: new Array(length)
1722
1754
  }, _global, _name, true, itemType);
@@ -1730,9 +1762,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1730
1762
  protoBC[x] = [
1731
1763
  [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1732
1764
  ...protoOut,
1733
-
1734
- ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1735
- ...setLastType(scope),
1765
+ ...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
1736
1766
  [ Opcodes.end ]
1737
1767
  ];
1738
1768
  }
@@ -1838,36 +1868,128 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1838
1868
  const [ local, global ] = lookupName(scope, name);
1839
1869
  if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1840
1870
 
1841
- // todo: only works when:
1842
- // 1. arg count matches arg count of function
1843
- // 2. function uses typedParams and typedReturns
1871
+ // todo: only works when function uses typedParams and typedReturns
1872
+
1873
+ const indirectMode = Prefs.indirectCallMode ?? 'vararg';
1874
+ // options: vararg, strict
1875
+ // - strict: simpler, smaller size usage, no func argc lut needed.
1876
+ // ONLY works when arg count of call == arg count of function being called
1877
+ // - vararg: large size usage, cursed.
1878
+ // works when arg count of call != arg count of function being called*
1879
+ // * most of the time, some edgecases
1844
1880
 
1845
1881
  funcs.table = true;
1882
+ scope.table = true;
1846
1883
 
1847
1884
  let args = decl.arguments;
1848
- let argWasm = [];
1885
+ let out = [];
1886
+
1887
+ let locals = [];
1888
+
1889
+ if (indirectMode === 'vararg') {
1890
+ const minArgc = Prefs.indirectCallMinArgc ?? 3;
1891
+
1892
+ if (args.length < minArgc) {
1893
+ args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
1894
+ }
1895
+ }
1849
1896
 
1850
1897
  for (let i = 0; i < args.length; i++) {
1851
1898
  const arg = args[i];
1852
- argWasm = argWasm.concat(generate(scope, arg));
1899
+ out = out.concat(generate(scope, arg));
1853
1900
 
1854
1901
  if (valtypeBinary !== Valtype.i32 && (
1855
1902
  (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1856
1903
  (importedFuncs[name] && name.startsWith('profile'))
1857
1904
  )) {
1858
- argWasm.push(Opcodes.i32_to);
1905
+ out.push(Opcodes.i32_to);
1859
1906
  }
1860
1907
 
1861
- argWasm = argWasm.concat(getNodeType(scope, arg));
1908
+ out = out.concat(getNodeType(scope, arg));
1909
+
1910
+ if (indirectMode === 'vararg') {
1911
+ const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
1912
+ const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
1913
+
1914
+ locals.push([valLocal, typeLocal]);
1915
+
1916
+ out.push(
1917
+ [ Opcodes.local_set, typeLocal ],
1918
+ [ Opcodes.local_set, valLocal ]
1919
+ );
1920
+ }
1862
1921
  }
1863
1922
 
1923
+ if (indirectMode === 'strict') {
1924
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1925
+ [TYPES.function]: [
1926
+ ...argWasm,
1927
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1928
+ Opcodes.i32_to_u,
1929
+ [ Opcodes.call_indirect, args.length, 0 ],
1930
+ ...setLastType(scope)
1931
+ ],
1932
+ default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1933
+ });
1934
+ }
1935
+
1936
+ // hi, I will now explain how vararg mode works:
1937
+ // wasm's indirect_call instruction requires you know the func type at compile-time
1938
+ // since we have varargs (variable argument count), we do not know it.
1939
+ // we could just store args in memory and not use wasm func args,
1940
+ // but that is slow (probably) and breaks js exports.
1941
+ // instead, we generate every* possibility of argc and use different indirect_call
1942
+ // ops for each one, with type depending on argc for that branch.
1943
+ // then we load the argc for the wanted function from a memory lut,
1944
+ // and call the branch with the matching argc we require.
1945
+ // sorry, yes it is very cursed (and size inefficient), but indirect calls
1946
+ // are kind of rare anyway (mostly callbacks) so I am not concerned atm.
1947
+ // *for argc 0-3, in future (todo:) the max number should be
1948
+ // dynamically changed to the max argc of any func in the js file.
1949
+
1950
+ const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
1951
+
1952
+ const gen = argc => {
1953
+ const out = [];
1954
+ for (let i = 0; i < argc; i++) {
1955
+ out.push(
1956
+ [ Opcodes.local_get, locals[i][0] ],
1957
+ [ Opcodes.local_get, locals[i][1] ]
1958
+ );
1959
+ }
1960
+
1961
+ out.push(
1962
+ [ Opcodes.local_get, funcLocal ],
1963
+ [ Opcodes.call_indirect, argc, 0 ],
1964
+ ...setLastType(scope)
1965
+ )
1966
+
1967
+ return out;
1968
+ };
1969
+
1970
+ const tableBc = {};
1971
+ for (let i = 0; i <= args.length; i++) {
1972
+ tableBc[i] = gen(i);
1973
+ }
1974
+
1975
+ // todo/perf: check if we should use br_table here or just generate our own big if..elses
1976
+
1864
1977
  return typeSwitch(scope, getNodeType(scope, decl.callee), {
1865
1978
  [TYPES.function]: [
1866
- ...argWasm,
1979
+ ...out,
1980
+
1867
1981
  [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1868
1982
  Opcodes.i32_to_u,
1869
- [ Opcodes.call_indirect, args.length, 0 ],
1870
- ...setLastType(scope)
1983
+ [ Opcodes.local_set, funcLocal ],
1984
+
1985
+ ...brTable([
1986
+ // get argc of func we are calling
1987
+ [ Opcodes.local_get, funcLocal ],
1988
+ ...number(ValtypeSize.i16, Valtype.i32),
1989
+ [ Opcodes.i32_mul ],
1990
+
1991
+ [ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
1992
+ ], tableBc, valtypeBinary)
1871
1993
  ],
1872
1994
  default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1873
1995
  });
@@ -1901,6 +2023,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1901
2023
  const arg = args[i];
1902
2024
  out = out.concat(generate(scope, arg));
1903
2025
 
2026
+ // todo: this should be used instead of the too many args thing above (by removing that)
1904
2027
  if (i >= paramCount) {
1905
2028
  // over param count of func, drop arg
1906
2029
  out.push([ Opcodes.drop ]);
@@ -1985,8 +2108,11 @@ const knownType = (scope, type) => {
1985
2108
  const idx = type[0][1];
1986
2109
 
1987
2110
  // 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;
2111
+ const name = Object.values(scope.locals).find(x => x.idx === idx)?.name;
2112
+ if (name) {
2113
+ const local = scope.locals[name];
2114
+ if (local.metadata?.type != null) return v.metadata.type;
2115
+ }
1990
2116
  }
1991
2117
 
1992
2118
  return null;
@@ -2021,16 +2147,17 @@ const brTable = (input, bc, returns) => {
2021
2147
  }
2022
2148
 
2023
2149
  for (let i = 0; i < count; i++) {
2024
- if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2150
+ // if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2151
+ if (i === 0) out.push([ Opcodes.block, returns ]);
2025
2152
  else out.push([ Opcodes.block, Blocktype.void ]);
2026
2153
  }
2027
2154
 
2028
- const nums = keys.filter(x => +x);
2155
+ const nums = keys.filter(x => +x >= 0);
2029
2156
  const offset = Math.min(...nums);
2030
2157
  const max = Math.max(...nums);
2031
2158
 
2032
2159
  const table = [];
2033
- let br = 1;
2160
+ let br = 0;
2034
2161
 
2035
2162
  for (let i = offset; i <= max; i++) {
2036
2163
  // if branch for this num, go to that block
@@ -2070,10 +2197,9 @@ const brTable = (input, bc, returns) => {
2070
2197
  br--;
2071
2198
  }
2072
2199
 
2073
- return [
2074
- ...out,
2075
- [ Opcodes.end, 'br table end' ]
2076
- ];
2200
+ out.push([ Opcodes.end ]);
2201
+
2202
+ return out;
2077
2203
  };
2078
2204
 
2079
2205
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
@@ -2117,6 +2243,17 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2117
2243
  return out;
2118
2244
  };
2119
2245
 
2246
+ const typeIsOneOf = (type, types, valtype = Valtype.i32) => {
2247
+ const out = [];
2248
+
2249
+ for (let i = 0; i < types.length; i++) {
2250
+ out.push(...type, ...number(types[i], valtype), valtype === Valtype.f64 ? [ Opcodes.f64_eq ] : [ Opcodes.i32_eq ]);
2251
+ if (i !== 0) out.push([ Opcodes.i32_or ]);
2252
+ }
2253
+
2254
+ return out;
2255
+ };
2256
+
2120
2257
  const allocVar = (scope, name, global = false, type = true) => {
2121
2258
  const target = global ? globals : scope.locals;
2122
2259
 
@@ -2133,7 +2270,7 @@ const allocVar = (scope, name, global = false, type = true) => {
2133
2270
 
2134
2271
  if (type) {
2135
2272
  let typeIdx = global ? globalInd++ : scope.localInd++;
2136
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2273
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32, name };
2137
2274
  }
2138
2275
 
2139
2276
  return idx;
@@ -2346,18 +2483,21 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2346
2483
  Opcodes.i32_to_u,
2347
2484
 
2348
2485
  // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2349
- ...number(ValtypeSize[valtype], Valtype.i32),
2486
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2350
2487
  [ Opcodes.i32_mul ],
2351
2488
  ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
2352
2489
  ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2353
2490
 
2354
2491
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2355
2492
  [ 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)),
2493
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2494
+ ], generate(scope, decl.right), [
2495
+ [ Opcodes.local_get, pointerTmp ],
2496
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
2497
+ ], getNodeType(scope, decl.right), false, name, true)),
2358
2498
  [ Opcodes.local_tee, newValueTmp ],
2359
2499
 
2360
- [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2500
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2361
2501
  ],
2362
2502
 
2363
2503
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
@@ -2611,21 +2751,16 @@ const generateConditional = (scope, decl) => {
2611
2751
  out.push([ Opcodes.if, valtypeBinary ]);
2612
2752
  depth.push('if');
2613
2753
 
2614
- out.push(...generate(scope, decl.consequent));
2615
-
2616
- // note type
2617
2754
  out.push(
2618
- ...getNodeType(scope, decl.consequent),
2619
- ...setLastType(scope)
2755
+ ...generate(scope, decl.consequent),
2756
+ ...setLastType(scope, getNodeType(scope, decl.consequent))
2620
2757
  );
2621
2758
 
2622
2759
  out.push([ Opcodes.else ]);
2623
- out.push(...generate(scope, decl.alternate));
2624
2760
 
2625
- // note type
2626
2761
  out.push(
2627
- ...getNodeType(scope, decl.alternate),
2628
- ...setLastType(scope)
2762
+ ...generate(scope, decl.alternate),
2763
+ ...setLastType(scope, getNodeType(scope, decl.alternate))
2629
2764
  );
2630
2765
 
2631
2766
  out.push([ Opcodes.end ]);
@@ -2773,12 +2908,15 @@ const generateForOf = (scope, decl) => {
2773
2908
  // todo: optimize away counter and use end pointer
2774
2909
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2775
2910
  [TYPES.array]: [
2776
- ...setType(scope, leftName, TYPES.number),
2777
-
2778
2911
  [ Opcodes.loop, Blocktype.void ],
2779
2912
 
2780
2913
  [ Opcodes.local_get, pointer ],
2781
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2914
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2915
+
2916
+ ...setType(scope, leftName, [
2917
+ [ Opcodes.local_get, pointer ],
2918
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
2919
+ ]),
2782
2920
 
2783
2921
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2784
2922
 
@@ -2787,9 +2925,9 @@ const generateForOf = (scope, decl) => {
2787
2925
  ...generate(scope, decl.body),
2788
2926
  [ Opcodes.end ],
2789
2927
 
2790
- // increment iter pointer by valtype size
2928
+ // increment iter pointer by valtype size + 1
2791
2929
  [ Opcodes.local_get, pointer ],
2792
- ...number(ValtypeSize[valtype], Valtype.i32),
2930
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2793
2931
  [ Opcodes.i32_add ],
2794
2932
  [ Opcodes.local_set, pointer ],
2795
2933
 
@@ -3106,7 +3244,7 @@ const getAllocType = itemType => {
3106
3244
  }
3107
3245
  };
3108
3246
 
3109
- const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
3247
+ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype, typed = false) => {
3110
3248
  const out = [];
3111
3249
 
3112
3250
  scope.arrays ??= new Map();
@@ -3169,7 +3307,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3169
3307
 
3170
3308
  const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3171
3309
 
3172
- // store length as 0th array
3310
+ // store length
3173
3311
  out.push(
3174
3312
  ...pointerWasm,
3175
3313
  ...number(length, Valtype.i32),
@@ -3177,14 +3315,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3177
3315
  );
3178
3316
 
3179
3317
  const storeOp = StoreOps[itemType];
3180
-
3318
+ const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
3181
3319
  if (!initEmpty) for (let i = 0; i < length; i++) {
3182
3320
  if (elements[i] == null) continue;
3183
3321
 
3322
+ const offset = ValtypeSize.i32 + i * sizePerEl;
3184
3323
  out.push(
3185
3324
  ...pointerWasm,
3186
3325
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
3187
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3326
+ [ storeOp, 0, ...unsignedLEB128(offset) ],
3327
+ ...(!typed ? [] : [ // typed presumes !useRawElements
3328
+ ...pointerWasm,
3329
+ ...getNodeType(scope, elements[i]),
3330
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(offset + ValtypeSize[itemType]) ]
3331
+ ])
3188
3332
  );
3189
3333
  }
3190
3334
 
@@ -3194,6 +3338,65 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3194
3338
  return [ out, pointer ];
3195
3339
  };
3196
3340
 
3341
+ const storeArray = (scope, array, index, element, aotPointer = null) => {
3342
+ if (!Array.isArray(element)) element = generate(scope, element);
3343
+ if (typeof index === 'number') index = number(index);
3344
+
3345
+ const offset = localTmp(scope, '#storeArray_offset', Valtype.i32);
3346
+
3347
+ return [
3348
+ // calculate offset
3349
+ ...index,
3350
+ Opcodes.i32_to_u,
3351
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3352
+ [ Opcodes.i32_mul ],
3353
+ ...(aotPointer ? [] : [
3354
+ ...array,
3355
+ Opcodes.i32_to_u,
3356
+ [ Opcodes.i32_add ],
3357
+ ]),
3358
+ [ Opcodes.local_set, offset ],
3359
+
3360
+ // store value
3361
+ [ Opcodes.local_get, offset ],
3362
+ ...generate(scope, element),
3363
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3364
+
3365
+ // store type
3366
+ [ Opcodes.local_get, offset ],
3367
+ ...getNodeType(scope, element),
3368
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3369
+ ];
3370
+ };
3371
+
3372
+ const loadArray = (scope, array, index, aotPointer = null) => {
3373
+ if (typeof index === 'number') index = number(index);
3374
+
3375
+ const offset = localTmp(scope, '#loadArray_offset', Valtype.i32);
3376
+
3377
+ return [
3378
+ // calculate offset
3379
+ ...index,
3380
+ Opcodes.i32_to_u,
3381
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3382
+ [ Opcodes.i32_mul ],
3383
+ ...(aotPointer ? [] : [
3384
+ ...array,
3385
+ Opcodes.i32_to_u,
3386
+ [ Opcodes.i32_add ],
3387
+ ]),
3388
+ [ Opcodes.local_set, offset ],
3389
+
3390
+ // load value
3391
+ [ Opcodes.local_get, offset ],
3392
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3393
+
3394
+ // load type
3395
+ [ Opcodes.local_get, offset ],
3396
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3397
+ ];
3398
+ };
3399
+
3197
3400
  const byteStringable = str => {
3198
3401
  if (!Prefs.bytestring) return false;
3199
3402
 
@@ -3222,14 +3425,28 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
3222
3425
  };
3223
3426
 
3224
3427
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
3225
- return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
3428
+ return makeArray(scope, decl, global, name, initEmpty, valtype, true)[0];
3226
3429
  };
3227
3430
 
3228
- export const generateMember = (scope, decl, _global, _name) => {
3431
+ const generateObject = (scope, decl, global = false, name = '$undeclared') => {
3432
+ if (decl.properties.length > 0) return todo(scope, 'objects are not supported yet', true);
3433
+
3434
+ return [
3435
+ ...number(1),
3436
+ ...setLastType(scope, TYPES.object)
3437
+ ];
3438
+ };
3439
+
3440
+ const withType = (scope, wasm, type) => [
3441
+ ...wasm,
3442
+ ...setLastType(scope, type)
3443
+ ];
3444
+
3445
+ const generateMember = (scope, decl, _global, _name) => {
3229
3446
  const name = decl.object.name;
3230
3447
  const pointer = scope.arrays?.get(name);
3231
3448
 
3232
- const aotPointer = Prefs.aotPointerOpt && pointer != null;
3449
+ const aotPointer = Prefs.aotPointerOpt && pointer;
3233
3450
 
3234
3451
  // hack: .name
3235
3452
  if (decl.property.name === 'name') {
@@ -3239,9 +3456,9 @@ export const generateMember = (scope, decl, _global, _name) => {
3239
3456
  // eg: __String_prototype_toLowerCase -> toLowerCase
3240
3457
  if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
3241
3458
 
3242
- return makeString(scope, nameProp, _global, _name, true);
3459
+ return withType(scope, makeString(scope, nameProp, _global, _name, true), TYPES.bytestring);
3243
3460
  } else {
3244
- return generate(scope, DEFAULT_VALUE);
3461
+ return withType(scope, number(0), TYPES.undefined);
3245
3462
  }
3246
3463
  }
3247
3464
 
@@ -3251,7 +3468,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3251
3468
  if (func) {
3252
3469
  const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3253
3470
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3254
- return number(typedParams ? func.params.length / 2 : func.params.length);
3471
+ return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3255
3472
  }
3256
3473
 
3257
3474
  if (builtinFuncs[name + '$constructor']) {
@@ -3261,24 +3478,88 @@ export const generateMember = (scope, decl, _global, _name) => {
3261
3478
  const constructorFunc = builtinFuncs[name + '$constructor'];
3262
3479
  const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3263
3480
 
3264
- return number(Math.max(regularParams, constructorParams));
3481
+ return withType(scope, number(Math.max(regularParams, constructorParams)), TYPES.number);
3265
3482
  }
3266
3483
 
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);
3484
+ if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
3485
+ if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params), TYPES.number);
3486
+ if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
3487
+
3488
+ if (Prefs.fastLength) {
3489
+ // presume valid length object
3490
+ return [
3491
+ ...(aotPointer ? number(0, Valtype.i32) : [
3492
+ ...generate(scope, decl.object),
3493
+ Opcodes.i32_to_u
3494
+ ]),
3495
+
3496
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3497
+ Opcodes.i32_from_u
3498
+ ];
3499
+ }
3500
+
3501
+ const type = getNodeType(scope, decl.object);
3502
+ const known = knownType(scope, type);
3503
+ if (known != null) {
3504
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
3505
+ ...(aotPointer ? number(0, Valtype.i32) : [
3506
+ ...generate(scope, decl.object),
3507
+ Opcodes.i32_to_u
3508
+ ]),
3509
+
3510
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3511
+ Opcodes.i32_from_u
3512
+ ];
3513
+
3514
+ return number(0);
3515
+ }
3270
3516
 
3271
3517
  return [
3272
- ...(aotPointer ? number(0, Valtype.i32) : [
3273
- ...generate(scope, decl.object),
3274
- Opcodes.i32_to_u
3275
- ]),
3518
+ ...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
3519
+ [ Opcodes.if, valtypeBinary ],
3520
+ ...(aotPointer ? number(0, Valtype.i32) : [
3521
+ ...generate(scope, decl.object),
3522
+ Opcodes.i32_to_u
3523
+ ]),
3524
+
3525
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3526
+ Opcodes.i32_from_u,
3276
3527
 
3277
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128((aotPointer ? pointer : 0)) ],
3278
- Opcodes.i32_from_u
3528
+ ...setLastType(scope, TYPES.number),
3529
+ [ Opcodes.else ],
3530
+ ...number(0),
3531
+ ...setLastType(scope, TYPES.undefined),
3532
+ [ Opcodes.end ]
3279
3533
  ];
3280
3534
  }
3281
3535
 
3536
+ // todo: generate this array procedurally during builtinFuncs creation
3537
+ if (['size', 'description'].includes(decl.property.name)) {
3538
+ const bc = {};
3539
+ const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3540
+
3541
+ if (cands.length > 0) {
3542
+ for (const x of cands) {
3543
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
3544
+ if (type == null) continue;
3545
+
3546
+ bc[type] = generateCall(scope, {
3547
+ callee: {
3548
+ type: 'Identifier',
3549
+ name: x
3550
+ },
3551
+ arguments: [ decl.object ],
3552
+ _protoInternalCall: true
3553
+ });
3554
+ }
3555
+ }
3556
+
3557
+ return typeSwitch(scope, getNodeType(scope, decl.object), {
3558
+ ...bc,
3559
+ default: withType(scope, number(0), TYPES.undefined)
3560
+ }, valtypeBinary);
3561
+ }
3562
+
3282
3563
  const object = generate(scope, decl.object);
3283
3564
  const property = generate(scope, decl.property);
3284
3565
 
@@ -3293,24 +3574,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3293
3574
 
3294
3575
  return typeSwitch(scope, getNodeType(scope, decl.object), {
3295
3576
  [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),
3577
+ ...loadArray(scope, object, property, aotPointer),
3314
3578
  ...setLastType(scope)
3315
3579
  ],
3316
3580
 
@@ -3341,9 +3605,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3341
3605
 
3342
3606
  // return new string (page)
3343
3607
  ...number(newPointer),
3344
-
3345
- ...number(TYPES.string, Valtype.i32),
3346
- ...setLastType(scope)
3608
+ ...setLastType(scope, TYPES.string)
3347
3609
  ],
3348
3610
  [TYPES.bytestring]: [
3349
3611
  // setup new/out array
@@ -3369,9 +3631,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3369
3631
 
3370
3632
  // return new string (page)
3371
3633
  ...number(newPointer),
3372
-
3373
- ...number(TYPES.bytestring, Valtype.i32),
3374
- ...setLastType(scope)
3634
+ ...setLastType(scope, TYPES.bytestring)
3375
3635
  ],
3376
3636
 
3377
3637
  default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
@@ -3396,7 +3656,7 @@ const objectHack = node => {
3396
3656
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3397
3657
 
3398
3658
  // if .name or .length, give up (hack within a hack!)
3399
- if (['name', 'length'].includes(node.property.name)) {
3659
+ if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
3400
3660
  node.object = objectHack(node.object);
3401
3661
  return;
3402
3662
  }
@@ -3446,7 +3706,8 @@ const generateFunc = (scope, decl) => {
3446
3706
 
3447
3707
  if (typedInput && decl.returnType) {
3448
3708
  const { type } = extractTypeAnnotation(decl.returnType);
3449
- if (type != null && !Prefs.indirectCalls) {
3709
+ // if (type != null && !Prefs.indirectCalls) {
3710
+ if (type != null) {
3450
3711
  innerScope.returnType = type;
3451
3712
  innerScope.returns = [ valtypeBinary ];
3452
3713
  }