porffor 0.14.0-3ba07b862 → 0.14.0-3fa90b5d2

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 ],
@@ -1053,7 +1044,7 @@ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals =
1053
1044
  });
1054
1045
  };
1055
1046
 
1056
- 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 }) => {
1057
1048
  const existing = funcs.find(x => x.name === name);
1058
1049
  if (existing) return existing;
1059
1050
 
@@ -1101,6 +1092,12 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1101
1092
  index: currentFuncIndex++
1102
1093
  };
1103
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
+
1104
1101
  funcs.push(func);
1105
1102
  funcIndex[name] = func.index;
1106
1103
 
@@ -1194,9 +1191,10 @@ const getLastType = scope => {
1194
1191
  return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1195
1192
  };
1196
1193
 
1197
- const setLastType = scope => {
1198
- return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1199
- };
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
+ ];
1200
1198
 
1201
1199
  const getNodeType = (scope, node) => {
1202
1200
  const ret = (() => {
@@ -1257,7 +1255,17 @@ const getNodeType = (scope, node) => {
1257
1255
 
1258
1256
  const func = spl[spl.length - 1];
1259
1257
  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;
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
+ }
1261
1269
  }
1262
1270
 
1263
1271
  if (name.startsWith('__Porffor_wasm_')) {
@@ -1352,22 +1360,27 @@ const getNodeType = (scope, node) => {
1352
1360
  }
1353
1361
 
1354
1362
  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
- }
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;
1362
1368
  }
1363
1369
 
1364
- // hack: if something.length, number type
1365
- if (node.property.name === 'length') return TYPES.number;
1366
1370
 
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;
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
+ }
1371
1384
 
1372
1385
  if (scope.locals['#last_type']) return getLastType(scope);
1373
1386
 
@@ -1470,7 +1483,7 @@ const disposeLeftover = wasm => {
1470
1483
  const generateExp = (scope, decl) => {
1471
1484
  const expression = decl.expression;
1472
1485
 
1473
- const out = generate(scope, expression, undefined, undefined, true);
1486
+ const out = generate(scope, expression, undefined, undefined, Prefs.optUnused);
1474
1487
  disposeLeftover(out);
1475
1488
 
1476
1489
  return out;
@@ -1561,16 +1574,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1561
1574
  out.splice(out.length - 1, 1);
1562
1575
 
1563
1576
  const finalStatement = parsed.body[parsed.body.length - 1];
1564
- out.push(
1565
- ...getNodeType(scope, finalStatement),
1566
- ...setLastType(scope)
1567
- );
1577
+ out.push(...setLastType(scope, getNodeType(scope, finalStatement)));
1568
1578
  } else if (countLeftover(out) === 0) {
1569
1579
  out.push(...number(UNDEFINED));
1570
- out.push(
1571
- ...number(TYPES.undefined, Valtype.i32),
1572
- ...setLastType(scope)
1573
- );
1580
+ out.push(...setLastType(scope, TYPES.undefined));
1574
1581
  }
1575
1582
 
1576
1583
  // if (lastInst && lastInst[0] === Opcodes.drop) {
@@ -1622,8 +1629,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1622
1629
  [ Opcodes.call, idx ],
1623
1630
  Opcodes.i32_from_u,
1624
1631
 
1625
- ...number(TYPES.boolean, Valtype.i32),
1626
- ...setLastType(scope)
1632
+ ...setLastType(scope, TYPES.boolean)
1627
1633
  ];
1628
1634
  }
1629
1635
 
@@ -1695,9 +1701,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1695
1701
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
1696
1702
  protoBC[x] = [
1697
1703
  ...RTArrayUtil.getLength(getPointer),
1698
-
1699
- ...number(TYPES.number, Valtype.i32),
1700
- ...setLastType(scope)
1704
+ ...setLastType(scope, TYPES.number)
1701
1705
  ];
1702
1706
  continue;
1703
1707
  }
@@ -1716,7 +1720,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1716
1720
  getI32: () => RTArrayUtil.getLengthI32(getPointer),
1717
1721
  set: value => RTArrayUtil.setLength(getPointer, value),
1718
1722
  setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
1719
- }, 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) => {
1720
1724
  return makeArray(scope, {
1721
1725
  rawElements: new Array(length)
1722
1726
  }, _global, _name, true, itemType);
@@ -1730,9 +1734,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1730
1734
  protoBC[x] = [
1731
1735
  [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1732
1736
  ...protoOut,
1733
-
1734
- ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1735
- ...setLastType(scope),
1737
+ ...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
1736
1738
  [ Opcodes.end ]
1737
1739
  ];
1738
1740
  }
@@ -1901,6 +1903,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1901
1903
  const arg = args[i];
1902
1904
  out = out.concat(generate(scope, arg));
1903
1905
 
1906
+ // todo: this should be used instead of the too many args thing above (by removing that)
1904
1907
  if (i >= paramCount) {
1905
1908
  // over param count of func, drop arg
1906
1909
  out.push([ Opcodes.drop ]);
@@ -1985,8 +1988,11 @@ const knownType = (scope, type) => {
1985
1988
  const idx = type[0][1];
1986
1989
 
1987
1990
  // 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;
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
+ }
1990
1996
  }
1991
1997
 
1992
1998
  return null;
@@ -2117,6 +2123,17 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2117
2123
  return out;
2118
2124
  };
