porffor 0.14.0-4e46400a9 → 0.14.0-549a04f84

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
 
@@ -351,9 +346,7 @@ const generateReturn = (scope, decl) => {
351
346
 
352
347
  return [
353
348
  ...generate(scope, decl.argument),
354
- ...(scope.returnType != null ? [] : [
355
- ...getNodeType(scope, decl.argument)
356
- ]),
349
+ ...(scope.returnType != null ? [] : getNodeType(scope, decl.argument)),
357
350
  [ Opcodes.return ]
358
351
  ];
359
352
  };
@@ -402,13 +395,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
402
395
  [ Opcodes.if, Valtype.i32 ],
403
396
  ...right,
404
397
  // note type
405
- ...rightType,
406
- ...setLastType(scope),
398
+ ...setLastType(scope, rightType),
407
399
  [ Opcodes.else ],
408
400
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
409
401
  // note type
410
- ...leftType,
411
- ...setLastType(scope),
402
+ ...setLastType(scope, leftType),
412
403
  [ Opcodes.end ],
413
404
  Opcodes.i32_from
414
405
  ];
@@ -421,13 +412,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
421
412
  [ Opcodes.if, valtypeBinary ],
422
413
  ...right,
423
414
  // note type
424
- ...rightType,
425
- ...setLastType(scope),
415
+ ...setLastType(scope, rightType),
426
416
  [ Opcodes.else ],
427
417
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
428
418
  // note type
429
- ...leftType,
430
- ...setLastType(scope),
419
+ ...setLastType(scope, leftType),
431
420
  [ Opcodes.end ]
432
421
  ];
433
422
  };
@@ -455,11 +444,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
455
444
  ...number(0, Valtype.i32), // base 0 for store later
456
445
 
457
446
  ...number(pointer, Valtype.i32),
458
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
447
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
459
448
  [ Opcodes.local_tee, leftLength ],
460
449
 
461
450
  [ Opcodes.local_get, rightPointer ],
462
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
451
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
463
452
  [ Opcodes.local_tee, rightLength ],
464
453
 
465
454
  [ Opcodes.i32_add ],
@@ -515,11 +504,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
515
504
  ...number(0, Valtype.i32), // base 0 for store later
516
505
 
517
506
  [ Opcodes.local_get, leftPointer ],
518
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
507
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
519
508
  [ Opcodes.local_tee, leftLength ],
520
509
 
521
510
  [ Opcodes.local_get, rightPointer ],
522
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
511
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
523
512
  [ Opcodes.local_tee, rightLength ],
524
513
 
525
514
  [ Opcodes.i32_add ],
@@ -597,11 +586,11 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
597
586
 
598
587
  // get lengths
599
588
  [ Opcodes.local_get, leftPointer ],
600
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
589
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
601
590
  [ Opcodes.local_tee, leftLength ],
602
591
 
603
592
  [ Opcodes.local_get, rightPointer ],
604
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
593
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
605
594
 
606
595
  // fast path: check leftLength != rightLength
607
596
  [ Opcodes.i32_ne ],
@@ -1055,7 +1044,7 @@ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals =
1055
1044
  });
1056
1045
  };
1057
1046
 
1058
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
1047
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [], callsSelf = false }) => {
1059
1048
  const existing = funcs.find(x => x.name === name);
1060
1049
  if (existing) return existing;
1061
1050
 
@@ -1103,6 +1092,12 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1103
1092
  index: currentFuncIndex++
1104
1093
  };
1105
1094
 
1095
+ if (callsSelf) for (const inst of wasm) {
1096
+ if (inst[0] === Opcodes.call && inst[1] === -1) {
1097
+ inst[1] = func.index;
1098
+ }
1099
+ }
1100
+
1106
1101
  funcs.push(func);
1107
1102
  funcIndex[name] = func.index;
1108
1103
 
@@ -1196,9 +1191,10 @@ const getLastType = scope => {
1196
1191
  return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1197
1192
  };
1198
1193
 
1199
- const setLastType = scope => {
1200
- return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1201
- };
1194
+ const setLastType = (scope, type = []) => [
1195
+ ...(typeof type === 'number' ? number(type, Valtype.i32) : type),
1196
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1197
+ ];
1202
1198
 
