porffor 0.14.0-a24ac8cf2 → 0.14.0-af9ac5ad4

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 = [], callsSelf = false }) => {
1057
1067
  const existing = funcs.find(x => x.name === name);
1058
1068
  if (existing) return existing;
1059
1069
 
@@ -1101,6 +1111,12 @@ 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
+
1104
1120
  funcs.push(func);
1105
1121
  funcIndex[name] = func.index;
1106
1122
 
@@ -1194,9 +1210,10 @@ const getLastType = scope => {
1194
1210
  return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1195
1211
  };
1196
1212
 
1197
- const setLastType = scope => {
1198
- return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1199
- };
1213
+ const setLastType = (scope, type = []) => [
1214
+ ...(typeof type === 'number' ? number(type, Valtype.i32) : type),
1215
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1216
+ ];
1200
1217
 
1201
1218
  const getNodeType = (scope, node) => {
1202
1219
  const ret = (() => {
@@ -1257,7 +1274,17 @@ const getNodeType = (scope, node) => {
1257
1274
 
1258
1275
  const func = spl[spl.length - 1];
1259
1276
  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;
1277
+ if (protoFuncs.length === 1) {
1278
+ if (protoFuncs[0].returnType) return protoFuncs[0].returnType;
1279
+ }
1280
+
1281
+ if (protoFuncs.length > 0) {
1282
+ if (scope.locals['#last_type']) return getLastType(scope);
1283
+
1284
+ // presume
1285
+ // todo: warn here?
1286
+ return TYPES.number;
1287
+ }
1261
1288
  }
1262
1289
 
1263
1290
  if (name.startsWith('__Porffor_wasm_')) {
@@ -1352,22 +1379,27 @@ const getNodeType = (scope, node) => {
1352
1379
  }
1353
1380
 
1354
1381
  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
- }
1382
+ const name = node.property.name;
1383
+
1384
+ if (name === 'length') {
1385
+ if (hasFuncWithName(node.object.name)) return TYPES.number;
1386
+ if (Prefs.fastLength) return TYPES.number;
1362
1387
  }
1363
1388
 
1364
- // hack: if something.length, number type
1365
- if (node.property.name === 'length') return TYPES.number;
1366
1389
 
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;
1390
+ const objectKnownType = knownType(scope, getNodeType(scope, node.object));
1391
+ if (objectKnownType != null) {
1392
+ if (name === 'length') {
1393
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(objectKnownType)) return TYPES.number;
1394
+ else return TYPES.undefined;
1395
+ }
1396
+
1397
+ if (node.computed) {
1398
+ if (objectKnownType === TYPES.string) return TYPES.string;
1399
+ if (objectKnownType === TYPES.bytestring) return TYPES.bytestring;
1400
+ if (objectKnownType === TYPES.array) return TYPES.number;
1401
+ }
1402
+ }
1371
1403
 
1372
1404
  if (scope.locals['#last_type']) return getLastType(scope);
1373
1405
 
@@ -1470,7 +1502,7 @@ const disposeLeftover = wasm => {
1470
1502
  const generateExp = (scope, decl) => {
1471
1503
  const expression = decl.expression;
1472
1504
 
1473
- const out = generate(scope, expression, undefined, undefined, true);
1505
+ const out = generate(scope, expression, undefined, undefined, Prefs.optUnused);
1474
1506
  disposeLeftover(out);
1475
1507
 
1476
1508
  return out;
@@ -1561,16 +1593,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1561
1593
  out.splice(out.length - 1, 1);
1562
1594
 
1563
1595
  const finalStatement = parsed.body[parsed.body.length - 1];
1564
- out.push(
1565
- ...getNodeType(scope, finalStatement),
1566
- ...setLastType(scope)
1567
- );
1596
+ out.push(...setLastType(scope, getNodeType(scope, finalStatement)));
1568
1597
  } else if (countLeftover(out) === 0) {
1569
1598
  out.push(...number(UNDEFINED));
1570
- out.push(
1571
- ...number(TYPES.undefined, Valtype.i32),
1572
- ...setLastType(scope)
1573
- );
1599
+ out.push(...setLastType(scope, TYPES.undefined));
1574
1600
  }
1575
1601
 
1576
1602
  // if (lastInst && lastInst[0] === Opcodes.drop) {
@@ -1622,8 +1648,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1622
1648
  [ Opcodes.call, idx ],
1623
1649
  Opcodes.i32_from_u,
1624
1650
 
1625
- ...number(TYPES.boolean, Valtype.i32),
1626
- ...setLastType(scope)
1651
+ ...setLastType(scope, TYPES.boolean)
1627
1652
  ];
1628
1653
  }
1629
1654
 
@@ -1695,9 +1720,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1695
1720
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
1696
1721
  protoBC[x] = [
1697
1722
  ...RTArrayUtil.getLength(getPointer),
1698
-
1699
- ...number(TYPES.number, Valtype.i32),
1700
- ...setLastType(scope)
1723
+ ...setLastType(scope, TYPES.number)
1701
1724
  ];
1702
1725
  continue;
1703
1726
  }
@@ -1716,7 +1739,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1716
1739
  getI32: () => RTArrayUtil.getLengthI32(getPointer),
1717
1740
  set: value => RTArrayUtil.setLength(getPointer, value),
1718
1741
  setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
1719
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1742
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), getNodeType(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1720
1743
  return makeArray(scope, {
1721
1744
  rawElements: new Array(length)
1722
1745
  }, _global, _name, true, itemType);
@@ -1730,9 +1753,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1730
1753
  protoBC[x] = [
1731
1754
  [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1732
1755
  ...protoOut,
1733
-
1734
- ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1735
- ...setLastType(scope),
1756
+ ...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
1736
1757
  [ Opcodes.end ]
1737
1758
  ];
1738
1759
  }
@@ -1901,6 +1922,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1901
1922
  const arg = args[i];
1902
1923
  out = out.concat(generate(scope, arg));
1903
1924
 
1925
+ // todo: this should be used instead of the too many args thing above (by removing that)
1926
+ if (i >= paramCount) {
1927
+ // over param count of func, drop arg
1928
+ out.push([ Opcodes.drop ]);
1929
+ continue;
1930
+ }
1931
+
1904
1932
  if (valtypeBinary !== Valtype.i32 && (
1905
1933
  (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1906
1934
  (importedFuncs[name] && name.startsWith('profile'))
@@ -1949,6 +1977,11 @@ const generateNew = (scope, decl, _global, _name) => {
1949
1977
  }, _global, _name);
1950
1978
  }
1951
1979
 
1980
+ if (
1981
+ (builtinFuncs[name] && !builtinFuncs[name].constr) ||
1982
+ (internalConstrs[name] && builtinFuncs[name].notConstr)
1983
+ ) return internalThrow(scope, 'TypeError', `${name} is not a constructor`);
1984
+
1952
1985
  if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1953
1986
 
1954
1987
  return generateCall(scope, decl, _global, _name);
@@ -1974,8 +2007,11 @@ const knownType = (scope, type) => {
1974
2007
  const idx = type[0][1];
1975
2008
 
1976
2009
  // type idx = var idx + 1
1977
- const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
1978
- if (v.metadata?.type != null) return v.metadata.type;
2010
+ const name = Object.values(scope.locals).find(x => x.idx === idx)?.name;
2011
+ if (name) {
2012
+ const local = scope.locals[name];
2013
+ if (local.metadata?.type != null) return v.metadata.type;
2014
+ }
1979
2015
  }
1980
2016
 
1981
2017
  return null;
@@ -2106,6 +2142,17 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2106
2142
  return out;
2107
2143
  };
2108
2144
 
2145
+ const typeIsOneOf = (type, types, valtype = Valtype.i32) => {
2146
+ const out = [];
2147
+
2148
+ for (let i = 0; i < types.length; i++) {
2149
+ out.push(...type, ...number(types[i], valtype), valtype === Valtype.f64 ? [ Opcodes.f64_eq ] : [ Opcodes.i32_eq ]);
2150
+ if (i !== 0) out.push([ Opcodes.i32_or ]);
2151
+ }
2152
+
2153
+ return out;
2154
+ };
2155
+
2109
2156
  const allocVar = (scope, name, global = false, type = true) => {
2110
2157
  const target = global ? globals : scope.locals;
2111
2158
 
@@ -2122,7 +2169,7 @@ const allocVar = (scope, name, global = false, type = true) => {
2122
2169
 
2123
2170
  if (type) {
2124
2171
  let typeIdx = global ? globalInd++ : scope.localInd++;
2125
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2172
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32, name };
2126
2173
  }
2127
2174
 
2128
2175
  return idx;
@@ -2335,18 +2382,21 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2335
2382
  Opcodes.i32_to_u,
2336
2383
 
2337
2384
  // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2338
- ...number(ValtypeSize[valtype], Valtype.i32),
2385
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2339
2386
  [ Opcodes.i32_mul ],
2340
2387
  ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
2341
2388
  ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2342
2389
 
2343
2390
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2344
2391
  [ Opcodes.local_get, pointerTmp ],
2345
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2346
- ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2392
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2393
+ ], generate(scope, decl.right), [
2394
+ [ Opcodes.local_get, pointerTmp ],
2395
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
2396
+ ], getNodeType(scope, decl.right), false, name, true)),
2347
2397
  [ Opcodes.local_tee, newValueTmp ],