2119
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
+
2120
2137
  const allocVar = (scope, name, global = false, type = true) => {
2121
2138
  const target = global ? globals : scope.locals;
2122
2139
 
@@ -2133,7 +2150,7 @@ const allocVar = (scope, name, global = false, type = true) => {
2133
2150
 
2134
2151
  if (type) {
2135
2152
  let typeIdx = global ? globalInd++ : scope.localInd++;
2136
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2153
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32, name };
2137
2154
  }
2138
2155
 
2139
2156
  return idx;
@@ -2346,18 +2363,21 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2346
2363
  Opcodes.i32_to_u,
2347
2364
 
2348
2365
  // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2349
- ...number(ValtypeSize[valtype], Valtype.i32),
2366
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2350
2367
  [ Opcodes.i32_mul ],
2351
2368
  ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
2352
2369
  ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2353
2370
 
2354
2371
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2355
2372
  [ 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)),
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)),
2358
2378
  [ Opcodes.local_tee, newValueTmp ],
2359
2379
 
2360
- [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2380
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2361
2381
  ],
2362
2382
 
2363
2383
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
@@ -2611,21 +2631,16 @@ const generateConditional = (scope, decl) => {
2611
2631
  out.push([ Opcodes.if, valtypeBinary ]);
2612
2632
  depth.push('if');
2613
2633
 
2614
- out.push(...generate(scope, decl.consequent));
2615
-
2616
- // note type
2617
2634
  out.push(
2618
- ...getNodeType(scope, decl.consequent),
2619
- ...setLastType(scope)
2635
+ ...generate(scope, decl.consequent),
2636
+ ...setLastType(scope, getNodeType(scope, decl.consequent))
2620
2637
  );
2621
2638
 
2622
2639
  out.push([ Opcodes.else ]);
2623
- out.push(...generate(scope, decl.alternate));
2624
2640
 
2625
- // note type
2626
2641
  out.push(
2627
- ...getNodeType(scope, decl.alternate),
2628
- ...setLastType(scope)
2642
+ ...generate(scope, decl.alternate),
2643
+ ...setLastType(scope, getNodeType(scope, decl.alternate))
2629
2644
  );
2630
2645
 
2631
2646
  out.push([ Opcodes.end ]);
@@ -2773,12 +2788,15 @@ const generateForOf = (scope, decl) => {
2773
2788
  // todo: optimize away counter and use end pointer
2774
2789
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2775
2790
  [TYPES.array]: [
2776
- ...setType(scope, leftName, TYPES.number),
2777
-
2778
2791
  [ Opcodes.loop, Blocktype.void ],
2779
2792
 
2780
2793
  [ Opcodes.local_get, pointer ],
2781
- [ 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
+ ]),
2782
2800
 
2783
2801
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2784
2802
 
@@ -2787,9 +2805,9 @@ const generateForOf = (scope, decl) => {
2787
2805
  ...generate(scope, decl.body),
2788
2806
  [ Opcodes.end ],
2789
2807
 
2790
- // increment iter pointer by valtype size
2808
+ // increment iter pointer by valtype size + 1
2791
2809
  [ Opcodes.local_get, pointer ],
2792
- ...number(ValtypeSize[valtype], Valtype.i32),
2810
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2793
2811
  [ Opcodes.i32_add ],
2794
2812
  [ Opcodes.local_set, pointer ],
2795
2813
 
@@ -3106,7 +3124,7 @@ const getAllocType = itemType => {
3106
3124
  }
3107
3125
  };
3108
3126
 
3109
- 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) => {
3110
3128
  const out = [];
3111
3129
 
3112
3130
  scope.arrays ??= new Map();
@@ -3169,7 +3187,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3169
3187
 
3170
3188
  const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3171
3189
 
3172
- // store length as 0th array
3190
+ // store length
3173
3191
  out.push(
3174
3192
  ...pointerWasm,
3175
3193
  ...number(length, Valtype.i32),
@@ -3177,14 +3195,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3177
3195
  );
3178
3196
 
3179
3197
  const storeOp = StoreOps[itemType];
3180
-
3198
+ const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
3181
3199
  if (!initEmpty) for (let i = 0; i < length; i++) {
3182
3200
  if (elements[i] == null) continue;
3183
3201
 
3202
+ const offset = ValtypeSize.i32 + i * sizePerEl;
3184
3203
  out.push(
3185
3204
  ...pointerWasm,
3186
3205
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
3187
- [ 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
+ ])
3188
3212
  );
3189
3213
  }
3190
3214
 
@@ -3194,6 +3218,65 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3194
3218
  return [ out, pointer ];
3195
3219
  };