1203
1199
  const getNodeType = (scope, node) => {
1204
1200
  const ret = (() => {
@@ -1259,7 +1255,17 @@ const getNodeType = (scope, node) => {
1259
1255
 
1260
1256
  const func = spl[spl.length - 1];
1261
1257
  const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
1262
- if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1258
+ if (protoFuncs.length === 1) {
1259
+ if (protoFuncs[0].returnType) return protoFuncs[0].returnType;
1260
+ }
1261
+
1262
+ if (protoFuncs.length > 0) {
1263
+ if (scope.locals['#last_type']) return getLastType(scope);
1264
+
1265
+ // presume
1266
+ // todo: warn here?
1267
+ return TYPES.number;
1268
+ }
1263
1269
  }
1264
1270
 
1265
1271
  if (name.startsWith('__Porffor_wasm_')) {
@@ -1354,22 +1360,27 @@ const getNodeType = (scope, node) => {
1354
1360
  }
1355
1361
 
1356
1362
  if (node.type === 'MemberExpression') {
1357
- // hack: if something.name, string type
1358
- if (node.property.name === 'name') {
1359
- if (hasFuncWithName(node.object.name)) {
1360
- return TYPES.bytestring;
1361
- } else {
1362
- return TYPES.undefined;
1363
- }
1363
+ const name = node.property.name;
1364
+
1365
+ if (name === 'length') {
1366
+ if (hasFuncWithName(node.object.name)) return TYPES.number;
1367
+ if (Prefs.fastLength) return TYPES.number;
1364
1368
  }
1365
1369
 
1366
- // hack: if something.length, number type
1367
- if (node.property.name === 'length') return TYPES.number;
1368
1370
 
1369
- // ts hack
1370
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1371
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.bytestring) return TYPES.bytestring;
1372
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.array) return TYPES.number;
1371
+ const objectKnownType = knownType(scope, getNodeType(scope, node.object));
1372
+ if (objectKnownType != null) {
1373
+ if (name === 'length') {
1374
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(objectKnownType)) return TYPES.number;
1375
+ else return TYPES.undefined;
1376
+ }
1377
+
1378
+ if (node.computed) {
1379
+ if (objectKnownType === TYPES.string) return TYPES.string;
1380
+ if (objectKnownType === TYPES.bytestring) return TYPES.bytestring;
1381
+ if (objectKnownType === TYPES.array) return TYPES.number;
1382
+ }
1383
+ }
1373
1384
 
1374
1385
  if (scope.locals['#last_type']) return getLastType(scope);
1375
1386
 
@@ -1451,6 +1462,10 @@ const countLeftover = wasm => {
1451
1462
  } else count--;
1452
1463
  if (func) count += func.returns.length;
1453
1464
  }
1465
+ } else if (inst[0] === Opcodes.call_indirect) {
1466
+ count--; // funcidx
1467
+ count -= inst[1] * 2; // params * 2 (typed)
1468
+ count += 2; // fixed return (value, type)
1454
1469
  } else count--;
1455
1470
 
1456
1471
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1468,7 +1483,7 @@ const disposeLeftover = wasm => {
1468
1483
  const generateExp = (scope, decl) => {
1469
1484
  const expression = decl.expression;
1470
1485
 
1471
- const out = generate(scope, expression, undefined, undefined, true);
1486
+ const out = generate(scope, expression, undefined, undefined, Prefs.optUnused);
1472
1487
  disposeLeftover(out);
1473
1488
 
1474
1489
  return out;
@@ -1559,16 +1574,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1559
1574
  out.splice(out.length - 1, 1);
1560
1575
 
1561
1576
  const finalStatement = parsed.body[parsed.body.length - 1];
1562
- out.push(
1563
- ...getNodeType(scope, finalStatement),
1564
- ...setLastType(scope)
1565
- );
1577
+ out.push(...setLastType(scope, getNodeType(scope, finalStatement)));
1566
1578
  } else if (countLeftover(out) === 0) {
1567
1579
  out.push(...number(UNDEFINED));
1568
- out.push(
1569
- ...number(TYPES.undefined, Valtype.i32),
1570
- ...setLastType(scope)
1571
- );
1580
+ out.push(...setLastType(scope, TYPES.undefined));
1572
1581
  }
1573
1582
 
1574
1583
  // if (lastInst && lastInst[0] === Opcodes.drop) {
@@ -1620,8 +1629,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1620
1629
  [ Opcodes.call, idx ],
1621
1630
  Opcodes.i32_from_u,
1622
1631
 
1623
- ...number(TYPES.boolean, Valtype.i32),
1624
- ...setLastType(scope)
1632
+ ...setLastType(scope, TYPES.boolean)
1625
1633
  ];
1626
1634
  }