2348
2398
 
2349
- [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2399
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2350
2400
  ],
2351
2401
 
2352
2402
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
@@ -2523,6 +2573,7 @@ const generateUnary = (scope, decl) => {
2523
2573
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2524
2574
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2525
2575
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2576
+ [TYPES.symbol]: makeString(scope, 'symbol', false, '#typeof_result'),
2526
2577
 
2527
2578
  [TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2528
2579
 
@@ -2599,21 +2650,16 @@ const generateConditional = (scope, decl) => {
2599
2650
  out.push([ Opcodes.if, valtypeBinary ]);
2600
2651
  depth.push('if');
2601
2652
 
2602
- out.push(...generate(scope, decl.consequent));
2603
-
2604
- // note type
2605
2653
  out.push(
2606
- ...getNodeType(scope, decl.consequent),
2607
- ...setLastType(scope)
2654
+ ...generate(scope, decl.consequent),
2655
+ ...setLastType(scope, getNodeType(scope, decl.consequent))
2608
2656
  );
2609
2657
 
2610
2658
  out.push([ Opcodes.else ]);
2611
- out.push(...generate(scope, decl.alternate));
2612
2659
 
2613
- // note type
2614
2660
  out.push(
2615
- ...getNodeType(scope, decl.alternate),
2616
- ...setLastType(scope)
2661
+ ...generate(scope, decl.alternate),
2662
+ ...setLastType(scope, getNodeType(scope, decl.alternate))
2617
2663
  );
2618
2664
 
2619
2665
  out.push([ Opcodes.end ]);
@@ -2761,12 +2807,15 @@ const generateForOf = (scope, decl) => {
2761
2807
  // todo: optimize away counter and use end pointer
2762
2808
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2763
2809
  [TYPES.array]: [
2764
- ...setType(scope, leftName, TYPES.number),
2765
-
2766
2810
  [ Opcodes.loop, Blocktype.void ],
2767
2811
 
2768
2812
  [ Opcodes.local_get, pointer ],
2769
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2813
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2814
+
2815
+ ...setType(scope, leftName, [
2816
+ [ Opcodes.local_get, pointer ],
2817
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
2818
+ ]),
2770
2819
 
2771
2820
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2772
2821
 
@@ -2775,9 +2824,9 @@ const generateForOf = (scope, decl) => {
2775
2824
  ...generate(scope, decl.body),
2776
2825
  [ Opcodes.end ],
2777
2826
 
2778
- // increment iter pointer by valtype size
2827
+ // increment iter pointer by valtype size + 1
2779
2828
  [ Opcodes.local_get, pointer ],
2780
- ...number(ValtypeSize[valtype], Valtype.i32),
2829
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2781
2830
  [ Opcodes.i32_add ],
2782
2831
  [ Opcodes.local_set, pointer ],
2783
2832
 
@@ -3094,7 +3143,7 @@ const getAllocType = itemType => {
3094
3143
  }
3095
3144
  };
3096
3145
 
3097
- const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
3146
+ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype, typed = false) => {
3098
3147
  const out = [];
3099
3148
 
3100
3149
  scope.arrays ??= new Map();
@@ -3157,7 +3206,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3157
3206
 
3158
3207
  const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3159
3208
 
3160
- // store length as 0th array
3209
+ // store length
3161
3210
  out.push(
3162
3211
  ...pointerWasm,
3163
3212
  ...number(length, Valtype.i32),
@@ -3165,14 +3214,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3165
3214
  );
3166
3215
 
3167
3216
  const storeOp = StoreOps[itemType];
3168
-
3217
+ const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
3169
3218
  if (!initEmpty) for (let i = 0; i < length; i++) {
3170
3219
  if (elements[i] == null) continue;
3171
3220
 
3221
+ const offset = ValtypeSize.i32 + i * sizePerEl;
3172
3222
  out.push(
3173
3223
  ...pointerWasm,
3174
3224
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
3175
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3225
+ [ storeOp, 0, ...unsignedLEB128(offset) ],
3226
+ ...(!typed ? [] : [ // typed presumes !useRawElements
3227
+ ...pointerWasm,
3228
+ ...getNodeType(scope, elements[i]),
3229
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(offset + ValtypeSize[itemType]) ]
3230
+ ])
3176
3231
  );
3177
3232
  }
3178
3233
 
@@ -3182,6 +3237,65 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3182
3237
  return [ out, pointer ];
3183
3238
  };
3184
3239
 
3240
+ const storeArray = (scope, array, index, element, aotPointer = null) => {
3241
+ if (!Array.isArray(element)) element = generate(scope, element);
3242
+ if (typeof index === 'number') index = number(index);
3243
+
3244
+ const offset = localTmp(scope, '#storeArray_offset', Valtype.i32);
3245
+
3246
+ return [
3247
+ // calculate offset
3248
+ ...index,
3249
+ Opcodes.i32_to_u,
3250
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3251
+ [ Opcodes.i32_mul ],
3252
+ ...(aotPointer ? [] : [
3253
+ ...array,
3254
+ Opcodes.i32_to_u,
3255
+ [ Opcodes.i32_add ],
3256
+ ]),
3257
+ [ Opcodes.local_set, offset ],
3258
+
3259
+ // store value
3260
+ [ Opcodes.local_get, offset ],
3261
+ ...generate(scope, element),
3262
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3263
+
3264
+ // store type
3265
+ [ Opcodes.local_get, offset ],
3266
+ ...getNodeType(scope, element),
3267
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3268
+ ];
3269
+ };
3270
+
3271
+ const loadArray = (scope, array, index, aotPointer = null) => {
3272
+ if (typeof index === 'number') index = number(index);
3273
+
3274
+ const offset = localTmp(scope, '#loadArray_offset', Valtype.i32);
3275
+
3276
+ return [
3277
+ // calculate offset
3278
+ ...index,
3279
+ Opcodes.i32_to_u,
3280
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3281
+ [ Opcodes.i32_mul ],
3282
+ ...(aotPointer ? [] : [
3283
+ ...array,
3284
+ Opcodes.i32_to_u,
3285
+ [ Opcodes.i32_add ],
3286
+ ]),
3287
+ [ Opcodes.local_set, offset ],
3288
+
3289
+ // load value
3290
+ [ Opcodes.local_get, offset ],
3291
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3292
+
3293
+ // load type
3294
+ [ Opcodes.local_get, offset ],
3295
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3296
+ ];
3297
+ };
3298
+
3185
3299
  const byteStringable = str => {
3186
3300
  if (!Prefs.bytestring) return false;
3187
3301
 
@@ -3210,14 +3324,28 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
3210
3324
  };
3211
3325
 
3212
3326
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
3213
- return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
3327
+ return makeArray(scope, decl, global, name, initEmpty, valtype, true)[0];
3214
3328
  };
3215
3329
 
3216
- export const generateMember = (scope, decl, _global, _name) => {
3330
+ const generateObject = (scope, decl, global = false, name = '$undeclared') => {
3331
+ if (decl.properties.length > 0) return todo(scope, 'objects are not supported yet', true);
3332
+
3333
+ return [
3334
+ ...number(1),
3335
+ ...setLastType(scope, TYPES.object)
3336
+ ];
3337
+ };
3338
+
3339
+ const withType = (scope, wasm, type) => [
3340
+ ...wasm,
3341
+ ...setLastType(scope, type)
3342
+ ];
3343
+
3344
+ const generateMember = (scope, decl, _global, _name) => {
3217
3345
  const name = decl.object.name;
3218
3346
  const pointer = scope.arrays?.get(name);
3219
3347
 
3220
- const aotPointer = Prefs.aotPointerOpt && pointer != null;
3348
+ const aotPointer = Prefs.aotPointerOpt && pointer;
3221
3349
 
3222
3350
  // hack: .name
3223
3351
  if (decl.property.name === 'name') {
@@ -3227,9 +3355,9 @@ export const generateMember = (scope, decl, _global, _name) => {
3227
3355
  // eg: __String_prototype_toLowerCase -> toLowerCase
3228
3356
  if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
3229
3357
 
3230
- return makeString(scope, nameProp, _global, _name, true);
3358
+ return withType(scope, makeString(scope, nameProp, _global, _name, true), TYPES.bytestring);
3231
3359
  } else {
3232
- return generate(scope, DEFAULT_VALUE);
3360
+ return withType(scope, number(0), TYPES.undefined);
3233
3361
  }
3234
3362
  }
3235
3363
 
@@ -3239,7 +3367,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3239
3367
  if (func) {
3240
3368
  const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3241
3369
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3242
- return number(typedParams ? func.params.length / 2 : func.params.length);
3370
+ return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3243
3371
  }
3244
3372
 
3245
3373
  if (builtinFuncs[name + '$constructor']) {
@@ -3249,24 +3377,88 @@ export const generateMember = (scope, decl, _global, _name) => {
3249
3377
  const constructorFunc = builtinFuncs[name + '$constructor'];
3250
3378
  const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3251
3379
 
3252
- return number(Math.max(regularParams, constructorParams));
3380
+ return withType(scope, number(Math.max(regularParams, constructorParams)), TYPES.number);
3253
3381
  }
3254
3382
 
3255
- if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3256
- if (importedFuncs[name]) return number(importedFuncs[name].params);
3257
- if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3383
+ if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
3384
+ if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params), TYPES.number);
3385
+ if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
3386
+
3387
+ if (Prefs.fastLength) {
3388
+ // presume valid length object
3389
+ return [
3390
+ ...(aotPointer ? number(0, Valtype.i32) : [
3391
+ ...generate(scope, decl.object),
3392
+ Opcodes.i32_to_u
3393
+ ]),
3394
+
3395
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3396
+ Opcodes.i32_from_u
3397
+ ];
3398
+ }
3399
+
3400
+ const type = getNodeType(scope, decl.object);
3401
+ const known = knownType(scope, type);
3402
+ if (known != null) {
3403
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
3404
+ ...(aotPointer ? number(0, Valtype.i32) : [
3405
+ ...generate(scope, decl.object),
3406
+ Opcodes.i32_to_u
3407
+ ]),
3408
+
3409
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3410
+ Opcodes.i32_from_u
3411
+ ];
3412
+
3413
+ return number(0);
3414
+ }
3258
3415
 
3259
3416
  return [
3260
- ...(aotPointer ? number(0, Valtype.i32) : [
3261
- ...generate(scope, decl.object),
3262
- Opcodes.i32_to_u
3263
- ]),
3417
+ ...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
3418
+ [ Opcodes.if, valtypeBinary ],
3419
+ ...(aotPointer ? number(0, Valtype.i32) : [
3420
+ ...generate(scope, decl.object),
3421
+ Opcodes.i32_to_u
3422
+ ]),
3264
3423
 
3265
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128((aotPointer ? pointer : 0)) ],
3266
- Opcodes.i32_from_u
3424
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3425
+ Opcodes.i32_from_u,
3426
+
3427
+ ...setLastType(scope, TYPES.number),
3428
+ [ Opcodes.else ],
3429
+ ...number(0),
3430
+ ...setLastType(scope, TYPES.undefined),
3431
+ [ Opcodes.end ]
3267
3432
  ];
3268
3433
  }
