pxt-core 13.2.2 → 13.2.3
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.
- package/built/pxt.js +585 -77
- package/built/pxtcompiler.d.ts +19 -1
- package/built/pxtcompiler.js +519 -64
- package/built/pxtlib.d.ts +1 -1
- package/built/pxtlib.js +66 -13
- package/built/target.js +1 -1
- package/built/targetlight.js +1 -1
- package/built/tests/thumb-test/asmchecks.d.ts +22 -0
- package/built/web/main.js +1 -1
- package/built/web/pxtapp.js +1 -1
- package/built/web/pxtcompiler.js +1 -1
- package/built/web/pxtembed.js +3 -3
- package/built/web/pxtlib.js +1 -1
- package/built/web/pxtworker.js +1 -1
- package/localtypings/pxtarget.d.ts +2 -0
- package/package.json +1 -1
package/built/pxtcompiler.js
CHANGED
|
@@ -1138,6 +1138,10 @@ ${baseLabel}_nochk:
|
|
|
1138
1138
|
}
|
|
1139
1139
|
emitFieldAccess(e, store = false) {
|
|
1140
1140
|
let info = e.data;
|
|
1141
|
+
if (!store && this.shouldSpecializeCheckedFieldLoad(info)) {
|
|
1142
|
+
this.emitCheckedFieldLoad(info);
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1141
1145
|
let pref = store ? "st" : "ld";
|
|
1142
1146
|
let lbl = pref + "fld_" + info.classInfo.id + "_" + info.name;
|
|
1143
1147
|
if (info.needsCheck && !pxtc.target.switches.skipClassCheck) {
|
|
@@ -1156,6 +1160,32 @@ ${baseLabel}_nochk:
|
|
|
1156
1160
|
this.write(`ldr r0, [r0, ${xoff}]`);
|
|
1157
1161
|
return;
|
|
1158
1162
|
}
|
|
1163
|
+
checkedFieldAccessCountKey(info) {
|
|
1164
|
+
return `${info.classInfo.id}:${info.name}:get`;
|
|
1165
|
+
}
|
|
1166
|
+
shouldSpecializeCheckedFieldLoad(info) {
|
|
1167
|
+
if (!pxtc.isThumb() || !this.bin.finalPass || pxtc.target.switches.noIfaceSpec)
|
|
1168
|
+
return false;
|
|
1169
|
+
if (pxtc.target.switches.skipClassCheck || !info.needsCheck)
|
|
1170
|
+
return false;
|
|
1171
|
+
return (this.bin.checkedFieldAccessCounts[this.checkedFieldAccessCountKey(info)] || 0) >= 5;
|
|
1172
|
+
}
|
|
1173
|
+
emitCheckedFieldLoad(info) {
|
|
1174
|
+
const helperKey = "ldfldchk_" + info.classInfo.id + "_" + info.name;
|
|
1175
|
+
const helper = this.ensureLabelledHelper(helperKey, () => {
|
|
1176
|
+
this.write(this.t.helper_prologue());
|
|
1177
|
+
this.emitInstanceOf(info.classInfo, "validate");
|
|
1178
|
+
let off = info.idx * 4 + 4;
|
|
1179
|
+
let xoff = "#" + off;
|
|
1180
|
+
if (off > 124) {
|
|
1181
|
+
this.write(this.t.emit_int(off, "r3"));
|
|
1182
|
+
xoff = "r3";
|
|
1183
|
+
}
|
|
1184
|
+
this.write(`ldr r0, [r0, ${xoff}]`);
|
|
1185
|
+
this.write(this.t.helper_epilogue());
|
|
1186
|
+
});
|
|
1187
|
+
this.write(this.t.call_lbl(helper, false, this.stackAlignmentNeeded(0)));
|
|
1188
|
+
}
|
|
1159
1189
|
writeFailBranch() {
|
|
1160
1190
|
this.write(`.fail:`);
|
|
1161
1191
|
this.write(`mov r1, lr`);
|
|
@@ -1376,12 +1406,50 @@ ${baseLabel}_nochk:
|
|
|
1376
1406
|
}
|
|
1377
1407
|
emitIfaceCall(procid, numargs, getset = "") {
|
|
1378
1408
|
pxtc.U.assert(procid.ifaceIndex > 0);
|
|
1379
|
-
|
|
1380
|
-
this.
|
|
1409
|
+
const helperKey = "ifacecall" + numargs + "_" + getset;
|
|
1410
|
+
const helper = this.ensureLabelledHelper(helperKey, () => {
|
|
1381
1411
|
this.write(`ldr r0, [sp, #0] ; ld-this`);
|
|
1382
1412
|
this.loadVTable();
|
|
1383
1413
|
this.ifaceCallCore(numargs, getset);
|
|
1384
1414
|
});
|
|
1415
|
+
if (this.shouldSpecializeIfaceCall(procid.ifaceIndex, numargs, getset)) {
|
|
1416
|
+
const thunkKey = helperKey + "_i" + procid.ifaceIndex;
|
|
1417
|
+
this.emitLabelledHelper(thunkKey, () => {
|
|
1418
|
+
this.write(this.t.emit_int(procid.ifaceIndex, "r1"));
|
|
1419
|
+
this.write(`ldlit r7, ${helper}@fn`);
|
|
1420
|
+
this.write(`bx r7`);
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
else {
|
|
1424
|
+
this.write(this.t.emit_int(procid.ifaceIndex, "r1"));
|
|
1425
|
+
this.write(this.t.call_lbl(helper));
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
ifaceCallCountKey(ifaceIndex, numargs, getset) {
|
|
1429
|
+
return `${ifaceIndex}:${numargs}:${getset || "call"}`;
|
|
1430
|
+
}
|
|
1431
|
+
// Count-driven specialization decisions (this method and the related
|
|
1432
|
+
// shouldSpecializeCheckedFieldLoad / shouldSpecializeMapSetByFieldId) assume:
|
|
1433
|
+
// - bin.finalPass is true, AND
|
|
1434
|
+
// - the count maps were populated during the same final-pass emit()
|
|
1435
|
+
// call that builds the IR this asm walk consumes.
|
|
1436
|
+
// This is currently true because the frontend populates counts during IR
|
|
1437
|
+
// generation and the backend reads them only during the subsequent asm walk.
|
|
1438
|
+
// Any future change that interleaves IR generation with asm emission would
|
|
1439
|
+
// silently regress these to "always false".
|
|
1440
|
+
//
|
|
1441
|
+
// Threshold of 3 is the break-even point for the labelled-thunk pattern:
|
|
1442
|
+
// each shared call site saves ~4 bytes vs an inline call, and the thunk
|
|
1443
|
+
// itself costs ~12 bytes. At 3 callers, savings break even with thunk cost.
|
|
1444
|
+
shouldSpecializeIfaceCall(ifaceIndex, numargs, getset) {
|
|
1445
|
+
if (!pxtc.isThumb() || !this.bin.finalPass || pxtc.target.switches.noIfaceSpec)
|
|
1446
|
+
return false;
|
|
1447
|
+
const count = this.bin.ifaceCallCounts[this.ifaceCallCountKey(ifaceIndex, numargs, getset)] || 0;
|
|
1448
|
+
if (getset == "get" && numargs == 1)
|
|
1449
|
+
return count >= 3;
|
|
1450
|
+
if (getset == "set" || !getset)
|
|
1451
|
+
return count >= 3;
|
|
1452
|
+
return false;
|
|
1385
1453
|
}
|
|
1386
1454
|
// vtable in r3; clobber r2
|
|
1387
1455
|
checkSubtype(info, failLbl = ".fail", r2 = "r2") {
|
|
@@ -1479,6 +1547,8 @@ ${baseLabel}_nochk:
|
|
|
1479
1547
|
}
|
|
1480
1548
|
emitRtCall(topExpr, genCall = null) {
|
|
1481
1549
|
let name = topExpr.data;
|
|
1550
|
+
const mapSetFieldId = this.mapSetByFieldId(topExpr);
|
|
1551
|
+
const specializeMapSetFieldId = mapSetFieldId !== undefined && this.shouldSpecializeMapSetByFieldId(mapSetFieldId);
|
|
1482
1552
|
if (name == "pxt::beginTry") {
|
|
1483
1553
|
return this.emitBeginTry(topExpr);
|
|
1484
1554
|
}
|
|
@@ -1491,6 +1561,8 @@ ${baseLabel}_nochk:
|
|
|
1491
1561
|
isRef: (maskInfo.refMask & (1 << i)) != 0,
|
|
1492
1562
|
conv: convs.find(c => c.argIdx == i)
|
|
1493
1563
|
}));
|
|
1564
|
+
if (specializeMapSetFieldId)
|
|
1565
|
+
allArgs = allArgs.filter(a => a.idx != 1);
|
|
1494
1566
|
pxtc.U.assert(allArgs.length <= 4);
|
|
1495
1567
|
let seenUpdate = false;
|
|
1496
1568
|
for (let a of pxtc.U.reversed(allArgs)) {
|
|
@@ -1593,33 +1665,69 @@ ${baseLabel}_nochk:
|
|
|
1593
1665
|
genCall();
|
|
1594
1666
|
}
|
|
1595
1667
|
else {
|
|
1596
|
-
if (
|
|
1668
|
+
if (specializeMapSetFieldId)
|
|
1669
|
+
this.emitMapSetByFieldIdCall(mapSetFieldId);
|
|
1670
|
+
else if (name != "langsupp::ignore")
|
|
1597
1671
|
this.alignedCall(name, "", 0, true);
|
|
1598
1672
|
}
|
|
1599
1673
|
if (clearStack) {
|
|
1600
1674
|
this.clearArgs(complexArgs.filter(a => !a.isRef).map(a => a.expr), complexArgs.filter(a => a.isRef).map(a => a.expr));
|
|
1601
1675
|
}
|
|
1602
1676
|
}
|
|
1677
|
+
mapSetByFieldId(topExpr) {
|
|
1678
|
+
if (topExpr.data != "pxtrt::mapSet" || topExpr.args.length != 3)
|
|
1679
|
+
return undefined;
|
|
1680
|
+
const fieldId = topExpr.args[1];
|
|
1681
|
+
if (fieldId.exprKind != pxtc.ir.EK.NumberLiteral || typeof fieldId.data != "number")
|
|
1682
|
+
return undefined;
|
|
1683
|
+
return fieldId.data;
|
|
1684
|
+
}
|
|
1685
|
+
shouldSpecializeMapSetByFieldId(fieldId) {
|
|
1686
|
+
if (!pxtc.isThumb() || !this.bin.finalPass || pxtc.target.switches.noIfaceSpec)
|
|
1687
|
+
return false;
|
|
1688
|
+
return (this.bin.mapSetByFieldIdCounts[fieldId + ""] || 0) >= 3;
|
|
1689
|
+
}
|
|
1690
|
+
emitMapSetByFieldIdCall(fieldId) {
|
|
1691
|
+
const helper = this.ensureLabelledHelper("mapset_i" + fieldId, () => {
|
|
1692
|
+
this.write(this.t.helper_prologue());
|
|
1693
|
+
this.write(this.t.emit_int(fieldId, "r1"));
|
|
1694
|
+
this.write(this.t.callCPP("pxtrt::mapSet"));
|
|
1695
|
+
this.write(this.t.helper_epilogue());
|
|
1696
|
+
});
|
|
1697
|
+
this.write(this.t.call_lbl(helper, false, this.stackAlignmentNeeded(0)));
|
|
1698
|
+
}
|
|
1603
1699
|
alignedCall(name, cmt = "", off = 0, saveStack = false) {
|
|
1604
1700
|
if (pxtc.U.startsWith(name, "_cmp_") || pxtc.U.startsWith(name, "_pxt_"))
|
|
1605
1701
|
saveStack = false;
|
|
1606
1702
|
this.write(this.t.call_lbl(name, saveStack, this.stackAlignmentNeeded(off)) + cmt);
|
|
1607
1703
|
}
|
|
1608
|
-
|
|
1704
|
+
// Lazily emit a deduped helper body and return its label.
|
|
1705
|
+
// Use this when you want to BRANCH to (or reference) the helper without
|
|
1706
|
+
// emitting a `bl` at the current cursor -- e.g. when wrapping the call in
|
|
1707
|
+
// a thunk, or storing the label for later use.
|
|
1708
|
+
ensureLabelledHelper(lbl, generate) {
|
|
1609
1709
|
if (!this.labelledHelpers[lbl]) {
|
|
1610
1710
|
let outp = this.redirectOutput(generate);
|
|
1611
|
-
this.
|
|
1612
|
-
this.labelledHelpers[lbl] = this.bin.codeHelpers[outp];
|
|
1613
|
-
}
|
|
1614
|
-
else {
|
|
1615
|
-
this.write(this.t.call_lbl(this.labelledHelpers[lbl]));
|
|
1711
|
+
this.labelledHelpers[lbl] = this.helperLabel(outp, lbl);
|
|
1616
1712
|
}
|
|
1713
|
+
return this.labelledHelpers[lbl];
|
|
1617
1714
|
}
|
|
1618
|
-
|
|
1715
|
+
// Ensure a deduped helper exists and emit a `bl` to it at the cursor.
|
|
1716
|
+
// Use this for the common case of "factor this sequence into a helper and call it here".
|
|
1717
|
+
emitLabelledHelper(lbl, generate) {
|
|
1718
|
+
const helper = this.ensureLabelledHelper(lbl, generate);
|
|
1719
|
+
this.write(this.t.call_lbl(helper));
|
|
1720
|
+
return helper;
|
|
1721
|
+
}
|
|
1722
|
+
helperLabel(asm, baseName = "hlp") {
|
|
1619
1723
|
if (!this.bin.codeHelpers[asm]) {
|
|
1620
1724
|
let len = Object.keys(this.bin.codeHelpers).length;
|
|
1621
1725
|
this.bin.codeHelpers[asm] = `_${baseName}_${len}`;
|
|
1622
1726
|
}
|
|
1727
|
+
return this.bin.codeHelpers[asm];
|
|
1728
|
+
}
|
|
1729
|
+
emitHelper(asm, baseName = "hlp") {
|
|
1730
|
+
this.helperLabel(asm, baseName);
|
|
1623
1731
|
this.write(this.t.call_lbl(this.bin.codeHelpers[asm]));
|
|
1624
1732
|
}
|
|
1625
1733
|
pushToExprStack(a) {
|
|
@@ -1656,48 +1764,85 @@ ${baseLabel}_nochk:
|
|
|
1656
1764
|
}
|
|
1657
1765
|
}
|
|
1658
1766
|
}
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
this.write(`
|
|
1767
|
+
emitFieldMethod(op) {
|
|
1768
|
+
this.write(`
|
|
1662
1769
|
${this.helperObject(op)}
|
|
1663
1770
|
.section code
|
|
1664
1771
|
_pxt_map_${op}:
|
|
1665
1772
|
`);
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1773
|
+
this.loadVTable("r4");
|
|
1774
|
+
this.checkSubtype(this.builtInClassNo(pxt.BuiltInType.RefMap), ".notmap", "r4");
|
|
1775
|
+
this.write(this.t.callCPPPush(op == "set" ? "pxtrt::mapSetByString" : "pxtrt::mapGetByString"));
|
|
1776
|
+
this.write(".notmap:");
|
|
1777
|
+
let numargs = op == "set" ? 2 : 1;
|
|
1778
|
+
let hasAlign = false;
|
|
1779
|
+
this.write("mov r4, r3 ; save VT");
|
|
1780
|
+
if (op == "set") {
|
|
1781
|
+
if (pxtc.target.stackAlign) {
|
|
1782
|
+
hasAlign = true;
|
|
1783
|
+
this.write("push {lr} ; align");
|
|
1784
|
+
}
|
|
1785
|
+
this.write(`
|
|
1679
1786
|
push {r0, r2, lr}
|
|
1680
1787
|
mov r0, r1
|
|
1681
1788
|
`);
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1789
|
+
}
|
|
1790
|
+
else {
|
|
1791
|
+
this.write(`
|
|
1685
1792
|
push {r0, lr}
|
|
1686
1793
|
mov r0, r1
|
|
1687
1794
|
`);
|
|
1688
|
-
|
|
1689
|
-
|
|
1795
|
+
}
|
|
1796
|
+
this.write(`
|
|
1690
1797
|
bl pxtrt::lookupMapKey
|
|
1691
1798
|
mov r1, r0 ; put key index in r1
|
|
1692
1799
|
ldr r0, [sp, #0] ; restore obj pointer
|
|
1693
1800
|
mov r3, r4 ; restore vt
|
|
1694
1801
|
bl .dowork
|
|
1695
1802
|
`);
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1803
|
+
this.write(this.t.pop_locals(numargs + (hasAlign ? 1 : 0)));
|
|
1804
|
+
this.write("pop {pc}");
|
|
1805
|
+
this.write(".dowork:");
|
|
1806
|
+
this.ifaceCallCore(numargs, op, true);
|
|
1807
|
+
}
|
|
1808
|
+
emitStringMapSetMethod() {
|
|
1809
|
+
// Fast path for the well-typed "map with string index signature" case.
|
|
1810
|
+
// - tagged-int / null receivers: unrecoverable, panic with failedCast (matches the
|
|
1811
|
+
// pre-optimization mapSetGeneric path, which also can't proceed on these).
|
|
1812
|
+
// - non-RefMap pointer receivers: tail-call _pxt_map_set, which handles
|
|
1813
|
+
// non-map objects via interface dispatch. Preserves back-compat for code that
|
|
1814
|
+
// casts non-map objects to a string-index-signature type.
|
|
1815
|
+
//
|
|
1816
|
+
// Emitted only when the final-pass IR selected mapSetByStringOnly
|
|
1817
|
+
// (never under `noIfaceSpec`): unreferenced helper text costs image
|
|
1818
|
+
// space in every program otherwise.
|
|
1819
|
+
if (!this.bin.usesMapSetByStringOnly)
|
|
1820
|
+
return;
|
|
1821
|
+
this.write(`
|
|
1822
|
+
${this.helperObject("set string map")}
|
|
1823
|
+
.section code
|
|
1824
|
+
_pxt_map_set_by_string:
|
|
1825
|
+
lsls r4, r0, #30
|
|
1826
|
+
bne .badtype
|
|
1827
|
+
cmp r0, #0
|
|
1828
|
+
beq .badtype
|
|
1829
|
+
ldr r3, [r0, #0]
|
|
1830
|
+
`);
|
|
1831
|
+
this.write(`
|
|
1832
|
+
ldrh r4, [r3, #8]
|
|
1833
|
+
cmp r4, #${pxt.BuiltInType.RefMap}
|
|
1834
|
+
bne .fallback
|
|
1835
|
+
`);
|
|
1836
|
+
this.write(this.t.callCPPPush("pxtrt::mapSetByString"));
|
|
1837
|
+
this.write(`
|
|
1838
|
+
.fallback:
|
|
1839
|
+
b _pxt_map_set
|
|
1840
|
+
.badtype:
|
|
1841
|
+
mov r1, lr
|
|
1842
|
+
mov r7, sp
|
|
1843
|
+
str r7, [r6, #4]
|
|
1844
|
+
bl pxt::failedCast
|
|
1845
|
+
`);
|
|
1701
1846
|
}
|
|
1702
1847
|
emitArrayMethod(op, isBuffer) {
|
|
1703
1848
|
this.write(`
|
|
@@ -1783,6 +1928,12 @@ ${baseLabel}_nochk:
|
|
|
1783
1928
|
this.emitArrayMethod(op, false);
|
|
1784
1929
|
}
|
|
1785
1930
|
}
|
|
1931
|
+
emitFieldMethods() {
|
|
1932
|
+
for (let op of ["get", "set"]) {
|
|
1933
|
+
this.emitFieldMethod(op);
|
|
1934
|
+
}
|
|
1935
|
+
this.emitStringMapSetMethod();
|
|
1936
|
+
}
|
|
1786
1937
|
emitLambdaTrampoline() {
|
|
1787
1938
|
let r3 = pxtc.target.stackAlign ? "r3," : "";
|
|
1788
1939
|
this.write(`
|
|
@@ -2130,6 +2281,12 @@ ${baseLabel}_nochk:
|
|
|
2130
2281
|
this.write(`.short 0, 0 ; no captured vars`);
|
|
2131
2282
|
this.write(`.word ${this.proc.label()}_args@fn`);
|
|
2132
2283
|
}
|
|
2284
|
+
if (this.proc.useExactIfaceWrapper) {
|
|
2285
|
+
this.write(`${this.proc.label()}_iface:`);
|
|
2286
|
+
this.write(`b ${this.proc.label()}_nochk`);
|
|
2287
|
+
if (!this.proc.info.usedAsValue)
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2133
2290
|
this.write(`${this.proc.label()}_args:`);
|
|
2134
2291
|
let numargs = this.proc.args.length;
|
|
2135
2292
|
if (numargs == 0)
|
|
@@ -2898,6 +3055,17 @@ var ts;
|
|
|
2898
3055
|
"pxt::fromInt": "_numops_fromInt",
|
|
2899
3056
|
"pxt::switch_eq": "_pxt_switch_eq",
|
|
2900
3057
|
};
|
|
3058
|
+
// Boolean conversion fast paths, kept out of inlineArithmetic so the
|
|
3059
|
+
// `noBoolLower` flag can drop the group (redirects and helper bodies alike).
|
|
3060
|
+
const inlineBoolOps = {
|
|
3061
|
+
"pxt::fromBool": "_pxt_fromBool",
|
|
3062
|
+
"numops::toBool": "_numops_toBool",
|
|
3063
|
+
"numops::toBoolDecr": "_numops_toBoolDecr",
|
|
3064
|
+
"Boolean_::bang": "_pxt_boolean_bang",
|
|
3065
|
+
};
|
|
3066
|
+
function boolLowerEnabled() {
|
|
3067
|
+
return !pxtc.target.switches.noBoolLower;
|
|
3068
|
+
}
|
|
2901
3069
|
// snippets for ARM Thumb assembly
|
|
2902
3070
|
class ThumbSnippets extends pxtc.AssemblerSnippets {
|
|
2903
3071
|
stackAligned() {
|
|
@@ -3005,6 +3173,8 @@ ${lbl}:`;
|
|
|
3005
3173
|
}
|
|
3006
3174
|
call_lbl(lbl, saveStack, stackAlign) {
|
|
3007
3175
|
let o = pxtc.U.lookup(inlineArithmetic, lbl);
|
|
3176
|
+
if (!o && boolLowerEnabled())
|
|
3177
|
+
o = pxtc.U.lookup(inlineBoolOps, lbl);
|
|
3008
3178
|
if (o) {
|
|
3009
3179
|
lbl = o;
|
|
3010
3180
|
saveStack = false;
|
|
@@ -3176,6 +3346,99 @@ _numops_fromInt:
|
|
|
3176
3346
|
blx lr
|
|
3177
3347
|
.over2:
|
|
3178
3348
|
${this.callCPPPush("pxt::fromInt")}
|
|
3349
|
+
`;
|
|
3350
|
+
// skipped under `noBoolLower` flag: unreferenced helper text still costs image space
|
|
3351
|
+
if (boolLowerEnabled())
|
|
3352
|
+
r += `
|
|
3353
|
+
; Tag a raw 0/1 truth value into a boolean object (taggedTrue/taggedFalse).
|
|
3354
|
+
; Inverse of the toBool fast paths below; matches C++ pxt::fromBool.
|
|
3355
|
+
_pxt_fromBool:
|
|
3356
|
+
@scope _pxt_fromBool
|
|
3357
|
+
cmp r0, #0
|
|
3358
|
+
beq .false
|
|
3359
|
+
.true:
|
|
3360
|
+
movs r0, #${pxtc.taggedTrue}
|
|
3361
|
+
blx lr
|
|
3362
|
+
.false:
|
|
3363
|
+
movs r0, #${pxtc.taggedFalse}
|
|
3364
|
+
blx lr
|
|
3365
|
+
|
|
3366
|
+
; Logical NOT of a raw 0/1 truth value -> raw 0/1 (matches C++ bool bang(bool)).
|
|
3367
|
+
; Callers pass an already-tested condition, so this is raw-in / raw-out; value
|
|
3368
|
+
; context (x = !y) wraps the result with fromBool to re-tag it.
|
|
3369
|
+
_pxt_boolean_bang:
|
|
3370
|
+
@scope _pxt_boolean_bang
|
|
3371
|
+
cmp r0, #0
|
|
3372
|
+
beq .true
|
|
3373
|
+
.false:
|
|
3374
|
+
movs r0, #0
|
|
3375
|
+
blx lr
|
|
3376
|
+
.true:
|
|
3377
|
+
movs r0, #1
|
|
3378
|
+
blx lr
|
|
3379
|
+
|
|
3380
|
+
; Fast-path truthiness test: tagged value in r0 -> raw 0/1 in r0.
|
|
3381
|
+
; Value encodings (see taggedSpecialValue in emitter.ts): tagged int = (n<<1)|1
|
|
3382
|
+
; (low bit set); special tags = (n<<2)|2 with undefined=0, null, false, NaN,
|
|
3383
|
+
; true; heap objects have low 2 bits 00.
|
|
3384
|
+
; Only non-pointer values are decided inline here; any heap object is handed to
|
|
3385
|
+
; the C++ helper at .boxed (which also handles boxed numbers/strings like 0.0
|
|
3386
|
+
; and ""). INVARIANT: the falsy set below must match C++ numops::toBool exactly
|
|
3387
|
+
; -- if a new falsy tagged value is ever added, it must be added here too, or it
|
|
3388
|
+
; will fall through to .true.
|
|
3389
|
+
_numops_toBool:
|
|
3390
|
+
@scope _numops_toBool
|
|
3391
|
+
cmp r0, #0 ; undefined (0) -> false
|
|
3392
|
+
beq .false
|
|
3393
|
+
cmp r0, #1 ; integer 0 ((0<<1)|1) -> false
|
|
3394
|
+
beq .false
|
|
3395
|
+
lsls r1, r0, #31 ; odd => nonzero tagged int -> true
|
|
3396
|
+
bne .true
|
|
3397
|
+
cmp r0, #${pxtc.taggedNull} ; null -> false
|
|
3398
|
+
beq .false
|
|
3399
|
+
cmp r0, #${pxtc.taggedFalse} ; false -> false
|
|
3400
|
+
beq .false
|
|
3401
|
+
cmp r0, #${pxtc.taggedNaN} ; NaN -> false
|
|
3402
|
+
beq .false
|
|
3403
|
+
lsls r1, r0, #30 ; low 2 bits 00 => heap object -> defer to C++
|
|
3404
|
+
beq .boxed
|
|
3405
|
+
.true: ; remaining specials (e.g. true) are truthy
|
|
3406
|
+
movs r0, #1
|
|
3407
|
+
blx lr
|
|
3408
|
+
.false:
|
|
3409
|
+
movs r0, #0
|
|
3410
|
+
blx lr
|
|
3411
|
+
.boxed:
|
|
3412
|
+
${this.callCPPPush("numops::toBool")}
|
|
3413
|
+
|
|
3414
|
+
; Same truthiness classification as _numops_toBool (see comments above), but
|
|
3415
|
+
; this variant also consumes (ref-decrements) its argument. Only heap objects
|
|
3416
|
+
; are ref-counted, and those go through the C++ fallback which does the decr;
|
|
3417
|
+
; the inline non-pointer paths carry no refcount, so they need none.
|
|
3418
|
+
_numops_toBoolDecr:
|
|
3419
|
+
@scope _numops_toBoolDecr
|
|
3420
|
+
cmp r0, #0 ; undefined (0) -> false
|
|
3421
|
+
beq .false
|
|
3422
|
+
cmp r0, #1 ; integer 0 ((0<<1)|1) -> false
|
|
3423
|
+
beq .false
|
|
3424
|
+
lsls r1, r0, #31 ; odd => nonzero tagged int -> true
|
|
3425
|
+
bne .true
|
|
3426
|
+
cmp r0, #${pxtc.taggedNull} ; null -> false
|
|
3427
|
+
beq .false
|
|
3428
|
+
cmp r0, #${pxtc.taggedFalse} ; false -> false
|
|
3429
|
+
beq .false
|
|
3430
|
+
cmp r0, #${pxtc.taggedNaN} ; NaN -> false
|
|
3431
|
+
beq .false
|
|
3432
|
+
lsls r1, r0, #30 ; low 2 bits 00 => heap object -> defer to C++
|
|
3433
|
+
beq .boxed
|
|
3434
|
+
.true:
|
|
3435
|
+
movs r0, #1
|
|
3436
|
+
blx lr
|
|
3437
|
+
.false:
|
|
3438
|
+
movs r0, #0
|
|
3439
|
+
blx lr
|
|
3440
|
+
.boxed:
|
|
3441
|
+
${this.callCPPPush("numops::toBoolDecr")}
|
|
3179
3442
|
`;
|
|
3180
3443
|
for (let op of Object.keys(pxtc.thumbCmpMap)) {
|
|
3181
3444
|
op = op.replace(/.*::/, "");
|
|
@@ -3200,9 +3463,10 @@ _cmp_${op}:
|
|
|
3200
3463
|
// the cmp isn't really needed, given how toBoolDecr() is compiled,
|
|
3201
3464
|
// but better not rely on it
|
|
3202
3465
|
// Also, cmp isn't needed when ref-counting (it ends with movs r0, r4)
|
|
3466
|
+
const toBoolDecr = boolLowerEnabled() ? "_numops_toBoolDecr" : "numops::toBoolDecr";
|
|
3203
3467
|
r += boxedOp(`
|
|
3204
3468
|
bl numops::${op}
|
|
3205
|
-
bl
|
|
3469
|
+
bl ${toBoolDecr}
|
|
3206
3470
|
cmp r0, #0`);
|
|
3207
3471
|
}
|
|
3208
3472
|
// switch(x){case k:} compiles to equality tests via pxt::switch_eq.
|
|
@@ -8648,6 +8912,7 @@ var ts;
|
|
|
8648
8912
|
this.classInfo = null;
|
|
8649
8913
|
this.perfCounterName = null;
|
|
8650
8914
|
this.perfCounterNo = 0;
|
|
8915
|
+
this.useExactIfaceWrapper = false;
|
|
8651
8916
|
this.body = [];
|
|
8652
8917
|
this.lblNo = 0;
|
|
8653
8918
|
this.action = null;
|
|
@@ -8665,6 +8930,11 @@ var ts;
|
|
|
8665
8930
|
isGetter() {
|
|
8666
8931
|
return this.action && this.action.kind == ts.SyntaxKind.GetAccessor;
|
|
8667
8932
|
}
|
|
8933
|
+
// Returns the proc's "_args" entry label -- the full arg-shuffling
|
|
8934
|
+
// wrapper. Use this for any vtable-style reference where the caller
|
|
8935
|
+
// cannot guarantee the wrapper-skip preconditions (numargs >= proc.args.length).
|
|
8936
|
+
// For iface-table entries that CAN accept the optimization, the consumer
|
|
8937
|
+
// (hexfile.ts) inspects useExactIfaceWrapper directly and picks _iface instead.
|
|
8668
8938
|
vtLabel() {
|
|
8669
8939
|
return this.label() + (pxtc.isStackMachine() ? "" : "_args");
|
|
8670
8940
|
}
|
|
@@ -9207,6 +9477,11 @@ var ts;
|
|
|
9207
9477
|
argsFmt: ["T", "T", "S", "T"],
|
|
9208
9478
|
value: 0
|
|
9209
9479
|
},
|
|
9480
|
+
"pxtrt::mapSetByStringOnly": {
|
|
9481
|
+
name: "_pxt_map_set_by_string",
|
|
9482
|
+
argsFmt: ["T", "T", "S", "T"],
|
|
9483
|
+
value: 0
|
|
9484
|
+
},
|
|
9210
9485
|
};
|
|
9211
9486
|
let EK = pxtc.ir.EK;
|
|
9212
9487
|
pxtc.SK = ts.SyntaxKind;
|
|
@@ -9940,6 +10215,87 @@ var ts;
|
|
|
9940
10215
|
bin.breakpoints = opts.breakpoints;
|
|
9941
10216
|
bin.name = opts.name;
|
|
9942
10217
|
bin.target = opts.target;
|
|
10218
|
+
function ifaceCallKindCounts(ifaceIndex, getset) {
|
|
10219
|
+
const suffix = ":" + (getset || "call");
|
|
10220
|
+
return Object.keys(bin.ifaceCallCounts)
|
|
10221
|
+
.filter(k => pxtc.U.startsWith(k, ifaceIndex + ":") && pxtc.U.endsWith(k, suffix))
|
|
10222
|
+
.map(k => ({
|
|
10223
|
+
numargs: parseInt(k.split(":")[1]),
|
|
10224
|
+
count: bin.ifaceCallCounts[k]
|
|
10225
|
+
}));
|
|
10226
|
+
}
|
|
10227
|
+
// Decides whether `proc` (an iface-dispatched method) can safely skip the
|
|
10228
|
+
// arg-shuffling _args wrapper and have its iface vtable entry point directly
|
|
10229
|
+
// at _nochk. Safe iff every call site that targets this (ifaceIndex, getset)
|
|
10230
|
+
// bucket passes at least `proc.args.length` arguments -- that's exactly the
|
|
10231
|
+
// condition the wrapper would short-circuit on (`cmp r4, #numargs; bge _nochk`).
|
|
10232
|
+
//
|
|
10233
|
+
// Notes:
|
|
10234
|
+
// - The counts are a per-(ifaceIndex, getset) projection, not per-proc. In a
|
|
10235
|
+
// polymorphic iface bucket each proc is evaluated independently; eligibility
|
|
10236
|
+
// ends up cascading to the proc with the largest args.length in the bucket.
|
|
10237
|
+
// - toString is excluded because hexfile.ts:~841 wires toString's vtable
|
|
10238
|
+
// slot to `vtLabel()` (i.e. _args). The fixed-slot dispatch through that
|
|
10239
|
+
// vtable entry has no arg-count guarantees, so the wrapper is required.
|
|
10240
|
+
// Skipping it for a toString proc would dangle the _args reference.
|
|
10241
|
+
// Any future fixed-slot vtable consumer that uses vtLabel() must add a
|
|
10242
|
+
// matching exclusion here.
|
|
10243
|
+
// - dynamicIfaceCalls disqualifies any ifaceIndex ever dispatched without a
|
|
10244
|
+
// known decl (line ~2503). Those callers don't go through the counted path.
|
|
10245
|
+
// - The "no get calls in a non-get bucket" check is conservative: a "get"
|
|
10246
|
+
// syntax call has args.length == 1 which could under-feed a multi-arg call
|
|
10247
|
+
// target sharing the bucket.
|
|
10248
|
+
function canUseExactIfaceWrapper(proc, ifaceIndex, getset) {
|
|
10249
|
+
if (!isThumb() || !proc || !proc.info.usedAsIface || proc.args.length == 0)
|
|
10250
|
+
return false;
|
|
10251
|
+
if (proc.action && isToString(proc.action))
|
|
10252
|
+
return false;
|
|
10253
|
+
if (bin.dynamicIfaceCalls[ifaceIndex + ""])
|
|
10254
|
+
return false;
|
|
10255
|
+
if (!getset && ifaceCallKindCounts(ifaceIndex, "get").length)
|
|
10256
|
+
return false;
|
|
10257
|
+
const counts = ifaceCallKindCounts(ifaceIndex, getset);
|
|
10258
|
+
return counts.every(c => c.numargs >= proc.args.length);
|
|
10259
|
+
}
|
|
10260
|
+
// Must run AFTER the final-pass emit(rootFunction) has populated
|
|
10261
|
+
// bin.ifaceCallCounts and bin.dynamicIfaceCalls, and BEFORE
|
|
10262
|
+
// hexfile.ts emits iface vtables (which read useExactIfaceWrapper to
|
|
10263
|
+
// pick _iface vs _args per entry) and backbase.ts emits proc wrappers
|
|
10264
|
+
// (which read useExactIfaceWrapper to decide whether to emit _iface).
|
|
10265
|
+
function markExactIfaceWrappers() {
|
|
10266
|
+
bin.procs.forEach(p => p.useExactIfaceWrapper = false);
|
|
10267
|
+
if (!isThumb() || pxtc.target.switches.noIfaceSpec)
|
|
10268
|
+
return;
|
|
10269
|
+
const eligible = {};
|
|
10270
|
+
const seen = {};
|
|
10271
|
+
const note = (proc, ifaceIndex, getset) => {
|
|
10272
|
+
if (!proc || proc.args.length == 0)
|
|
10273
|
+
return;
|
|
10274
|
+
const key = proc.seqNo + "";
|
|
10275
|
+
seen[key] = true;
|
|
10276
|
+
if (eligible[key] === undefined)
|
|
10277
|
+
eligible[key] = true;
|
|
10278
|
+
if (!canUseExactIfaceWrapper(proc, ifaceIndex, getset))
|
|
10279
|
+
eligible[key] = false;
|
|
10280
|
+
};
|
|
10281
|
+
for (const info of bin.usedClassInfos) {
|
|
10282
|
+
if (!info.itable)
|
|
10283
|
+
continue;
|
|
10284
|
+
for (const entry of info.itable) {
|
|
10285
|
+
if (entry.proc)
|
|
10286
|
+
note(entry.proc, entry.idx, entry.proc.isGetter() ? "get" : "");
|
|
10287
|
+
if (entry.setProc)
|
|
10288
|
+
note(entry.setProc, entry.idx, "set");
|
|
10289
|
+
}
|
|
10290
|
+
}
|
|
10291
|
+
for (const proc of bin.procs) {
|
|
10292
|
+
const key = proc.seqNo + "";
|
|
10293
|
+
proc.useExactIfaceWrapper = !!seen[key] && !!eligible[key];
|
|
10294
|
+
}
|
|
10295
|
+
}
|
|
10296
|
+
function emitEscapedExpression(expr, reason) {
|
|
10297
|
+
return emitExpr(expr);
|
|
10298
|
+
}
|
|
9943
10299
|
function reset() {
|
|
9944
10300
|
bin.reset();
|
|
9945
10301
|
proc = null;
|
|
@@ -10020,7 +10376,13 @@ var ts;
|
|
|
10020
10376
|
reset();
|
|
10021
10377
|
needsUsingInfo = false;
|
|
10022
10378
|
bin.finalPass = true;
|
|
10379
|
+
bin.ifaceCallCounts = {};
|
|
10380
|
+
bin.mapSetByFieldIdCounts = {};
|
|
10381
|
+
bin.checkedFieldAccessCounts = {};
|
|
10382
|
+
bin.dynamicIfaceCalls = {};
|
|
10383
|
+
bin.usesMapSetByStringOnly = false;
|
|
10023
10384
|
emit(rootFunction);
|
|
10385
|
+
markExactIfaceWrappers();
|
|
10024
10386
|
pxtc.U.assert(usedWorkList.length == 0);
|
|
10025
10387
|
res.configData = [];
|
|
10026
10388
|
for (let k of Object.keys(configEntries)) {
|
|
@@ -10710,7 +11072,7 @@ ${lbl}: .short 0xffff
|
|
|
10710
11072
|
let coll = pxtc.ir.shared(pxtc.ir.rtcall("Array_::mk", []));
|
|
10711
11073
|
for (let elt of node.elements) {
|
|
10712
11074
|
let mask = isRefCountedExpr(elt) ? (1 << 1) : 0;
|
|
10713
|
-
proc.emitExpr(pxtc.ir.rtcall("Array_::push", [coll,
|
|
11075
|
+
proc.emitExpr(pxtc.ir.rtcall("Array_::push", [coll, emitEscapedExpression(elt, "arrayElement")], mask));
|
|
10714
11076
|
}
|
|
10715
11077
|
if (node.elements.length > 0)
|
|
10716
11078
|
// Make sure there is at least one use of the array, so it doesn't get GC-ed away in the last push.
|
|
@@ -10752,8 +11114,11 @@ ${lbl}: .short 0xffff
|
|
|
10752
11114
|
if (isRefCountedExpr(p.initializer))
|
|
10753
11115
|
mask |= 1 << 2;
|
|
10754
11116
|
}
|
|
11117
|
+
const nativeFieldId = pxtc.target.isNative ? getIfaceMemberId(keyName) : undefined;
|
|
11118
|
+
if (nativeFieldId !== undefined)
|
|
11119
|
+
bin.mapSetByFieldIdCounts[nativeFieldId + ""] = (bin.mapSetByFieldIdCounts[nativeFieldId + ""] || 0) + 1;
|
|
10755
11120
|
const fieldId = pxtc.target.isNative
|
|
10756
|
-
? pxtc.ir.numlit(
|
|
11121
|
+
? pxtc.ir.numlit(nativeFieldId)
|
|
10757
11122
|
: pxtc.ir.ptrlit(null, JSON.stringify(keyName));
|
|
10758
11123
|
const args = [
|
|
10759
11124
|
expr,
|
|
@@ -10813,6 +11178,9 @@ ${lbl}: .short 0xffff
|
|
|
10813
11178
|
return emitShim(fld, decl, [node.expression]);
|
|
10814
11179
|
}
|
|
10815
11180
|
else {
|
|
11181
|
+
if (idx.needsCheck && !pxtc.target.switches.skipClassCheck) {
|
|
11182
|
+
bin.checkedFieldAccessCounts[checkedFieldAccessCountKey(idx)] = (bin.checkedFieldAccessCounts[checkedFieldAccessCountKey(idx)] || 0) + 1;
|
|
11183
|
+
}
|
|
10816
11184
|
return pxtc.ir.op(EK.FieldAccess, [emitExpr(node.expression)], idx);
|
|
10817
11185
|
}
|
|
10818
11186
|
}
|
|
@@ -10866,7 +11234,17 @@ ${lbl}: .short 0xffff
|
|
|
10866
11234
|
indexer = assign ? attrs.indexerSet : attrs.indexerGet;
|
|
10867
11235
|
}
|
|
10868
11236
|
if (!indexer && (t.flags & (ts.TypeFlags.Any | ts.TypeFlags.StructuredOrTypeVariable))) {
|
|
10869
|
-
|
|
11237
|
+
const hasIndexSignature = !!checker.getIndexTypeOfType(t, ts.IndexKind.String) || !!checker.getIndexTypeOfType(t, ts.IndexKind.Number);
|
|
11238
|
+
// Native-only: mapSetByStringOnly resolves to the _pxt_map_set_by_string
|
|
11239
|
+
// thumb helper. The JS backend has no such runtime entry, so the generic
|
|
11240
|
+
// setter is the only valid choice there.
|
|
11241
|
+
const mapOnlySet = !pxtc.target.switches.noIfaceSpec && pxtc.target.isNative
|
|
11242
|
+
&& !!assign && !(t.flags & ts.TypeFlags.Any) && hasIndexSignature;
|
|
11243
|
+
if (mapOnlySet)
|
|
11244
|
+
bin.usesMapSetByStringOnly = true;
|
|
11245
|
+
indexer = assign
|
|
11246
|
+
? (mapOnlySet ? "pxtrt::mapSetByStringOnly" : "pxtrt::mapSetGeneric")
|
|
11247
|
+
: "pxtrt::mapGetGeneric";
|
|
10870
11248
|
stringOk = true;
|
|
10871
11249
|
}
|
|
10872
11250
|
if (indexer) {
|
|
@@ -11203,7 +11581,7 @@ ${lbl}: .short 0xffff
|
|
|
11203
11581
|
lst.push(tracked);
|
|
11204
11582
|
}
|
|
11205
11583
|
function emitPlain() {
|
|
11206
|
-
let r = mkProcCall(decl, node, args.map(
|
|
11584
|
+
let r = mkProcCall(decl, node, args.map(x => emitEscapedExpression(x, "callArgument")));
|
|
11207
11585
|
let pp = r.data;
|
|
11208
11586
|
if (args[0] && pp.proc && pp.proc.classInfo)
|
|
11209
11587
|
pp.isThis = args[0].kind == pxtc.SK.ThisKeyword;
|
|
@@ -11230,7 +11608,7 @@ ${lbl}: .short 0xffff
|
|
|
11230
11608
|
baseCtor = p.ctor;
|
|
11231
11609
|
if (!baseCtor && bin.finalPass)
|
|
11232
11610
|
throw userError(9280, lf("super() call requires an explicit constructor in base class"));
|
|
11233
|
-
let ctorArgs = args.map((x) =>
|
|
11611
|
+
let ctorArgs = args.map((x) => emitEscapedExpression(x, "callArgument"));
|
|
11234
11612
|
ctorArgs.unshift(emitThis(funcExpr));
|
|
11235
11613
|
return mkProcCallCore(baseCtor, node, ctorArgs);
|
|
11236
11614
|
}
|
|
@@ -11246,9 +11624,11 @@ ${lbl}: .short 0xffff
|
|
|
11246
11624
|
// TODO in VT accessor/field/method -> different
|
|
11247
11625
|
pxtc.U.assert(funcExpr.kind == pxtc.SK.PropertyAccessExpression);
|
|
11248
11626
|
const fieldName = funcExpr.name.text;
|
|
11627
|
+
const ifaceIndex = getIfaceMemberId(fieldName, true);
|
|
11628
|
+
bin.dynamicIfaceCalls[ifaceIndex + ""] = true;
|
|
11249
11629
|
// completely dynamic dispatch
|
|
11250
|
-
return mkMethodCall(args.map((x) =>
|
|
11251
|
-
ifaceIndex
|
|
11630
|
+
return mkMethodCall(args.map((x) => emitEscapedExpression(x, "callArgument")), {
|
|
11631
|
+
ifaceIndex,
|
|
11252
11632
|
callLocationIndex: markCallLocation(node),
|
|
11253
11633
|
noArgs
|
|
11254
11634
|
});
|
|
@@ -11272,7 +11652,7 @@ ${lbl}: .short 0xffff
|
|
|
11272
11652
|
pxtc.U.assert(false);
|
|
11273
11653
|
}
|
|
11274
11654
|
pxtc.U.assert(!bin.finalPass || info.virtualIndex != null, "!bin.finalPass || info.virtualIndex != null");
|
|
11275
|
-
return mkMethodCall(args.map((x) =>
|
|
11655
|
+
return mkMethodCall(args.map((x) => emitEscapedExpression(x, "callArgument")), {
|
|
11276
11656
|
classInfo: info.parentClassInfo,
|
|
11277
11657
|
virtualIndex: info.virtualIndex,
|
|
11278
11658
|
noArgs,
|
|
@@ -11312,9 +11692,15 @@ ${lbl}: .short 0xffff
|
|
|
11312
11692
|
return emitPlain();
|
|
11313
11693
|
}
|
|
11314
11694
|
else if (needsVCall || pxtc.target.switches.slowMethods || !forceMethod) {
|
|
11315
|
-
|
|
11316
|
-
|
|
11317
|
-
|
|
11695
|
+
const ifaceIndex = getIfaceMemberId(getName(decl), true);
|
|
11696
|
+
const isSet = noArgs && args.length == 2;
|
|
11697
|
+
const callKind = isSet ? "set" : noArgs ? "get" : "call";
|
|
11698
|
+
const helperGetSet = callKind == "call" ? "" : callKind;
|
|
11699
|
+
const countKey = `${ifaceIndex}:${args.length}:${helperGetSet || "call"}`;
|
|
11700
|
+
bin.ifaceCallCounts[countKey] = (bin.ifaceCallCounts[countKey] || 0) + 1;
|
|
11701
|
+
return mkMethodCall(args.map((x) => emitEscapedExpression(x, "callArgument")), {
|
|
11702
|
+
ifaceIndex,
|
|
11703
|
+
isSet,
|
|
11318
11704
|
callLocationIndex: markCallLocation(node),
|
|
11319
11705
|
noArgs
|
|
11320
11706
|
});
|
|
@@ -11333,7 +11719,7 @@ ${lbl}: .short 0xffff
|
|
|
11333
11719
|
// here's where we will recurse to generate funcExpr
|
|
11334
11720
|
args.unshift(funcExpr);
|
|
11335
11721
|
pxtc.U.assert(!noArgs);
|
|
11336
|
-
return mkMethodCall(args.map(x =>
|
|
11722
|
+
return mkMethodCall(args.map(x => emitEscapedExpression(x, "callArgument")), {
|
|
11337
11723
|
virtualIndex: -1,
|
|
11338
11724
|
callLocationIndex: markCallLocation(node),
|
|
11339
11725
|
noArgs
|
|
@@ -11429,6 +11815,8 @@ ${lbl}: .short 0xffff
|
|
|
11429
11815
|
}
|
|
11430
11816
|
}
|
|
11431
11817
|
function getCtor(decl) {
|
|
11818
|
+
if (!decl.members)
|
|
11819
|
+
return null;
|
|
11432
11820
|
return decl.members.filter(m => m.kind == pxtc.SK.Constructor)[0];
|
|
11433
11821
|
}
|
|
11434
11822
|
function isIfaceMemberUsed(name) {
|
|
@@ -11476,7 +11864,7 @@ ${lbl}: .short 0xffff
|
|
|
11476
11864
|
// let sig = checker.getResolvedSignature(node)
|
|
11477
11865
|
// TODO: can we have overloeads?
|
|
11478
11866
|
addDefaultParametersAndTypeCheck(checker.getResolvedSignature(node), args, ctorAttrs);
|
|
11479
|
-
let compiled = args.map((x) =>
|
|
11867
|
+
let compiled = args.map((x) => emitEscapedExpression(x, "callArgument"));
|
|
11480
11868
|
if (ctorAttrs.shim) {
|
|
11481
11869
|
// TODO need to deal with refMask and tagged ints here
|
|
11482
11870
|
// we drop 'obj' variable
|
|
@@ -11865,7 +12253,7 @@ ${lbl}: .short 0xffff
|
|
|
11865
12253
|
for (let f of initalizedFields) {
|
|
11866
12254
|
let idx = fieldIndexCore(proc.classInfo, getFieldInfo(proc.classInfo, getName(f)), false);
|
|
11867
12255
|
let trg2 = pxtc.ir.op(EK.FieldAccess, [emitLocalLoad(info.thisParameter)], idx);
|
|
11868
|
-
proc.emitExpr(pxtc.ir.op(EK.Store, [trg2,
|
|
12256
|
+
proc.emitExpr(pxtc.ir.op(EK.Store, [trg2, emitEscapedExpression(f.initializer, "initializer")]));
|
|
11869
12257
|
}
|
|
11870
12258
|
flushHoistedFunctionDefinitions();
|
|
11871
12259
|
if (node.body.kind == pxtc.SK.Block) {
|
|
@@ -12083,9 +12471,12 @@ ${lbl}: .short 0xffff
|
|
|
12083
12471
|
isRef: true,
|
|
12084
12472
|
shimName: attrs.shim,
|
|
12085
12473
|
classInfo: info,
|
|
12086
|
-
needsCheck
|
|
12474
|
+
needsCheck,
|
|
12087
12475
|
};
|
|
12088
12476
|
}
|
|
12477
|
+
function checkedFieldAccessCountKey(info) {
|
|
12478
|
+
return `${info.classInfo.id}:${info.name}:get`;
|
|
12479
|
+
}
|
|
12089
12480
|
function fieldIndex(pacc) {
|
|
12090
12481
|
const tp = typeOf(pacc.expression);
|
|
12091
12482
|
if (isPossiblyGenericClassType(tp)) {
|
|
@@ -12116,7 +12507,7 @@ ${lbl}: .short 0xffff
|
|
|
12116
12507
|
if (decl && (isGlobal || isVar(decl) || isParameter(decl))) {
|
|
12117
12508
|
let l = lookupCell(decl);
|
|
12118
12509
|
recordUse(decl, true);
|
|
12119
|
-
proc.emitExpr(l.storeByRef(
|
|
12510
|
+
proc.emitExpr(l.storeByRef(emitEscapedExpression(src, "assignment")));
|
|
12120
12511
|
}
|
|
12121
12512
|
else {
|
|
12122
12513
|
unhandled(trg, lf("bad target identifier"), 9248);
|
|
@@ -12132,7 +12523,10 @@ ${lbl}: .short 0xffff
|
|
|
12132
12523
|
}
|
|
12133
12524
|
proc.emitExpr(emitCallCore(trg, trg, [src], null, decl));
|
|
12134
12525
|
}
|
|
12135
|
-
else if (decl && (decl.kind == pxtc.SK.PropertySignature || decl.kind == pxtc.SK.PropertyAssignment
|
|
12526
|
+
else if (decl && (decl.kind == pxtc.SK.PropertySignature || decl.kind == pxtc.SK.PropertyAssignment)) {
|
|
12527
|
+
proc.emitExpr(emitCallCore(trg, trg, [src], null, decl));
|
|
12528
|
+
}
|
|
12529
|
+
else if (decl && isSlowField(decl)) {
|
|
12136
12530
|
proc.emitExpr(emitCallCore(trg, trg, [src], null, decl));
|
|
12137
12531
|
}
|
|
12138
12532
|
else {
|
|
@@ -12148,7 +12542,7 @@ ${lbl}: .short 0xffff
|
|
|
12148
12542
|
}
|
|
12149
12543
|
}
|
|
12150
12544
|
let trg2 = emitExpr(trg);
|
|
12151
|
-
proc.emitExpr(pxtc.ir.op(EK.Store, [trg2,
|
|
12545
|
+
proc.emitExpr(pxtc.ir.op(EK.Store, [trg2, emitEscapedExpression(src, "assignment")]));
|
|
12152
12546
|
break;
|
|
12153
12547
|
}
|
|
12154
12548
|
}
|
|
@@ -12559,7 +12953,7 @@ ${lbl}: .short 0xffff
|
|
|
12559
12953
|
let mask = getMask(args);
|
|
12560
12954
|
let convInfos = [];
|
|
12561
12955
|
let args2 = args.map((a, i) => {
|
|
12562
|
-
let r =
|
|
12956
|
+
let r = emitEscapedExpression(a, "runtimeCallArgument");
|
|
12563
12957
|
if (!needsNumberConversions())
|
|
12564
12958
|
return r;
|
|
12565
12959
|
let f = fmt[i + 1];
|
|
@@ -12897,6 +13291,43 @@ ${lbl}: .short 0xffff
|
|
|
12897
13291
|
emitExprAsStmt(node.expression);
|
|
12898
13292
|
}
|
|
12899
13293
|
function emitCondition(expr, inner = null) {
|
|
13294
|
+
// with `noBoolLower` flag, the generic paths below handle every condition
|
|
13295
|
+
const boolLower = !pxtc.target.switches.noBoolLower;
|
|
13296
|
+
// Lower `&&` / `||` used as a condition into short-circuiting jumps
|
|
13297
|
+
// that yield 0/1 directly, instead of boxing the intermediate
|
|
13298
|
+
// booleans and converting them back with a runtime helper.
|
|
13299
|
+
if (boolLower && !inner && !isStackMachine() && expr.kind == pxtc.SK.BinaryExpression) {
|
|
13300
|
+
const binary = expr;
|
|
13301
|
+
const op = binary.operatorToken.kind;
|
|
13302
|
+
if (op == pxtc.SK.AmpersandAmpersandToken || op == pxtc.SK.BarBarToken) {
|
|
13303
|
+
const shortCircuitLabel = proc.mkLabel("lazycond");
|
|
13304
|
+
const doneLabel = proc.mkLabel("lazycondfin");
|
|
13305
|
+
if (op == pxtc.SK.AmpersandAmpersandToken) {
|
|
13306
|
+
proc.emitJmp(shortCircuitLabel, emitCondition(binary.left), pxtc.ir.JmpMode.IfZero);
|
|
13307
|
+
proc.emitJmp(shortCircuitLabel, emitCondition(binary.right), pxtc.ir.JmpMode.IfZero);
|
|
13308
|
+
proc.emitJmp(doneLabel, pxtc.ir.numlit(1), pxtc.ir.JmpMode.Always);
|
|
13309
|
+
proc.emitLbl(shortCircuitLabel);
|
|
13310
|
+
proc.emitJmp(doneLabel, pxtc.ir.numlit(0), pxtc.ir.JmpMode.Always);
|
|
13311
|
+
}
|
|
13312
|
+
else {
|
|
13313
|
+
proc.emitJmp(shortCircuitLabel, emitCondition(binary.left), pxtc.ir.JmpMode.IfNotZero);
|
|
13314
|
+
proc.emitJmp(shortCircuitLabel, emitCondition(binary.right), pxtc.ir.JmpMode.IfNotZero);
|
|
13315
|
+
proc.emitJmp(doneLabel, pxtc.ir.numlit(0), pxtc.ir.JmpMode.Always);
|
|
13316
|
+
proc.emitLbl(shortCircuitLabel);
|
|
13317
|
+
proc.emitJmp(doneLabel, pxtc.ir.numlit(1), pxtc.ir.JmpMode.Always);
|
|
13318
|
+
}
|
|
13319
|
+
proc.emitLbl(doneLabel);
|
|
13320
|
+
return captureJmpValue();
|
|
13321
|
+
}
|
|
13322
|
+
}
|
|
13323
|
+
// Lower `!expr` used as a condition by negating the operand's
|
|
13324
|
+
// condition directly, avoiding a boxed boolean just to invert it.
|
|
13325
|
+
if (boolLower && !inner && !isStackMachine() && expr.kind == pxtc.SK.PrefixUnaryExpression) {
|
|
13326
|
+
const unary = expr;
|
|
13327
|
+
if (unary.operator == pxtc.SK.ExclamationToken) {
|
|
13328
|
+
return pxtc.ir.rtcall("Boolean_::bang", [emitCondition(unary.operand)]);
|
|
13329
|
+
}
|
|
13330
|
+
}
|
|
12900
13331
|
if (!inner && isThumb() && expr.kind == pxtc.SK.BinaryExpression) {
|
|
12901
13332
|
let be = expr;
|
|
12902
13333
|
let mapped = pxtc.U.lookup(pxtc.thumbCmpMap, simpleInstruction(be, be.operatorToken.kind));
|
|
@@ -13348,7 +13779,10 @@ ${lbl}: .short 0xffff
|
|
|
13348
13779
|
}
|
|
13349
13780
|
}
|
|
13350
13781
|
typeCheckSubtoSup(node.initializer, node);
|
|
13351
|
-
|
|
13782
|
+
const initializerExpr = isGlobalVar(node)
|
|
13783
|
+
? emitEscapedExpression(node.initializer, "initializer")
|
|
13784
|
+
: emitExpr(node.initializer);
|
|
13785
|
+
proc.emitExpr(loc.storeByRef(initializerExpr));
|
|
13352
13786
|
currJres = null;
|
|
13353
13787
|
proc.stackEmpty();
|
|
13354
13788
|
}
|
|
@@ -13373,8 +13807,17 @@ ${lbl}: .short 0xffff
|
|
|
13373
13807
|
exres = pxtc.ir.op(EK.FieldAccess, [objRef], fieldIndexCore(info, getFieldInfo(info, fieldName)));
|
|
13374
13808
|
}
|
|
13375
13809
|
else {
|
|
13810
|
+
// Dynamic field access on a non-class-typed receiver (any, structural type,
|
|
13811
|
+
// etc). The call site supplies exactly 1 arg (the receiver) but the target
|
|
13812
|
+
// proc resolved via iface dispatch may be a method that expects more --
|
|
13813
|
+
// the _args wrapper is what normally fills in the missing args. Flag this
|
|
13814
|
+
// ifaceIndex so canUseExactIfaceWrapper disqualifies the wrapper-skip
|
|
13815
|
+
// optimization for any proc reachable here. Mirrors the dynamic-method-call
|
|
13816
|
+
// path above.
|
|
13817
|
+
const ifaceIndex = getIfaceMemberId(fieldName, true);
|
|
13818
|
+
bin.dynamicIfaceCalls[ifaceIndex + ""] = true;
|
|
13376
13819
|
exres = mkMethodCall([objRef], {
|
|
13377
|
-
ifaceIndex
|
|
13820
|
+
ifaceIndex,
|
|
13378
13821
|
callLocationIndex: markCallLocation(node),
|
|
13379
13822
|
noArgs: true
|
|
13380
13823
|
});
|
|
@@ -13757,6 +14200,11 @@ ${lbl}: .short 0xffff
|
|
|
13757
14200
|
this.usedChars = new Uint32Array(0x10000 / 32);
|
|
13758
14201
|
this.explicitlyUsedIfaceMembers = {};
|
|
13759
14202
|
this.ifaceMemberMap = {};
|
|
14203
|
+
this.ifaceCallCounts = {};
|
|
14204
|
+
this.mapSetByFieldIdCounts = {};
|
|
14205
|
+
this.checkedFieldAccessCounts = {};
|
|
14206
|
+
this.dynamicIfaceCalls = {};
|
|
14207
|
+
this.usesMapSetByStringOnly = false;
|
|
13760
14208
|
this.strings = {};
|
|
13761
14209
|
this.hexlits = {};
|
|
13762
14210
|
this.doubles = {};
|
|
@@ -16207,9 +16655,9 @@ ${lbl}: ${snippets.obj_header("pxt::number_vt")}
|
|
|
16207
16655
|
uint32_t ifaceHashMult;
|
|
16208
16656
|
PVoid methods[2 or 4];
|
|
16209
16657
|
*/
|
|
16210
|
-
const ifaceInfo =
|
|
16211
|
-
|
|
16212
|
-
|
|
16658
|
+
const ifaceInfo = info.itable.length
|
|
16659
|
+
? computeHashMultiplier(info.itable.map(e => e.idx))
|
|
16660
|
+
: { mult: 0, mapping: new Uint16Array(0), size: 0 };
|
|
16213
16661
|
let ptrSz = pxtc.target.shortPointers ? ".short" : ".word";
|
|
16214
16662
|
let s = `
|
|
16215
16663
|
.object ${info.id}_VT
|
|
@@ -16243,6 +16691,11 @@ ${info.id}_IfaceVT:
|
|
|
16243
16691
|
`;
|
|
16244
16692
|
const descSize = 8;
|
|
16245
16693
|
const zeroOffset = ifaceInfo.mapping.length * 2;
|
|
16694
|
+
// Iface table is the one consumer that can elect the wrapper-skip
|
|
16695
|
+
// optimization. Procs with useExactIfaceWrapper get pointed at _iface
|
|
16696
|
+
// (which `b _nochk`); everyone else gets the usual _args wrapper entry.
|
|
16697
|
+
// canUseExactIfaceWrapper guarantees this is safe for the elected procs.
|
|
16698
|
+
const procIfaceLabel = (p) => (p.useExactIfaceWrapper ? p.label() + "_iface" : p.vtLabel()) + "@fn";
|
|
16246
16699
|
let descs = "";
|
|
16247
16700
|
let offset = zeroOffset;
|
|
16248
16701
|
let offsets = {};
|
|
@@ -16250,11 +16703,11 @@ ${info.id}_IfaceVT:
|
|
|
16250
16703
|
offsets[e.idx + ""] = offset;
|
|
16251
16704
|
const desc = !e.proc ? 0 : e.proc.isGetter() ? 1 : 2;
|
|
16252
16705
|
descs += ` .short ${e.idx}, ${desc} ; ${e.name}\n`;
|
|
16253
|
-
descs += ` .word ${e.proc ? e.proc
|
|
16706
|
+
descs += ` .word ${e.proc ? procIfaceLabel(e.proc) : e.info}\n`;
|
|
16254
16707
|
offset += descSize;
|
|
16255
16708
|
if (e.setProc) {
|
|
16256
16709
|
descs += ` .short ${e.idx}, 0 ; set ${e.name}\n`;
|
|
16257
|
-
descs += ` .word ${e.setProc
|
|
16710
|
+
descs += ` .word ${procIfaceLabel(e.setProc)}\n`;
|
|
16258
16711
|
offset += descSize;
|
|
16259
16712
|
}
|
|
16260
16713
|
}
|
|
@@ -16267,7 +16720,9 @@ ${info.id}_IfaceVT:
|
|
|
16267
16720
|
bin.itFullEntries++;
|
|
16268
16721
|
}
|
|
16269
16722
|
// offsets are relative to the position in the array
|
|
16270
|
-
|
|
16723
|
+
const hashOffsets = pxtc.U.toArray(map).map((e, i) => (offsets[e + ""] || zeroOffset) - (i * 2));
|
|
16724
|
+
if (hashOffsets.length)
|
|
16725
|
+
s += " .short " + hashOffsets.join(", ") + "\n";
|
|
16271
16726
|
s += descs;
|
|
16272
16727
|
s += "\n";
|
|
16273
16728
|
return s;
|