1627
1635
 
@@ -1693,9 +1701,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1693
1701
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
1694
1702
  protoBC[x] = [
1695
1703
  ...RTArrayUtil.getLength(getPointer),
1696
-
1697
- ...number(TYPES.number, Valtype.i32),
1698
- ...setLastType(scope)
1704
+ ...setLastType(scope, TYPES.number)
1699
1705
  ];
1700
1706
  continue;
1701
1707
  }
@@ -1714,7 +1720,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1714
1720
  getI32: () => RTArrayUtil.getLengthI32(getPointer),
1715
1721
  set: value => RTArrayUtil.setLength(getPointer, value),
1716
1722
  setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
1717
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1723
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), getNodeType(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1718
1724
  return makeArray(scope, {
1719
1725
  rawElements: new Array(length)
1720
1726
  }, _global, _name, true, itemType);
@@ -1728,9 +1734,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1728
1734
  protoBC[x] = [
1729
1735
  [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1730
1736
  ...protoOut,
1731
-
1732
- ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1733
- ...setLastType(scope),
1737
+ ...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
1734
1738
  [ Opcodes.end ]
1735
1739
  ];
1736
1740
  }
@@ -1833,22 +1837,44 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1833
1837
 
1834
1838
  if (idx === undefined) {
1835
1839
  if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) {
1836
- if (!Prefs.indirectCalls) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1837
-
1838
1840
  const [ local, global ] = lookupName(scope, name);
1839
- funcs.table = true;
1841
+ if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1840
1842
 
1841
1843
  // todo: only works when:
1842
1844
  // 1. arg count matches arg count of function
1843
1845
  // 2. function uses typedParams and typedReturns
1844
- return typeSwitch(scope, getNodeType(decl.callee), {
1846
+
1847
+ funcs.table = true;
1848
+
1849
+ let args = decl.arguments;
1850
+ let argWasm = [];
1851
+
1852
+ for (let i = 0; i < args.length; i++) {
1853
+ const arg = args[i];
1854
+ argWasm = argWasm.concat(generate(scope, arg));
1855
+
1856
+ if (valtypeBinary !== Valtype.i32 && (
1857
+ (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1858
+ (importedFuncs[name] && name.startsWith('profile'))
1859
+ )) {
1860
+ argWasm.push(Opcodes.i32_to);
1861
+ }
1862
+
1863
+ argWasm = argWasm.concat(getNodeType(scope, arg));
1864
+ }
1865
+
1866
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1845
1867
  [TYPES.function]: [
1868
+ ...argWasm,
1846
1869
  [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1847
- [ Opcodes.call_indirect ]
1870
+ Opcodes.i32_to_u,
1871
+ [ Opcodes.call_indirect, args.length, 0 ],
1872
+ ...setLastType(scope)
1848
1873
  ],
1849
1874
  default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1850
1875
  });
1851
1876
  }
1877
+
1852
1878
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1853
1879
  }
1854
1880
 
@@ -1877,6 +1903,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1877
1903
  const arg = args[i];
1878
1904
  out = out.concat(generate(scope, arg));
1879
1905
 
1906
+ // todo: this should be used instead of the too many args thing above (by removing that)
1907
+ if (i >= paramCount) {
1908
+ // over param count of func, drop arg
1909
+ out.push([ Opcodes.drop ]);
1910
+ continue;
1911
+ }
1912
+
1880
1913
  if (valtypeBinary !== Valtype.i32 && (
1881
1914
  (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1882
1915
  (importedFuncs[name] && name.startsWith('profile'))
@@ -1925,6 +1958,11 @@ const generateNew = (scope, decl, _global, _name) => {
1925
1958
  }, _global, _name);
1926
1959
  }
1927
1960
 
1961
+ if (
1962
+ (builtinFuncs[name] && !builtinFuncs[name].constr) ||
1963
+ (internalConstrs[name] && builtinFuncs[name].notConstr)
1964
+ ) return internalThrow(scope, 'TypeError', `${name} is not a constructor`);
1965
+
1928
1966
  if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1929
1967
 
1930
1968
  return generateCall(scope, decl, _global, _name);
@@ -1950,8 +1988,11 @@ const knownType = (scope, type) => {
1950
1988
  const idx = type[0][1];
1951
1989
 
1952
1990
  // type idx = var idx + 1
1953
- const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
1954
- if (v.metadata?.type != null) return v.metadata.type;
1991
+ const name = Object.values(scope.locals).find(x => x.idx === idx)?.name;
1992
+ if (name) {
1993
+ const local = scope.locals[name];
1994
+ if (local.metadata?.type != null) return v.metadata.type;
1995
+ }
1955
1996
  }
1956
1997
 
1957
1998
  return null;
@@ -2082,6 +2123,17 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2082
2123
  return out;
2083
2124
  };
2084
2125
 
2126
+ const typeIsOneOf = (type, types, valtype = Valtype.i32) => {
2127
+ const out = [];
2128
+
2129
+ for (let i = 0; i < types.length; i++) {
2130
+ out.push(...type, ...number(types[i], valtype), valtype === Valtype.f64 ? [ Opcodes.f64_eq ] : [ Opcodes.i32_eq ]);
2131
+ if (i !== 0) out.push([ Opcodes.i32_or ]);
2132
+ }
2133
+
2134
+ return out;
2135
+ };
2136
+
2085
2137
  const allocVar = (scope, name, global = false, type = true) => {
2086
2138
  const target = global ? globals : scope.locals;
2087
2139
 
@@ -2098,7 +2150,7 @@ const allocVar = (scope, name, global = false, type = true) => {
2098
2150
 
2099
2151
  if (type) {
2100
2152
  let typeIdx = global ? globalInd++ : scope.localInd++;
2101
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2153
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32, name };
2102
2154
  }
2103
2155
 
2104
2156
  return idx;
@@ -2311,18 +2363,21 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2311
2363
  Opcodes.i32_to_u,
2312
2364
 
2313
2365
  // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2314
- ...number(ValtypeSize[valtype], Valtype.i32),
2366
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2315
2367
  [ Opcodes.i32_mul ],
2316
2368
  ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
2317
2369
  ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2318
2370
 
2319
2371
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2320
2372
  [ Opcodes.local_get, pointerTmp ],
2321
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2322
- ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2373
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2374
+ ], generate(scope, decl.right), [
2375
+ [ Opcodes.local_get, pointerTmp ],
2376
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
2377
+ ], getNodeType(scope, decl.right), false, name, true)),
2323
2378
  [ Opcodes.local_tee, newValueTmp ],