3269
3434
 
3435
+ // todo: generate this array procedurally during builtinFuncs creation
3436
+ if (['size', 'description'].includes(decl.property.name)) {
3437
+ const bc = {};
3438
+ const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3439
+
3440
+ if (cands.length > 0) {
3441
+ for (const x of cands) {
3442
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
3443
+ if (type == null) continue;
3444
+
3445
+ bc[type] = generateCall(scope, {
3446
+ callee: {
3447
+ type: 'Identifier',
3448
+ name: x
3449
+ },
3450
+ arguments: [ decl.object ],
3451
+ _protoInternalCall: true
3452
+ });
3453
+ }
3454
+ }
3455
+
3456
+ return typeSwitch(scope, getNodeType(scope, decl.object), {
3457
+ ...bc,
3458
+ default: withType(scope, number(0), TYPES.undefined)
3459
+ }, valtypeBinary);
3460
+ }
3461
+
3270
3462
  const object = generate(scope, decl.object);
3271
3463
  const property = generate(scope, decl.property);
3272
3464
 
@@ -3281,24 +3473,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3281
3473
 
3282
3474
  return typeSwitch(scope, getNodeType(scope, decl.object), {
3283
3475
  [TYPES.array]: [
3284
- // get index as valtype
3285
- ...property,
3286
-
3287
- // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
3288
- Opcodes.i32_to_u,
3289
- ...number(ValtypeSize[valtype], Valtype.i32),
3290
- [ Opcodes.i32_mul ],
3291
-
3292
- ...(aotPointer ? [] : [
3293
- ...object,
3294
- Opcodes.i32_to_u,
3295
- [ Opcodes.i32_add ]
3296
- ]),
3297
-
3298
- // read from memory
3299
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3300
-
3301
- ...number(TYPES.number, Valtype.i32),
3476
+ ...loadArray(scope, object, property, aotPointer),
3302
3477
  ...setLastType(scope)
3303
3478
  ],
3304
3479
 
@@ -3329,9 +3504,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3329
3504
 
3330
3505
  // return new string (page)
3331
3506
  ...number(newPointer),
3332
-
3333
- ...number(TYPES.string, Valtype.i32),
3334
- ...setLastType(scope)
3507
+ ...setLastType(scope, TYPES.string)
3335
3508
  ],
3336
3509
  [TYPES.bytestring]: [
3337
3510
  // setup new/out array
@@ -3357,9 +3530,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3357
3530
 
3358
3531
  // return new string (page)
3359
3532
  ...number(newPointer),
3360
-
3361
- ...number(TYPES.bytestring, Valtype.i32),
3362
- ...setLastType(scope)
3533
+ ...setLastType(scope, TYPES.bytestring)
3363
3534
  ],
3364
3535
 
3365
3536
  default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
@@ -3384,7 +3555,7 @@ const objectHack = node => {
3384
3555
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3385
3556
 
3386
3557
  // if .name or .length, give up (hack within a hack!)
3387
- if (['name', 'length'].includes(node.property.name)) {
3558
+ if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
3388
3559
  node.object = objectHack(node.object);
3389
3560
  return;
3390
3561
  }