3196
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
+
3197
3280
  const byteStringable = str => {
3198
3281
  if (!Prefs.bytestring) return false;
3199
3282
 
@@ -3222,14 +3305,28 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
3222
3305
  };
3223
3306
 
3224
3307
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
3225
- return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
3308
+ return makeArray(scope, decl, global, name, initEmpty, valtype, true)[0];
3226
3309
  };
3227
3310
 
3228
- export const generateMember = (scope, decl, _global, _name) => {
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
+ ];
3318
+ };
3319
+
3320
+ const withType = (scope, wasm, type) => [
3321
+ ...wasm,
3322
+ ...setLastType(scope, type)
3323
+ ];
3324
+
3325
+ const generateMember = (scope, decl, _global, _name) => {
3229
3326
  const name = decl.object.name;
3230
3327
  const pointer = scope.arrays?.get(name);
3231
3328
 
3232
- const aotPointer = Prefs.aotPointerOpt && pointer != null;
3329
+ const aotPointer = Prefs.aotPointerOpt && pointer;
3233
3330
 
3234
3331
  // hack: .name
3235
3332
  if (decl.property.name === 'name') {
@@ -3239,9 +3336,9 @@ export const generateMember = (scope, decl, _global, _name) => {
3239
3336
  // eg: __String_prototype_toLowerCase -> toLowerCase
3240
3337
  if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
3241
3338
 
3242
- return makeString(scope, nameProp, _global, _name, true);
3339
+ return withType(scope, makeString(scope, nameProp, _global, _name, true), TYPES.bytestring);
3243
3340
  } else {
3244
- return generate(scope, DEFAULT_VALUE);
3341
+ return withType(scope, number(0), TYPES.undefined);
3245
3342
  }
3246
3343
  }
3247
3344
 
@@ -3251,7 +3348,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3251
3348
  if (func) {
3252
3349
  const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3253
3350
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3254
- 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);
3255
3352
  }
3256
3353
 
3257
3354
  if (builtinFuncs[name + '$constructor']) {
@@ -3261,24 +3358,88 @@ export const generateMember = (scope, decl, _global, _name) => {
3261
3358
  const constructorFunc = builtinFuncs[name + '$constructor'];
3262
3359
  const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3263
3360
 
3264
- 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
+ ];
3265
3379
  }
3266
3380
 
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);
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
+ }
3270
3396
 
3271
3397
  return [
3272
- ...(aotPointer ? number(0, Valtype.i32) : [
3273
- ...generate(scope, decl.object),
3274
- Opcodes.i32_to_u
3275
- ]),
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
+ ]),
3404
+
3405
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3406
+ Opcodes.i32_from_u,
3276
3407
 
3277
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128((aotPointer ? pointer : 0)) ],
3278
- Opcodes.i32_from_u
3408
+ ...setLastType(scope, TYPES.number),
3409
+ [ Opcodes.else ],
3410
+ ...number(0),
3411
+ ...setLastType(scope, TYPES.undefined),
3412
+ [ Opcodes.end ]
3279
3413
  ];
3280
3414
  }
3281
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
+
3282
3443
  const object = generate(scope, decl.object);
3283
3444
  const property = generate(scope, decl.property);
3284
3445
 
@@ -3293,24 +3454,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3293
3454
 
3294
3455
  return typeSwitch(scope, getNodeType(scope, decl.object), {
3295
3456
  [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),
3457
+ ...loadArray(scope, object, property, aotPointer),
3314
3458
  ...setLastType(scope)
3315
3459
  ],
3316
3460
 
@@ -3341,9 +3485,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3341
3485
 
3342
3486
  // return new string (page)
3343
3487
  ...number(newPointer),
3344
-
3345
- ...number(TYPES.string, Valtype.i32),
3346
- ...setLastType(scope)
3488
+ ...setLastType(scope, TYPES.string)
3347
3489
  ],
3348
3490
  [TYPES.bytestring]: [
3349
3491
  // setup new/out array
@@ -3369,9 +3511,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3369
3511
 
3370
3512
  // return new string (page)
3371
3513
  ...number(newPointer),
3372
-
3373
- ...number(TYPES.bytestring, Valtype.i32),
3374
- ...setLastType(scope)
3514
+ ...setLastType(scope, TYPES.bytestring)
3375
3515
  ],
3376
3516
 
3377
3517
  default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
@@ -3396,7 +3536,7 @@ const objectHack = node => {
3396
3536
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3397
3537
 
3398
3538
  // if .name or .length, give up (hack within a hack!)
3399
- if (['name', 'length'].includes(node.property.name)) {
3539
+ if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
3400
3540
  node.object = objectHack(node.object);
3401
3541
  return;
3402
3542
  }