2324
2379
 
2325
- [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2380
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2326
2381
  ],
2327
2382
 
2328
2383
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
@@ -2499,6 +2554,7 @@ const generateUnary = (scope, decl) => {
2499
2554
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2500
2555
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2501
2556
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2557
+ [TYPES.symbol]: makeString(scope, 'symbol', false, '#typeof_result'),
2502
2558
 
2503
2559
  [TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2504
2560
 
@@ -2575,21 +2631,16 @@ const generateConditional = (scope, decl) => {
2575
2631
  out.push([ Opcodes.if, valtypeBinary ]);
2576
2632
  depth.push('if');
2577
2633
 
2578
- out.push(...generate(scope, decl.consequent));
2579
-
2580
- // note type
2581
2634
  out.push(
2582
- ...getNodeType(scope, decl.consequent),
2583
- ...setLastType(scope)
2635
+ ...generate(scope, decl.consequent),
2636
+ ...setLastType(scope, getNodeType(scope, decl.consequent))
2584
2637
  );
2585
2638
 
2586
2639
  out.push([ Opcodes.else ]);
2587
- out.push(...generate(scope, decl.alternate));
2588
2640
 
2589
- // note type
2590
2641
  out.push(
2591
- ...getNodeType(scope, decl.alternate),
2592
- ...setLastType(scope)
2642
+ ...generate(scope, decl.alternate),
2643
+ ...setLastType(scope, getNodeType(scope, decl.alternate))
2593
2644
  );
2594
2645
 
2595
2646
  out.push([ Opcodes.end ]);
@@ -2737,12 +2788,15 @@ const generateForOf = (scope, decl) => {
2737
2788
  // todo: optimize away counter and use end pointer
2738
2789
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2739
2790
  [TYPES.array]: [
2740
- ...setType(scope, leftName, TYPES.number),
2741
-
2742
2791
  [ Opcodes.loop, Blocktype.void ],
2743
2792
 
2744
2793
  [ Opcodes.local_get, pointer ],
2745
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2794
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2795
+
2796
+ ...setType(scope, leftName, [
2797
+ [ Opcodes.local_get, pointer ],
2798
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
2799
+ ]),
2746
2800
 
2747
2801
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2748
2802
 
@@ -2751,9 +2805,9 @@ const generateForOf = (scope, decl) => {
2751
2805
  ...generate(scope, decl.body),
2752
2806
  [ Opcodes.end ],
2753
2807
 
2754
- // increment iter pointer by valtype size
2808
+ // increment iter pointer by valtype size + 1
2755
2809
  [ Opcodes.local_get, pointer ],
2756
- ...number(ValtypeSize[valtype], Valtype.i32),
2810
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2757
2811
  [ Opcodes.i32_add ],
2758
2812
  [ Opcodes.local_set, pointer ],
2759
2813
 
@@ -3070,7 +3124,7 @@ const getAllocType = itemType => {
3070
3124
  }
3071
3125
  };
3072
3126
 
3073
- const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
3127
+ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype, typed = false) => {
3074
3128
  const out = [];
3075
3129
 
3076
3130
  scope.arrays ??= new Map();
@@ -3133,7 +3187,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3133
3187
 
3134
3188
  const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3135
3189
 
3136
- // store length as 0th array
3190
+ // store length
3137
3191
  out.push(
3138
3192
  ...pointerWasm,
3139
3193
  ...number(length, Valtype.i32),
@@ -3141,14 +3195,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3141
3195
  );
3142
3196
 
3143
3197
  const storeOp = StoreOps[itemType];
3144
-
3198
+ const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
3145
3199
  if (!initEmpty) for (let i = 0; i < length; i++) {
3146
3200
  if (elements[i] == null) continue;
3147
3201
 
3202
+ const offset = ValtypeSize.i32 + i * sizePerEl;
3148
3203
  out.push(
3149
3204
  ...pointerWasm,
3150
3205
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
3151
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3206
+ [ storeOp, 0, ...unsignedLEB128(offset) ],
3207
+ ...(!typed ? [] : [ // typed presumes !useRawElements
3208
+ ...pointerWasm,
3209
+ ...getNodeType(scope, elements[i]),
3210
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(offset + ValtypeSize[itemType]) ]
3211
+ ])
3152
3212
  );
3153
3213
  }
3154
3214
 
@@ -3158,6 +3218,65 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3158
3218
  return [ out, pointer ];
3159
3219
  };
3160
3220
 
3221
+ const storeArray = (scope, array, index, element, aotPointer = null) => {
3222
+ if (!Array.isArray(element)) element = generate(scope, element);
3223
+ if (typeof index === 'number') index = number(index);
3224
+
3225
+ const offset = localTmp(scope, '#storeArray_offset', Valtype.i32);
3226
+
3227
+ return [
3228
+ // calculate offset
3229
+ ...index,
3230
+ Opcodes.i32_to_u,
3231
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3232
+ [ Opcodes.i32_mul ],
3233
+ ...(aotPointer ? [] : [
3234
+ ...array,
3235
+ Opcodes.i32_to_u,
3236
+ [ Opcodes.i32_add ],
3237
+ ]),
3238
+ [ Opcodes.local_set, offset ],
3239
+
3240
+ // store value
3241
+ [ Opcodes.local_get, offset ],
3242
+ ...generate(scope, element),
3243
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3244
+
3245
+ // store type
3246
+ [ Opcodes.local_get, offset ],
3247
+ ...getNodeType(scope, element),
3248
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3249
+ ];
3250
+ };
3251
+
3252
+ const loadArray = (scope, array, index, aotPointer = null) => {
3253
+ if (typeof index === 'number') index = number(index);
3254
+
3255
+ const offset = localTmp(scope, '#loadArray_offset', Valtype.i32);
3256
+
3257
+ return [
3258
+ // calculate offset
3259
+ ...index,
3260
+ Opcodes.i32_to_u,
3261
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3262
+ [ Opcodes.i32_mul ],
3263
+ ...(aotPointer ? [] : [
3264
+ ...array,
3265
+ Opcodes.i32_to_u,
3266
+ [ Opcodes.i32_add ],
3267
+ ]),
3268
+ [ Opcodes.local_set, offset ],
3269
+
3270
+ // load value
3271
+ [ Opcodes.local_get, offset ],
3272
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3273
+
3274
+ // load type
3275
+ [ Opcodes.local_get, offset ],
3276
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3277
+ ];
3278
+ };
3279
+
3161
3280
  const byteStringable = str => {
3162
3281
  if (!Prefs.bytestring) return false;
3163
3282
 
@@ -3186,14 +3305,28 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
3186
3305
  };
3187
3306
 
3188
3307
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
3189
- return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
3308
+ return makeArray(scope, decl, global, name, initEmpty, valtype, true)[0];
3309
+ };
3310
+
3311
+ const generateObject = (scope, decl, global = false, name = '$undeclared') => {
3312
+ if (decl.properties.length > 0) return todo(scope, 'objects are not supported yet', true);
3313
+
3314
+ return [
3315
+ ...number(1),
3316
+ ...setLastType(scope, TYPES.object)
3317
+ ];
3190
3318
  };
3191
3319
 
3192
- export const generateMember = (scope, decl, _global, _name) => {
3320
+ const withType = (scope, wasm, type) => [
3321
+ ...wasm,
3322
+ ...setLastType(scope, type)
3323
+ ];
3324
+
3325
+ const generateMember = (scope, decl, _global, _name) => {
3193
3326
  const name = decl.object.name;
3194
3327
  const pointer = scope.arrays?.get(name);
3195
3328
 
3196
- const aotPointer = Prefs.aotPointerOpt && pointer != null;
3329
+ const aotPointer = Prefs.aotPointerOpt && pointer;
3197
3330
 
3198
3331
  // hack: .name
3199
3332
  if (decl.property.name === 'name') {
@@ -3203,9 +3336,9 @@ export const generateMember = (scope, decl, _global, _name) => {
3203
3336
  // eg: __String_prototype_toLowerCase -> toLowerCase
3204
3337
  if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
3205
3338
 
3206
- return makeString(scope, nameProp, _global, _name, true);
3339
+ return withType(scope, makeString(scope, nameProp, _global, _name, true), TYPES.bytestring);
3207
3340
  } else {
3208
- return generate(scope, DEFAULT_VALUE);
3341
+ return withType(scope, number(0), TYPES.undefined);
3209
3342
  }
3210
3343
  }
3211
3344
 
@@ -3215,7 +3348,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3215
3348
  if (func) {
3216
3349
  const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3217
3350
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3218
- return number(typedParams ? func.params.length / 2 : func.params.length);
3351
+ return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3219
3352
  }
3220
3353
 
3221
3354
  if (builtinFuncs[name + '$constructor']) {
@@ -3225,24 +3358,88 @@ export const generateMember = (scope, decl, _global, _name) => {
3225
3358
  const constructorFunc = builtinFuncs[name + '$constructor'];
3226
3359
  const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3227
3360
 
3228
- return number(Math.max(regularParams, constructorParams));
3361
+ return withType(scope, number(Math.max(regularParams, constructorParams)), TYPES.number);
3362
+ }
3363
+
3364
+ if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
3365
+ if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params), TYPES.number);
3366
+ if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
3367
+
3368
+ if (Prefs.fastLength) {
3369
+ // presume valid length object
3370
+ return [
3371
+ ...(aotPointer ? number(0, Valtype.i32) : [
3372
+ ...generate(scope, decl.object),
3373
+ Opcodes.i32_to_u
3374
+ ]),
3375
+
3376
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3377
+ Opcodes.i32_from_u
3378
+ ];
3229
3379
  }
3230
3380
 
3231
- if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3232
- if (importedFuncs[name]) return number(importedFuncs[name].params);
3233
- if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3381
+ const type = getNodeType(scope, decl.object);
3382
+ const known = knownType(scope, type);
3383
+ if (known != null) {
3384
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
3385
+ ...(aotPointer ? number(0, Valtype.i32) : [
3386
+ ...generate(scope, decl.object),
3387
+ Opcodes.i32_to_u
3388
+ ]),
3389
+
3390
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3391
+ Opcodes.i32_from_u
3392
+ ];
3393
+
3394
+ return number(0);
3395
+ }
3234
3396
 
3235
3397
  return [
3236
- ...(aotPointer ? number(0, Valtype.i32) : [
3237
- ...generate(scope, decl.object),
3238
- Opcodes.i32_to_u
3239
- ]),
3398
+ ...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
3399
+ [ Opcodes.if, valtypeBinary ],
3400
+ ...(aotPointer ? number(0, Valtype.i32) : [
3401
+ ...generate(scope, decl.object),
3402
+ Opcodes.i32_to_u
3403
+ ]),
3240
3404
 
3241
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128((aotPointer ? pointer : 0)) ],
3242
- Opcodes.i32_from_u
3405
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3406
+ Opcodes.i32_from_u,
3407
+
3408
+ ...setLastType(scope, TYPES.number),
3409
+ [ Opcodes.else ],
3410
+ ...number(0),
3411
+ ...setLastType(scope, TYPES.undefined),
3412
+ [ Opcodes.end ]
3243
3413
  ];
3244
3414
  }
3245
3415
 
3416
+ // todo: generate this array procedurally during builtinFuncs creation
3417
+ if (['size', 'description'].includes(decl.property.name)) {
3418
+ const bc = {};
3419
+ const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3420
+
3421
+ if (cands.length > 0) {
3422
+ for (const x of cands) {
3423
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
3424
+ if (type == null) continue;
3425
+
3426
+ bc[type] = generateCall(scope, {
3427
+ callee: {
3428
+ type: 'Identifier',
3429
+ name: x
3430
+ },
3431
+ arguments: [ decl.object ],
3432
+ _protoInternalCall: true
3433
+ });
3434
+ }
3435
+ }
3436
+
3437
+ return typeSwitch(scope, getNodeType(scope, decl.object), {
3438
+ ...bc,
3439
+ default: withType(scope, number(0), TYPES.undefined)
3440
+ }, valtypeBinary);
3441
+ }
3442
+
3246
3443
  const object = generate(scope, decl.object);
3247
3444
  const property = generate(scope, decl.property);
3248
3445
 
@@ -3257,24 +3454,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3257
3454
 
3258
3455
  return typeSwitch(scope, getNodeType(scope, decl.object), {
3259
3456
  [TYPES.array]: [
3260
- // get index as valtype
3261
- ...property,
3262
-
3263
- // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
3264
- Opcodes.i32_to_u,
3265
- ...number(ValtypeSize[valtype], Valtype.i32),
3266
- [ Opcodes.i32_mul ],
3267
-
3268
- ...(aotPointer ? [] : [
3269
- ...object,
3270
- Opcodes.i32_to_u,
3271
- [ Opcodes.i32_add ]
3272
- ]),
3273
-
3274
- // read from memory
3275
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3276
-
3277
- ...number(TYPES.number, Valtype.i32),
3457
+ ...loadArray(scope, object, property, aotPointer),
3278
3458
  ...setLastType(scope)
3279
3459
  ],
3280
3460
 
@@ -3305,9 +3485,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3305
3485
 
3306
3486
  // return new string (page)
3307
3487
  ...number(newPointer),
3308
-
3309
- ...number(TYPES.string, Valtype.i32),
3310
- ...setLastType(scope)
3488
+ ...setLastType(scope, TYPES.string)
3311
3489
  ],
3312
3490
  [TYPES.bytestring]: [
3313
3491
  // setup new/out array
@@ -3333,9 +3511,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3333
3511
 
3334
3512
  // return new string (page)
3335
3513
  ...number(newPointer),
3336
-
3337
- ...number(TYPES.bytestring, Valtype.i32),
3338
- ...setLastType(scope)
3514
+ ...setLastType(scope, TYPES.bytestring)
3339
3515
  ],
3340
3516
 
3341
3517
  default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
@@ -3360,7 +3536,7 @@ const objectHack = node => {
3360
3536
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3361
3537
 
3362
3538
  // if .name or .length, give up (hack within a hack!)
3363
- if (['name', 'length'].includes(node.property.name)) {
3539
+ if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
3364
3540
  node.object = objectHack(node.object);
3365
3541
  return;
3366
3542
  }
@@ -3628,8 +3804,10 @@ const internalConstrs = {
3628
3804
  }),
3629
3805
 
3630
3806
  // print space
3631
- ...number(32),
3632
- [ Opcodes.call, importedFuncs.printChar ]
3807
+ ...(i !== decl.arguments.length - 1 ? [
3808
+ ...number(32),
3809
+ [ Opcodes.call, importedFuncs.printChar ]
3810
+ ] : [])
3633
3811
  );
3634
3812
  }
3635
3813
 
@@ -3639,6 +3817,8 @@ const internalConstrs = {
3639
3817
  [ Opcodes.call, importedFuncs.printChar ]
3640
3818
  );
3641
3819
 
3820
+ out.push(...number(UNDEFINED));
3821
+
3642
3822
  return out;
3643
3823
  },
3644
3824
  type: TYPES.undefined,