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/pxt.js
CHANGED
|
@@ -108611,8 +108611,29 @@ var pxt;
|
|
|
108611
108611
|
}
|
|
108612
108612
|
async loadConfigAsync(repopath, tag) {
|
|
108613
108613
|
if (!tag) {
|
|
108614
|
-
pxt.debug(`dep:
|
|
108615
|
-
|
|
108614
|
+
pxt.debug(`dep: no tag specified, try master and main`);
|
|
108615
|
+
let res;
|
|
108616
|
+
let error;
|
|
108617
|
+
try {
|
|
108618
|
+
res = await this.loadConfigAsync(repopath, "master");
|
|
108619
|
+
}
|
|
108620
|
+
catch (e) {
|
|
108621
|
+
error = e;
|
|
108622
|
+
pxt.log(`Error loading default master config for ${repopath}: ${e.message}`);
|
|
108623
|
+
}
|
|
108624
|
+
if (res)
|
|
108625
|
+
return res;
|
|
108626
|
+
try {
|
|
108627
|
+
res = await this.loadConfigAsync(repopath, "main");
|
|
108628
|
+
}
|
|
108629
|
+
catch (e) {
|
|
108630
|
+
error = e;
|
|
108631
|
+
pxt.log(`Error loading default main config for ${repopath}: ${e.message}`);
|
|
108632
|
+
}
|
|
108633
|
+
if (res)
|
|
108634
|
+
return res;
|
|
108635
|
+
else
|
|
108636
|
+
throw error;
|
|
108616
108637
|
}
|
|
108617
108638
|
// cache lookup
|
|
108618
108639
|
const key = `${repopath}/${tag}`;
|
|
@@ -108646,8 +108667,29 @@ var pxt;
|
|
|
108646
108667
|
}
|
|
108647
108668
|
async loadPackageAsync(repopath, tag, fallbackPackageFiles) {
|
|
108648
108669
|
if (!tag) {
|
|
108649
|
-
pxt.debug(`load pkg:
|
|
108650
|
-
|
|
108670
|
+
pxt.debug(`load pkg: no tag specified, try master and main`);
|
|
108671
|
+
let res;
|
|
108672
|
+
let error;
|
|
108673
|
+
try {
|
|
108674
|
+
res = await this.loadPackageAsync(repopath, "master", fallbackPackageFiles);
|
|
108675
|
+
}
|
|
108676
|
+
catch (e) {
|
|
108677
|
+
error = e;
|
|
108678
|
+
pxt.log(`Error loading default master package for ${repopath}: ${e.message}`);
|
|
108679
|
+
}
|
|
108680
|
+
if (res)
|
|
108681
|
+
return res;
|
|
108682
|
+
try {
|
|
108683
|
+
res = await this.loadPackageAsync(repopath, "main", fallbackPackageFiles);
|
|
108684
|
+
}
|
|
108685
|
+
catch (e) {
|
|
108686
|
+
error = e;
|
|
108687
|
+
pxt.log(`Error loading default main package for ${repopath}: ${e.message}`);
|
|
108688
|
+
}
|
|
108689
|
+
if (res)
|
|
108690
|
+
return res;
|
|
108691
|
+
else
|
|
108692
|
+
throw error;
|
|
108651
108693
|
}
|
|
108652
108694
|
// try using github proxy first
|
|
108653
108695
|
if (hasProxy()) {
|
|
@@ -108933,16 +108975,27 @@ var pxt;
|
|
|
108933
108975
|
});
|
|
108934
108976
|
}
|
|
108935
108977
|
github.mergeAsync = mergeAsync;
|
|
108936
|
-
function getRefAsync(repopath, branch) {
|
|
108937
|
-
|
|
108938
|
-
|
|
108939
|
-
|
|
108940
|
-
|
|
108941
|
-
|
|
108978
|
+
async function getRefAsync(repopath, branch) {
|
|
108979
|
+
if (!branch) {
|
|
108980
|
+
// try master and main
|
|
108981
|
+
let res = await getRefAsync(repopath, "master");
|
|
108982
|
+
if (!res)
|
|
108983
|
+
res = await getRefAsync(repopath, "main");
|
|
108984
|
+
return res;
|
|
108985
|
+
}
|
|
108986
|
+
try {
|
|
108987
|
+
const ref = await ghGetJsonAsync("https://api.github.com/repos/" + repopath + "/git/refs/heads/" + branch);
|
|
108988
|
+
return resolveRefAsync(ref);
|
|
108989
|
+
}
|
|
108990
|
+
catch (e) {
|
|
108991
|
+
pxt.debug(`Error fetching ref for ${repopath} branch ${branch}: ${e.message}`);
|
|
108992
|
+
if (e.statusCode === 404) {
|
|
108942
108993
|
return undefined;
|
|
108943
|
-
|
|
108944
|
-
|
|
108945
|
-
|
|
108994
|
+
}
|
|
108995
|
+
else {
|
|
108996
|
+
throw e;
|
|
108997
|
+
}
|
|
108998
|
+
}
|
|
108946
108999
|
}
|
|
108947
109000
|
github.getRefAsync = getRefAsync;
|
|
108948
109001
|
function generateNextRefName(res, pref) {
|
|
@@ -126069,6 +126122,10 @@ ${baseLabel}_nochk:
|
|
|
126069
126122
|
}
|
|
126070
126123
|
emitFieldAccess(e, store = false) {
|
|
126071
126124
|
let info = e.data;
|
|
126125
|
+
if (!store && this.shouldSpecializeCheckedFieldLoad(info)) {
|
|
126126
|
+
this.emitCheckedFieldLoad(info);
|
|
126127
|
+
return;
|
|
126128
|
+
}
|
|
126072
126129
|
let pref = store ? "st" : "ld";
|
|
126073
126130
|
let lbl = pref + "fld_" + info.classInfo.id + "_" + info.name;
|
|
126074
126131
|
if (info.needsCheck && !pxtc.target.switches.skipClassCheck) {
|
|
@@ -126087,6 +126144,32 @@ ${baseLabel}_nochk:
|
|
|
126087
126144
|
this.write(`ldr r0, [r0, ${xoff}]`);
|
|
126088
126145
|
return;
|
|
126089
126146
|
}
|
|
126147
|
+
checkedFieldAccessCountKey(info) {
|
|
126148
|
+
return `${info.classInfo.id}:${info.name}:get`;
|
|
126149
|
+
}
|
|
126150
|
+
shouldSpecializeCheckedFieldLoad(info) {
|
|
126151
|
+
if (!pxtc.isThumb() || !this.bin.finalPass || pxtc.target.switches.noIfaceSpec)
|
|
126152
|
+
return false;
|
|
126153
|
+
if (pxtc.target.switches.skipClassCheck || !info.needsCheck)
|
|
126154
|
+
return false;
|
|
126155
|
+
return (this.bin.checkedFieldAccessCounts[this.checkedFieldAccessCountKey(info)] || 0) >= 5;
|
|
126156
|
+
}
|
|
126157
|
+
emitCheckedFieldLoad(info) {
|
|
126158
|
+
const helperKey = "ldfldchk_" + info.classInfo.id + "_" + info.name;
|
|
126159
|
+
const helper = this.ensureLabelledHelper(helperKey, () => {
|
|
126160
|
+
this.write(this.t.helper_prologue());
|
|
126161
|
+
this.emitInstanceOf(info.classInfo, "validate");
|
|
126162
|
+
let off = info.idx * 4 + 4;
|
|
126163
|
+
let xoff = "#" + off;
|
|
126164
|
+
if (off > 124) {
|
|
126165
|
+
this.write(this.t.emit_int(off, "r3"));
|
|
126166
|
+
xoff = "r3";
|
|
126167
|
+
}
|
|
126168
|
+
this.write(`ldr r0, [r0, ${xoff}]`);
|
|
126169
|
+
this.write(this.t.helper_epilogue());
|
|
126170
|
+
});
|
|
126171
|
+
this.write(this.t.call_lbl(helper, false, this.stackAlignmentNeeded(0)));
|
|
126172
|
+
}
|
|
126090
126173
|
writeFailBranch() {
|
|
126091
126174
|
this.write(`.fail:`);
|
|
126092
126175
|
this.write(`mov r1, lr`);
|
|
@@ -126307,12 +126390,50 @@ ${baseLabel}_nochk:
|
|
|
126307
126390
|
}
|
|
126308
126391
|
emitIfaceCall(procid, numargs, getset = "") {
|
|
126309
126392
|
pxtc.U.assert(procid.ifaceIndex > 0);
|
|
126310
|
-
|
|
126311
|
-
this.
|
|
126393
|
+
const helperKey = "ifacecall" + numargs + "_" + getset;
|
|
126394
|
+
const helper = this.ensureLabelledHelper(helperKey, () => {
|
|
126312
126395
|
this.write(`ldr r0, [sp, #0] ; ld-this`);
|
|
126313
126396
|
this.loadVTable();
|
|
126314
126397
|
this.ifaceCallCore(numargs, getset);
|
|
126315
126398
|
});
|
|
126399
|
+
if (this.shouldSpecializeIfaceCall(procid.ifaceIndex, numargs, getset)) {
|
|
126400
|
+
const thunkKey = helperKey + "_i" + procid.ifaceIndex;
|
|
126401
|
+
this.emitLabelledHelper(thunkKey, () => {
|
|
126402
|
+
this.write(this.t.emit_int(procid.ifaceIndex, "r1"));
|
|
126403
|
+
this.write(`ldlit r7, ${helper}@fn`);
|
|
126404
|
+
this.write(`bx r7`);
|
|
126405
|
+
});
|
|
126406
|
+
}
|
|
126407
|
+
else {
|
|
126408
|
+
this.write(this.t.emit_int(procid.ifaceIndex, "r1"));
|
|
126409
|
+
this.write(this.t.call_lbl(helper));
|
|
126410
|
+
}
|
|
126411
|
+
}
|
|
126412
|
+
ifaceCallCountKey(ifaceIndex, numargs, getset) {
|
|
126413
|
+
return `${ifaceIndex}:${numargs}:${getset || "call"}`;
|
|
126414
|
+
}
|
|
126415
|
+
// Count-driven specialization decisions (this method and the related
|
|
126416
|
+
// shouldSpecializeCheckedFieldLoad / shouldSpecializeMapSetByFieldId) assume:
|
|
126417
|
+
// - bin.finalPass is true, AND
|
|
126418
|
+
// - the count maps were populated during the same final-pass emit()
|
|
126419
|
+
// call that builds the IR this asm walk consumes.
|
|
126420
|
+
// This is currently true because the frontend populates counts during IR
|
|
126421
|
+
// generation and the backend reads them only during the subsequent asm walk.
|
|
126422
|
+
// Any future change that interleaves IR generation with asm emission would
|
|
126423
|
+
// silently regress these to "always false".
|
|
126424
|
+
//
|
|
126425
|
+
// Threshold of 3 is the break-even point for the labelled-thunk pattern:
|
|
126426
|
+
// each shared call site saves ~4 bytes vs an inline call, and the thunk
|
|
126427
|
+
// itself costs ~12 bytes. At 3 callers, savings break even with thunk cost.
|
|
126428
|
+
shouldSpecializeIfaceCall(ifaceIndex, numargs, getset) {
|
|
126429
|
+
if (!pxtc.isThumb() || !this.bin.finalPass || pxtc.target.switches.noIfaceSpec)
|
|
126430
|
+
return false;
|
|
126431
|
+
const count = this.bin.ifaceCallCounts[this.ifaceCallCountKey(ifaceIndex, numargs, getset)] || 0;
|
|
126432
|
+
if (getset == "get" && numargs == 1)
|
|
126433
|
+
return count >= 3;
|
|
126434
|
+
if (getset == "set" || !getset)
|
|
126435
|
+
return count >= 3;
|
|
126436
|
+
return false;
|
|
126316
126437
|
}
|
|
126317
126438
|
// vtable in r3; clobber r2
|
|
126318
126439
|
checkSubtype(info, failLbl = ".fail", r2 = "r2") {
|
|
@@ -126410,6 +126531,8 @@ ${baseLabel}_nochk:
|
|
|
126410
126531
|
}
|
|
126411
126532
|
emitRtCall(topExpr, genCall = null) {
|
|
126412
126533
|
let name = topExpr.data;
|
|
126534
|
+
const mapSetFieldId = this.mapSetByFieldId(topExpr);
|
|
126535
|
+
const specializeMapSetFieldId = mapSetFieldId !== undefined && this.shouldSpecializeMapSetByFieldId(mapSetFieldId);
|
|
126413
126536
|
if (name == "pxt::beginTry") {
|
|
126414
126537
|
return this.emitBeginTry(topExpr);
|
|
126415
126538
|
}
|
|
@@ -126422,6 +126545,8 @@ ${baseLabel}_nochk:
|
|
|
126422
126545
|
isRef: (maskInfo.refMask & (1 << i)) != 0,
|
|
126423
126546
|
conv: convs.find(c => c.argIdx == i)
|
|
126424
126547
|
}));
|
|
126548
|
+
if (specializeMapSetFieldId)
|
|
126549
|
+
allArgs = allArgs.filter(a => a.idx != 1);
|
|
126425
126550
|
pxtc.U.assert(allArgs.length <= 4);
|
|
126426
126551
|
let seenUpdate = false;
|
|
126427
126552
|
for (let a of pxtc.U.reversed(allArgs)) {
|
|
@@ -126524,33 +126649,69 @@ ${baseLabel}_nochk:
|
|
|
126524
126649
|
genCall();
|
|
126525
126650
|
}
|
|
126526
126651
|
else {
|
|
126527
|
-
if (
|
|
126652
|
+
if (specializeMapSetFieldId)
|
|
126653
|
+
this.emitMapSetByFieldIdCall(mapSetFieldId);
|
|
126654
|
+
else if (name != "langsupp::ignore")
|
|
126528
126655
|
this.alignedCall(name, "", 0, true);
|
|
126529
126656
|
}
|
|
126530
126657
|
if (clearStack) {
|
|
126531
126658
|
this.clearArgs(complexArgs.filter(a => !a.isRef).map(a => a.expr), complexArgs.filter(a => a.isRef).map(a => a.expr));
|
|
126532
126659
|
}
|
|
126533
126660
|
}
|
|
126661
|
+
mapSetByFieldId(topExpr) {
|
|
126662
|
+
if (topExpr.data != "pxtrt::mapSet" || topExpr.args.length != 3)
|
|
126663
|
+
return undefined;
|
|
126664
|
+
const fieldId = topExpr.args[1];
|
|
126665
|
+
if (fieldId.exprKind != pxtc.ir.EK.NumberLiteral || typeof fieldId.data != "number")
|
|
126666
|
+
return undefined;
|
|
126667
|
+
return fieldId.data;
|
|
126668
|
+
}
|
|
126669
|
+
shouldSpecializeMapSetByFieldId(fieldId) {
|
|
126670
|
+
if (!pxtc.isThumb() || !this.bin.finalPass || pxtc.target.switches.noIfaceSpec)
|
|
126671
|
+
return false;
|
|
126672
|
+
return (this.bin.mapSetByFieldIdCounts[fieldId + ""] || 0) >= 3;
|
|
126673
|
+
}
|
|
126674
|
+
emitMapSetByFieldIdCall(fieldId) {
|
|
126675
|
+
const helper = this.ensureLabelledHelper("mapset_i" + fieldId, () => {
|
|
126676
|
+
this.write(this.t.helper_prologue());
|
|
126677
|
+
this.write(this.t.emit_int(fieldId, "r1"));
|
|
126678
|
+
this.write(this.t.callCPP("pxtrt::mapSet"));
|
|
126679
|
+
this.write(this.t.helper_epilogue());
|
|
126680
|
+
});
|
|
126681
|
+
this.write(this.t.call_lbl(helper, false, this.stackAlignmentNeeded(0)));
|
|
126682
|
+
}
|
|
126534
126683
|
alignedCall(name, cmt = "", off = 0, saveStack = false) {
|
|
126535
126684
|
if (pxtc.U.startsWith(name, "_cmp_") || pxtc.U.startsWith(name, "_pxt_"))
|
|
126536
126685
|
saveStack = false;
|
|
126537
126686
|
this.write(this.t.call_lbl(name, saveStack, this.stackAlignmentNeeded(off)) + cmt);
|
|
126538
126687
|
}
|
|
126539
|
-
|
|
126688
|
+
// Lazily emit a deduped helper body and return its label.
|
|
126689
|
+
// Use this when you want to BRANCH to (or reference) the helper without
|
|
126690
|
+
// emitting a `bl` at the current cursor -- e.g. when wrapping the call in
|
|
126691
|
+
// a thunk, or storing the label for later use.
|
|
126692
|
+
ensureLabelledHelper(lbl, generate) {
|
|
126540
126693
|
if (!this.labelledHelpers[lbl]) {
|
|
126541
126694
|
let outp = this.redirectOutput(generate);
|
|
126542
|
-
this.
|
|
126543
|
-
this.labelledHelpers[lbl] = this.bin.codeHelpers[outp];
|
|
126544
|
-
}
|
|
126545
|
-
else {
|
|
126546
|
-
this.write(this.t.call_lbl(this.labelledHelpers[lbl]));
|
|
126695
|
+
this.labelledHelpers[lbl] = this.helperLabel(outp, lbl);
|
|
126547
126696
|
}
|
|
126697
|
+
return this.labelledHelpers[lbl];
|
|
126548
126698
|
}
|
|
126549
|
-
|
|
126699
|
+
// Ensure a deduped helper exists and emit a `bl` to it at the cursor.
|
|
126700
|
+
// Use this for the common case of "factor this sequence into a helper and call it here".
|
|
126701
|
+
emitLabelledHelper(lbl, generate) {
|
|
126702
|
+
const helper = this.ensureLabelledHelper(lbl, generate);
|
|
126703
|
+
this.write(this.t.call_lbl(helper));
|
|
126704
|
+
return helper;
|
|
126705
|
+
}
|
|
126706
|
+
helperLabel(asm, baseName = "hlp") {
|
|
126550
126707
|
if (!this.bin.codeHelpers[asm]) {
|
|
126551
126708
|
let len = Object.keys(this.bin.codeHelpers).length;
|
|
126552
126709
|
this.bin.codeHelpers[asm] = `_${baseName}_${len}`;
|
|
126553
126710
|
}
|
|
126711
|
+
return this.bin.codeHelpers[asm];
|
|
126712
|
+
}
|
|
126713
|
+
emitHelper(asm, baseName = "hlp") {
|
|
126714
|
+
this.helperLabel(asm, baseName);
|
|
126554
126715
|
this.write(this.t.call_lbl(this.bin.codeHelpers[asm]));
|
|
126555
126716
|
}
|
|
126556
126717
|
pushToExprStack(a) {
|
|
@@ -126587,48 +126748,85 @@ ${baseLabel}_nochk:
|
|
|
126587
126748
|
}
|
|
126588
126749
|
}
|
|
126589
126750
|
}
|
|
126590
|
-
|
|
126591
|
-
|
|
126592
|
-
this.write(`
|
|
126751
|
+
emitFieldMethod(op) {
|
|
126752
|
+
this.write(`
|
|
126593
126753
|
${this.helperObject(op)}
|
|
126594
126754
|
.section code
|
|
126595
126755
|
_pxt_map_${op}:
|
|
126596
126756
|
`);
|
|
126597
|
-
|
|
126598
|
-
|
|
126599
|
-
|
|
126600
|
-
|
|
126601
|
-
|
|
126602
|
-
|
|
126603
|
-
|
|
126604
|
-
|
|
126605
|
-
|
|
126606
|
-
|
|
126607
|
-
|
|
126608
|
-
|
|
126609
|
-
|
|
126757
|
+
this.loadVTable("r4");
|
|
126758
|
+
this.checkSubtype(this.builtInClassNo(pxt.BuiltInType.RefMap), ".notmap", "r4");
|
|
126759
|
+
this.write(this.t.callCPPPush(op == "set" ? "pxtrt::mapSetByString" : "pxtrt::mapGetByString"));
|
|
126760
|
+
this.write(".notmap:");
|
|
126761
|
+
let numargs = op == "set" ? 2 : 1;
|
|
126762
|
+
let hasAlign = false;
|
|
126763
|
+
this.write("mov r4, r3 ; save VT");
|
|
126764
|
+
if (op == "set") {
|
|
126765
|
+
if (pxtc.target.stackAlign) {
|
|
126766
|
+
hasAlign = true;
|
|
126767
|
+
this.write("push {lr} ; align");
|
|
126768
|
+
}
|
|
126769
|
+
this.write(`
|
|
126610
126770
|
push {r0, r2, lr}
|
|
126611
126771
|
mov r0, r1
|
|
126612
126772
|
`);
|
|
126613
|
-
|
|
126614
|
-
|
|
126615
|
-
|
|
126773
|
+
}
|
|
126774
|
+
else {
|
|
126775
|
+
this.write(`
|
|
126616
126776
|
push {r0, lr}
|
|
126617
126777
|
mov r0, r1
|
|
126618
126778
|
`);
|
|
126619
|
-
|
|
126620
|
-
|
|
126779
|
+
}
|
|
126780
|
+
this.write(`
|
|
126621
126781
|
bl pxtrt::lookupMapKey
|
|
126622
126782
|
mov r1, r0 ; put key index in r1
|
|
126623
126783
|
ldr r0, [sp, #0] ; restore obj pointer
|
|
126624
126784
|
mov r3, r4 ; restore vt
|
|
126625
126785
|
bl .dowork
|
|
126626
126786
|
`);
|
|
126627
|
-
|
|
126628
|
-
|
|
126629
|
-
|
|
126630
|
-
|
|
126631
|
-
|
|
126787
|
+
this.write(this.t.pop_locals(numargs + (hasAlign ? 1 : 0)));
|
|
126788
|
+
this.write("pop {pc}");
|
|
126789
|
+
this.write(".dowork:");
|
|
126790
|
+
this.ifaceCallCore(numargs, op, true);
|
|
126791
|
+
}
|
|
126792
|
+
emitStringMapSetMethod() {
|
|
126793
|
+
// Fast path for the well-typed "map with string index signature" case.
|
|
126794
|
+
// - tagged-int / null receivers: unrecoverable, panic with failedCast (matches the
|
|
126795
|
+
// pre-optimization mapSetGeneric path, which also can't proceed on these).
|
|
126796
|
+
// - non-RefMap pointer receivers: tail-call _pxt_map_set, which handles
|
|
126797
|
+
// non-map objects via interface dispatch. Preserves back-compat for code that
|
|
126798
|
+
// casts non-map objects to a string-index-signature type.
|
|
126799
|
+
//
|
|
126800
|
+
// Emitted only when the final-pass IR selected mapSetByStringOnly
|
|
126801
|
+
// (never under `noIfaceSpec`): unreferenced helper text costs image
|
|
126802
|
+
// space in every program otherwise.
|
|
126803
|
+
if (!this.bin.usesMapSetByStringOnly)
|
|
126804
|
+
return;
|
|
126805
|
+
this.write(`
|
|
126806
|
+
${this.helperObject("set string map")}
|
|
126807
|
+
.section code
|
|
126808
|
+
_pxt_map_set_by_string:
|
|
126809
|
+
lsls r4, r0, #30
|
|
126810
|
+
bne .badtype
|
|
126811
|
+
cmp r0, #0
|
|
126812
|
+
beq .badtype
|
|
126813
|
+
ldr r3, [r0, #0]
|
|
126814
|
+
`);
|
|
126815
|
+
this.write(`
|
|
126816
|
+
ldrh r4, [r3, #8]
|
|
126817
|
+
cmp r4, #${pxt.BuiltInType.RefMap}
|
|
126818
|
+
bne .fallback
|
|
126819
|
+
`);
|
|
126820
|
+
this.write(this.t.callCPPPush("pxtrt::mapSetByString"));
|
|
126821
|
+
this.write(`
|
|
126822
|
+
.fallback:
|
|
126823
|
+
b _pxt_map_set
|
|
126824
|
+
.badtype:
|
|
126825
|
+
mov r1, lr
|
|
126826
|
+
mov r7, sp
|
|
126827
|
+
str r7, [r6, #4]
|
|
126828
|
+
bl pxt::failedCast
|
|
126829
|
+
`);
|
|
126632
126830
|
}
|
|
126633
126831
|
emitArrayMethod(op, isBuffer) {
|
|
126634
126832
|
this.write(`
|
|
@@ -126714,6 +126912,12 @@ ${baseLabel}_nochk:
|
|
|
126714
126912
|
this.emitArrayMethod(op, false);
|
|
126715
126913
|
}
|
|
126716
126914
|
}
|
|
126915
|
+
emitFieldMethods() {
|
|
126916
|
+
for (let op of ["get", "set"]) {
|
|
126917
|
+
this.emitFieldMethod(op);
|
|
126918
|
+
}
|
|
126919
|
+
this.emitStringMapSetMethod();
|
|
126920
|
+
}
|
|
126717
126921
|
emitLambdaTrampoline() {
|
|
126718
126922
|
let r3 = pxtc.target.stackAlign ? "r3," : "";
|
|
126719
126923
|
this.write(`
|
|
@@ -127061,6 +127265,12 @@ ${baseLabel}_nochk:
|
|
|
127061
127265
|
this.write(`.short 0, 0 ; no captured vars`);
|
|
127062
127266
|
this.write(`.word ${this.proc.label()}_args@fn`);
|
|
127063
127267
|
}
|
|
127268
|
+
if (this.proc.useExactIfaceWrapper) {
|
|
127269
|
+
this.write(`${this.proc.label()}_iface:`);
|
|
127270
|
+
this.write(`b ${this.proc.label()}_nochk`);
|
|
127271
|
+
if (!this.proc.info.usedAsValue)
|
|
127272
|
+
return;
|
|
127273
|
+
}
|
|
127064
127274
|
this.write(`${this.proc.label()}_args:`);
|
|
127065
127275
|
let numargs = this.proc.args.length;
|
|
127066
127276
|
if (numargs == 0)
|
|
@@ -127829,6 +128039,17 @@ var ts;
|
|
|
127829
128039
|
"pxt::fromInt": "_numops_fromInt",
|
|
127830
128040
|
"pxt::switch_eq": "_pxt_switch_eq",
|
|
127831
128041
|
};
|
|
128042
|
+
// Boolean conversion fast paths, kept out of inlineArithmetic so the
|
|
128043
|
+
// `noBoolLower` flag can drop the group (redirects and helper bodies alike).
|
|
128044
|
+
const inlineBoolOps = {
|
|
128045
|
+
"pxt::fromBool": "_pxt_fromBool",
|
|
128046
|
+
"numops::toBool": "_numops_toBool",
|
|
128047
|
+
"numops::toBoolDecr": "_numops_toBoolDecr",
|
|
128048
|
+
"Boolean_::bang": "_pxt_boolean_bang",
|
|
128049
|
+
};
|
|
128050
|
+
function boolLowerEnabled() {
|
|
128051
|
+
return !pxtc.target.switches.noBoolLower;
|
|
128052
|
+
}
|
|
127832
128053
|
// snippets for ARM Thumb assembly
|
|
127833
128054
|
class ThumbSnippets extends pxtc.AssemblerSnippets {
|
|
127834
128055
|
stackAligned() {
|
|
@@ -127936,6 +128157,8 @@ ${lbl}:`;
|
|
|
127936
128157
|
}
|
|
127937
128158
|
call_lbl(lbl, saveStack, stackAlign) {
|
|
127938
128159
|
let o = pxtc.U.lookup(inlineArithmetic, lbl);
|
|
128160
|
+
if (!o && boolLowerEnabled())
|
|
128161
|
+
o = pxtc.U.lookup(inlineBoolOps, lbl);
|
|
127939
128162
|
if (o) {
|
|
127940
128163
|
lbl = o;
|
|
127941
128164
|
saveStack = false;
|
|
@@ -128107,6 +128330,99 @@ _numops_fromInt:
|
|
|
128107
128330
|
blx lr
|
|
128108
128331
|
.over2:
|
|
128109
128332
|
${this.callCPPPush("pxt::fromInt")}
|
|
128333
|
+
`;
|
|
128334
|
+
// skipped under `noBoolLower` flag: unreferenced helper text still costs image space
|
|
128335
|
+
if (boolLowerEnabled())
|
|
128336
|
+
r += `
|
|
128337
|
+
; Tag a raw 0/1 truth value into a boolean object (taggedTrue/taggedFalse).
|
|
128338
|
+
; Inverse of the toBool fast paths below; matches C++ pxt::fromBool.
|
|
128339
|
+
_pxt_fromBool:
|
|
128340
|
+
@scope _pxt_fromBool
|
|
128341
|
+
cmp r0, #0
|
|
128342
|
+
beq .false
|
|
128343
|
+
.true:
|
|
128344
|
+
movs r0, #${pxtc.taggedTrue}
|
|
128345
|
+
blx lr
|
|
128346
|
+
.false:
|
|
128347
|
+
movs r0, #${pxtc.taggedFalse}
|
|
128348
|
+
blx lr
|
|
128349
|
+
|
|
128350
|
+
; Logical NOT of a raw 0/1 truth value -> raw 0/1 (matches C++ bool bang(bool)).
|
|
128351
|
+
; Callers pass an already-tested condition, so this is raw-in / raw-out; value
|
|
128352
|
+
; context (x = !y) wraps the result with fromBool to re-tag it.
|
|
128353
|
+
_pxt_boolean_bang:
|
|
128354
|
+
@scope _pxt_boolean_bang
|
|
128355
|
+
cmp r0, #0
|
|
128356
|
+
beq .true
|
|
128357
|
+
.false:
|
|
128358
|
+
movs r0, #0
|
|
128359
|
+
blx lr
|
|
128360
|
+
.true:
|
|
128361
|
+
movs r0, #1
|
|
128362
|
+
blx lr
|
|
128363
|
+
|
|
128364
|
+
; Fast-path truthiness test: tagged value in r0 -> raw 0/1 in r0.
|
|
128365
|
+
; Value encodings (see taggedSpecialValue in emitter.ts): tagged int = (n<<1)|1
|
|
128366
|
+
; (low bit set); special tags = (n<<2)|2 with undefined=0, null, false, NaN,
|
|
128367
|
+
; true; heap objects have low 2 bits 00.
|
|
128368
|
+
; Only non-pointer values are decided inline here; any heap object is handed to
|
|
128369
|
+
; the C++ helper at .boxed (which also handles boxed numbers/strings like 0.0
|
|
128370
|
+
; and ""). INVARIANT: the falsy set below must match C++ numops::toBool exactly
|
|
128371
|
+
; -- if a new falsy tagged value is ever added, it must be added here too, or it
|
|
128372
|
+
; will fall through to .true.
|
|
128373
|
+
_numops_toBool:
|
|
128374
|
+
@scope _numops_toBool
|
|
128375
|
+
cmp r0, #0 ; undefined (0) -> false
|
|
128376
|
+
beq .false
|
|
128377
|
+
cmp r0, #1 ; integer 0 ((0<<1)|1) -> false
|
|
128378
|
+
beq .false
|
|
128379
|
+
lsls r1, r0, #31 ; odd => nonzero tagged int -> true
|
|
128380
|
+
bne .true
|
|
128381
|
+
cmp r0, #${pxtc.taggedNull} ; null -> false
|
|
128382
|
+
beq .false
|
|
128383
|
+
cmp r0, #${pxtc.taggedFalse} ; false -> false
|
|
128384
|
+
beq .false
|
|
128385
|
+
cmp r0, #${pxtc.taggedNaN} ; NaN -> false
|
|
128386
|
+
beq .false
|
|
128387
|
+
lsls r1, r0, #30 ; low 2 bits 00 => heap object -> defer to C++
|
|
128388
|
+
beq .boxed
|
|
128389
|
+
.true: ; remaining specials (e.g. true) are truthy
|
|
128390
|
+
movs r0, #1
|
|
128391
|
+
blx lr
|
|
128392
|
+
.false:
|
|
128393
|
+
movs r0, #0
|
|
128394
|
+
blx lr
|
|
128395
|
+
.boxed:
|
|
128396
|
+
${this.callCPPPush("numops::toBool")}
|
|
128397
|
+
|
|
128398
|
+
; Same truthiness classification as _numops_toBool (see comments above), but
|
|
128399
|
+
; this variant also consumes (ref-decrements) its argument. Only heap objects
|
|
128400
|
+
; are ref-counted, and those go through the C++ fallback which does the decr;
|
|
128401
|
+
; the inline non-pointer paths carry no refcount, so they need none.
|
|
128402
|
+
_numops_toBoolDecr:
|
|
128403
|
+
@scope _numops_toBoolDecr
|
|
128404
|
+
cmp r0, #0 ; undefined (0) -> false
|
|
128405
|
+
beq .false
|
|
128406
|
+
cmp r0, #1 ; integer 0 ((0<<1)|1) -> false
|
|
128407
|
+
beq .false
|
|
128408
|
+
lsls r1, r0, #31 ; odd => nonzero tagged int -> true
|
|
128409
|
+
bne .true
|
|
128410
|
+
cmp r0, #${pxtc.taggedNull} ; null -> false
|
|
128411
|
+
beq .false
|
|
128412
|
+
cmp r0, #${pxtc.taggedFalse} ; false -> false
|
|
128413
|
+
beq .false
|
|
128414
|
+
cmp r0, #${pxtc.taggedNaN} ; NaN -> false
|
|
128415
|
+
beq .false
|
|
128416
|
+
lsls r1, r0, #30 ; low 2 bits 00 => heap object -> defer to C++
|
|
128417
|
+
beq .boxed
|
|
128418
|
+
.true:
|
|
128419
|
+
movs r0, #1
|
|
128420
|
+
blx lr
|
|
128421
|
+
.false:
|
|
128422
|
+
movs r0, #0
|
|
128423
|
+
blx lr
|
|
128424
|
+
.boxed:
|
|
128425
|
+
${this.callCPPPush("numops::toBoolDecr")}
|
|
128110
128426
|
`;
|
|
128111
128427
|
for (let op of Object.keys(pxtc.thumbCmpMap)) {
|
|
128112
128428
|
op = op.replace(/.*::/, "");
|
|
@@ -128131,9 +128447,10 @@ _cmp_${op}:
|
|
|
128131
128447
|
// the cmp isn't really needed, given how toBoolDecr() is compiled,
|
|
128132
128448
|
// but better not rely on it
|
|
128133
128449
|
// Also, cmp isn't needed when ref-counting (it ends with movs r0, r4)
|
|
128450
|
+
const toBoolDecr = boolLowerEnabled() ? "_numops_toBoolDecr" : "numops::toBoolDecr";
|
|
128134
128451
|
r += boxedOp(`
|
|
128135
128452
|
bl numops::${op}
|
|
128136
|
-
bl
|
|
128453
|
+
bl ${toBoolDecr}
|
|
128137
128454
|
cmp r0, #0`);
|
|
128138
128455
|
}
|
|
128139
128456
|
// switch(x){case k:} compiles to equality tests via pxt::switch_eq.
|
|
@@ -133579,6 +133896,7 @@ var ts;
|
|
|
133579
133896
|
this.classInfo = null;
|
|
133580
133897
|
this.perfCounterName = null;
|
|
133581
133898
|
this.perfCounterNo = 0;
|
|
133899
|
+
this.useExactIfaceWrapper = false;
|
|
133582
133900
|
this.body = [];
|
|
133583
133901
|
this.lblNo = 0;
|
|
133584
133902
|
this.action = null;
|
|
@@ -133596,6 +133914,11 @@ var ts;
|
|
|
133596
133914
|
isGetter() {
|
|
133597
133915
|
return this.action && this.action.kind == ts.SyntaxKind.GetAccessor;
|
|
133598
133916
|
}
|
|
133917
|
+
// Returns the proc's "_args" entry label -- the full arg-shuffling
|
|
133918
|
+
// wrapper. Use this for any vtable-style reference where the caller
|
|
133919
|
+
// cannot guarantee the wrapper-skip preconditions (numargs >= proc.args.length).
|
|
133920
|
+
// For iface-table entries that CAN accept the optimization, the consumer
|
|
133921
|
+
// (hexfile.ts) inspects useExactIfaceWrapper directly and picks _iface instead.
|
|
133599
133922
|
vtLabel() {
|
|
133600
133923
|
return this.label() + (pxtc.isStackMachine() ? "" : "_args");
|
|
133601
133924
|
}
|
|
@@ -134138,6 +134461,11 @@ var ts;
|
|
|
134138
134461
|
argsFmt: ["T", "T", "S", "T"],
|
|
134139
134462
|
value: 0
|
|
134140
134463
|
},
|
|
134464
|
+
"pxtrt::mapSetByStringOnly": {
|
|
134465
|
+
name: "_pxt_map_set_by_string",
|
|
134466
|
+
argsFmt: ["T", "T", "S", "T"],
|
|
134467
|
+
value: 0
|
|
134468
|
+
},
|
|
134141
134469
|
};
|
|
134142
134470
|
let EK = pxtc.ir.EK;
|
|
134143
134471
|
pxtc.SK = ts.SyntaxKind;
|
|
@@ -134871,6 +135199,87 @@ var ts;
|
|
|
134871
135199
|
bin.breakpoints = opts.breakpoints;
|
|
134872
135200
|
bin.name = opts.name;
|
|
134873
135201
|
bin.target = opts.target;
|
|
135202
|
+
function ifaceCallKindCounts(ifaceIndex, getset) {
|
|
135203
|
+
const suffix = ":" + (getset || "call");
|
|
135204
|
+
return Object.keys(bin.ifaceCallCounts)
|
|
135205
|
+
.filter(k => pxtc.U.startsWith(k, ifaceIndex + ":") && pxtc.U.endsWith(k, suffix))
|
|
135206
|
+
.map(k => ({
|
|
135207
|
+
numargs: parseInt(k.split(":")[1]),
|
|
135208
|
+
count: bin.ifaceCallCounts[k]
|
|
135209
|
+
}));
|
|
135210
|
+
}
|
|
135211
|
+
// Decides whether `proc` (an iface-dispatched method) can safely skip the
|
|
135212
|
+
// arg-shuffling _args wrapper and have its iface vtable entry point directly
|
|
135213
|
+
// at _nochk. Safe iff every call site that targets this (ifaceIndex, getset)
|
|
135214
|
+
// bucket passes at least `proc.args.length` arguments -- that's exactly the
|
|
135215
|
+
// condition the wrapper would short-circuit on (`cmp r4, #numargs; bge _nochk`).
|
|
135216
|
+
//
|
|
135217
|
+
// Notes:
|
|
135218
|
+
// - The counts are a per-(ifaceIndex, getset) projection, not per-proc. In a
|
|
135219
|
+
// polymorphic iface bucket each proc is evaluated independently; eligibility
|
|
135220
|
+
// ends up cascading to the proc with the largest args.length in the bucket.
|
|
135221
|
+
// - toString is excluded because hexfile.ts:~841 wires toString's vtable
|
|
135222
|
+
// slot to `vtLabel()` (i.e. _args). The fixed-slot dispatch through that
|
|
135223
|
+
// vtable entry has no arg-count guarantees, so the wrapper is required.
|
|
135224
|
+
// Skipping it for a toString proc would dangle the _args reference.
|
|
135225
|
+
// Any future fixed-slot vtable consumer that uses vtLabel() must add a
|
|
135226
|
+
// matching exclusion here.
|
|
135227
|
+
// - dynamicIfaceCalls disqualifies any ifaceIndex ever dispatched without a
|
|
135228
|
+
// known decl (line ~2503). Those callers don't go through the counted path.
|
|
135229
|
+
// - The "no get calls in a non-get bucket" check is conservative: a "get"
|
|
135230
|
+
// syntax call has args.length == 1 which could under-feed a multi-arg call
|
|
135231
|
+
// target sharing the bucket.
|
|
135232
|
+
function canUseExactIfaceWrapper(proc, ifaceIndex, getset) {
|
|
135233
|
+
if (!isThumb() || !proc || !proc.info.usedAsIface || proc.args.length == 0)
|
|
135234
|
+
return false;
|
|
135235
|
+
if (proc.action && isToString(proc.action))
|
|
135236
|
+
return false;
|
|
135237
|
+
if (bin.dynamicIfaceCalls[ifaceIndex + ""])
|
|
135238
|
+
return false;
|
|
135239
|
+
if (!getset && ifaceCallKindCounts(ifaceIndex, "get").length)
|
|
135240
|
+
return false;
|
|
135241
|
+
const counts = ifaceCallKindCounts(ifaceIndex, getset);
|
|
135242
|
+
return counts.every(c => c.numargs >= proc.args.length);
|
|
135243
|
+
}
|
|
135244
|
+
// Must run AFTER the final-pass emit(rootFunction) has populated
|
|
135245
|
+
// bin.ifaceCallCounts and bin.dynamicIfaceCalls, and BEFORE
|
|
135246
|
+
// hexfile.ts emits iface vtables (which read useExactIfaceWrapper to
|
|
135247
|
+
// pick _iface vs _args per entry) and backbase.ts emits proc wrappers
|
|
135248
|
+
// (which read useExactIfaceWrapper to decide whether to emit _iface).
|
|
135249
|
+
function markExactIfaceWrappers() {
|
|
135250
|
+
bin.procs.forEach(p => p.useExactIfaceWrapper = false);
|
|
135251
|
+
if (!isThumb() || pxtc.target.switches.noIfaceSpec)
|
|
135252
|
+
return;
|
|
135253
|
+
const eligible = {};
|
|
135254
|
+
const seen = {};
|
|
135255
|
+
const note = (proc, ifaceIndex, getset) => {
|
|
135256
|
+
if (!proc || proc.args.length == 0)
|
|
135257
|
+
return;
|
|
135258
|
+
const key = proc.seqNo + "";
|
|
135259
|
+
seen[key] = true;
|
|
135260
|
+
if (eligible[key] === undefined)
|
|
135261
|
+
eligible[key] = true;
|
|
135262
|
+
if (!canUseExactIfaceWrapper(proc, ifaceIndex, getset))
|
|
135263
|
+
eligible[key] = false;
|
|
135264
|
+
};
|
|
135265
|
+
for (const info of bin.usedClassInfos) {
|
|
135266
|
+
if (!info.itable)
|
|
135267
|
+
continue;
|
|
135268
|
+
for (const entry of info.itable) {
|
|
135269
|
+
if (entry.proc)
|
|
135270
|
+
note(entry.proc, entry.idx, entry.proc.isGetter() ? "get" : "");
|
|
135271
|
+
if (entry.setProc)
|
|
135272
|
+
note(entry.setProc, entry.idx, "set");
|
|
135273
|
+
}
|
|
135274
|
+
}
|
|
135275
|
+
for (const proc of bin.procs) {
|
|
135276
|
+
const key = proc.seqNo + "";
|
|
135277
|
+
proc.useExactIfaceWrapper = !!seen[key] && !!eligible[key];
|
|
135278
|
+
}
|
|
135279
|
+
}
|
|
135280
|
+
function emitEscapedExpression(expr, reason) {
|
|
135281
|
+
return emitExpr(expr);
|
|
135282
|
+
}
|
|
134874
135283
|
function reset() {
|
|
134875
135284
|
bin.reset();
|
|
134876
135285
|
proc = null;
|
|
@@ -134951,7 +135360,13 @@ var ts;
|
|
|
134951
135360
|
reset();
|
|
134952
135361
|
needsUsingInfo = false;
|
|
134953
135362
|
bin.finalPass = true;
|
|
135363
|
+
bin.ifaceCallCounts = {};
|
|
135364
|
+
bin.mapSetByFieldIdCounts = {};
|
|
135365
|
+
bin.checkedFieldAccessCounts = {};
|
|
135366
|
+
bin.dynamicIfaceCalls = {};
|
|
135367
|
+
bin.usesMapSetByStringOnly = false;
|
|
134954
135368
|
emit(rootFunction);
|
|
135369
|
+
markExactIfaceWrappers();
|
|
134955
135370
|
pxtc.U.assert(usedWorkList.length == 0);
|
|
134956
135371
|
res.configData = [];
|
|
134957
135372
|
for (let k of Object.keys(configEntries)) {
|
|
@@ -135641,7 +136056,7 @@ ${lbl}: .short 0xffff
|
|
|
135641
136056
|
let coll = pxtc.ir.shared(pxtc.ir.rtcall("Array_::mk", []));
|
|
135642
136057
|
for (let elt of node.elements) {
|
|
135643
136058
|
let mask = isRefCountedExpr(elt) ? (1 << 1) : 0;
|
|
135644
|
-
proc.emitExpr(pxtc.ir.rtcall("Array_::push", [coll,
|
|
136059
|
+
proc.emitExpr(pxtc.ir.rtcall("Array_::push", [coll, emitEscapedExpression(elt, "arrayElement")], mask));
|
|
135645
136060
|
}
|
|
135646
136061
|
if (node.elements.length > 0)
|
|
135647
136062
|
// Make sure there is at least one use of the array, so it doesn't get GC-ed away in the last push.
|
|
@@ -135683,8 +136098,11 @@ ${lbl}: .short 0xffff
|
|
|
135683
136098
|
if (isRefCountedExpr(p.initializer))
|
|
135684
136099
|
mask |= 1 << 2;
|
|
135685
136100
|
}
|
|
136101
|
+
const nativeFieldId = pxtc.target.isNative ? getIfaceMemberId(keyName) : undefined;
|
|
136102
|
+
if (nativeFieldId !== undefined)
|
|
136103
|
+
bin.mapSetByFieldIdCounts[nativeFieldId + ""] = (bin.mapSetByFieldIdCounts[nativeFieldId + ""] || 0) + 1;
|
|
135686
136104
|
const fieldId = pxtc.target.isNative
|
|
135687
|
-
? pxtc.ir.numlit(
|
|
136105
|
+
? pxtc.ir.numlit(nativeFieldId)
|
|
135688
136106
|
: pxtc.ir.ptrlit(null, JSON.stringify(keyName));
|
|
135689
136107
|
const args = [
|
|
135690
136108
|
expr,
|
|
@@ -135744,6 +136162,9 @@ ${lbl}: .short 0xffff
|
|
|
135744
136162
|
return emitShim(fld, decl, [node.expression]);
|
|
135745
136163
|
}
|
|
135746
136164
|
else {
|
|
136165
|
+
if (idx.needsCheck && !pxtc.target.switches.skipClassCheck) {
|
|
136166
|
+
bin.checkedFieldAccessCounts[checkedFieldAccessCountKey(idx)] = (bin.checkedFieldAccessCounts[checkedFieldAccessCountKey(idx)] || 0) + 1;
|
|
136167
|
+
}
|
|
135747
136168
|
return pxtc.ir.op(EK.FieldAccess, [emitExpr(node.expression)], idx);
|
|
135748
136169
|
}
|
|
135749
136170
|
}
|
|
@@ -135797,7 +136218,17 @@ ${lbl}: .short 0xffff
|
|
|
135797
136218
|
indexer = assign ? attrs.indexerSet : attrs.indexerGet;
|
|
135798
136219
|
}
|
|
135799
136220
|
if (!indexer && (t.flags & (ts.TypeFlags.Any | ts.TypeFlags.StructuredOrTypeVariable))) {
|
|
135800
|
-
|
|
136221
|
+
const hasIndexSignature = !!checker.getIndexTypeOfType(t, ts.IndexKind.String) || !!checker.getIndexTypeOfType(t, ts.IndexKind.Number);
|
|
136222
|
+
// Native-only: mapSetByStringOnly resolves to the _pxt_map_set_by_string
|
|
136223
|
+
// thumb helper. The JS backend has no such runtime entry, so the generic
|
|
136224
|
+
// setter is the only valid choice there.
|
|
136225
|
+
const mapOnlySet = !pxtc.target.switches.noIfaceSpec && pxtc.target.isNative
|
|
136226
|
+
&& !!assign && !(t.flags & ts.TypeFlags.Any) && hasIndexSignature;
|
|
136227
|
+
if (mapOnlySet)
|
|
136228
|
+
bin.usesMapSetByStringOnly = true;
|
|
136229
|
+
indexer = assign
|
|
136230
|
+
? (mapOnlySet ? "pxtrt::mapSetByStringOnly" : "pxtrt::mapSetGeneric")
|
|
136231
|
+
: "pxtrt::mapGetGeneric";
|
|
135801
136232
|
stringOk = true;
|
|
135802
136233
|
}
|
|
135803
136234
|
if (indexer) {
|
|
@@ -136134,7 +136565,7 @@ ${lbl}: .short 0xffff
|
|
|
136134
136565
|
lst.push(tracked);
|
|
136135
136566
|
}
|
|
136136
136567
|
function emitPlain() {
|
|
136137
|
-
let r = mkProcCall(decl, node, args.map(
|
|
136568
|
+
let r = mkProcCall(decl, node, args.map(x => emitEscapedExpression(x, "callArgument")));
|
|
136138
136569
|
let pp = r.data;
|
|
136139
136570
|
if (args[0] && pp.proc && pp.proc.classInfo)
|
|
136140
136571
|
pp.isThis = args[0].kind == pxtc.SK.ThisKeyword;
|
|
@@ -136161,7 +136592,7 @@ ${lbl}: .short 0xffff
|
|
|
136161
136592
|
baseCtor = p.ctor;
|
|
136162
136593
|
if (!baseCtor && bin.finalPass)
|
|
136163
136594
|
throw userError(9280, lf("super() call requires an explicit constructor in base class"));
|
|
136164
|
-
let ctorArgs = args.map((x) =>
|
|
136595
|
+
let ctorArgs = args.map((x) => emitEscapedExpression(x, "callArgument"));
|
|
136165
136596
|
ctorArgs.unshift(emitThis(funcExpr));
|
|
136166
136597
|
return mkProcCallCore(baseCtor, node, ctorArgs);
|
|
136167
136598
|
}
|
|
@@ -136177,9 +136608,11 @@ ${lbl}: .short 0xffff
|
|
|
136177
136608
|
// TODO in VT accessor/field/method -> different
|
|
136178
136609
|
pxtc.U.assert(funcExpr.kind == pxtc.SK.PropertyAccessExpression);
|
|
136179
136610
|
const fieldName = funcExpr.name.text;
|
|
136611
|
+
const ifaceIndex = getIfaceMemberId(fieldName, true);
|
|
136612
|
+
bin.dynamicIfaceCalls[ifaceIndex + ""] = true;
|
|
136180
136613
|
// completely dynamic dispatch
|
|
136181
|
-
return mkMethodCall(args.map((x) =>
|
|
136182
|
-
ifaceIndex
|
|
136614
|
+
return mkMethodCall(args.map((x) => emitEscapedExpression(x, "callArgument")), {
|
|
136615
|
+
ifaceIndex,
|
|
136183
136616
|
callLocationIndex: markCallLocation(node),
|
|
136184
136617
|
noArgs
|
|
136185
136618
|
});
|
|
@@ -136203,7 +136636,7 @@ ${lbl}: .short 0xffff
|
|
|
136203
136636
|
pxtc.U.assert(false);
|
|
136204
136637
|
}
|
|
136205
136638
|
pxtc.U.assert(!bin.finalPass || info.virtualIndex != null, "!bin.finalPass || info.virtualIndex != null");
|
|
136206
|
-
return mkMethodCall(args.map((x) =>
|
|
136639
|
+
return mkMethodCall(args.map((x) => emitEscapedExpression(x, "callArgument")), {
|
|
136207
136640
|
classInfo: info.parentClassInfo,
|
|
136208
136641
|
virtualIndex: info.virtualIndex,
|
|
136209
136642
|
noArgs,
|
|
@@ -136243,9 +136676,15 @@ ${lbl}: .short 0xffff
|
|
|
136243
136676
|
return emitPlain();
|
|
136244
136677
|
}
|
|
136245
136678
|
else if (needsVCall || pxtc.target.switches.slowMethods || !forceMethod) {
|
|
136246
|
-
|
|
136247
|
-
|
|
136248
|
-
|
|
136679
|
+
const ifaceIndex = getIfaceMemberId(getName(decl), true);
|
|
136680
|
+
const isSet = noArgs && args.length == 2;
|
|
136681
|
+
const callKind = isSet ? "set" : noArgs ? "get" : "call";
|
|
136682
|
+
const helperGetSet = callKind == "call" ? "" : callKind;
|
|
136683
|
+
const countKey = `${ifaceIndex}:${args.length}:${helperGetSet || "call"}`;
|
|
136684
|
+
bin.ifaceCallCounts[countKey] = (bin.ifaceCallCounts[countKey] || 0) + 1;
|
|
136685
|
+
return mkMethodCall(args.map((x) => emitEscapedExpression(x, "callArgument")), {
|
|
136686
|
+
ifaceIndex,
|
|
136687
|
+
isSet,
|
|
136249
136688
|
callLocationIndex: markCallLocation(node),
|
|
136250
136689
|
noArgs
|
|
136251
136690
|
});
|
|
@@ -136264,7 +136703,7 @@ ${lbl}: .short 0xffff
|
|
|
136264
136703
|
// here's where we will recurse to generate funcExpr
|
|
136265
136704
|
args.unshift(funcExpr);
|
|
136266
136705
|
pxtc.U.assert(!noArgs);
|
|
136267
|
-
return mkMethodCall(args.map(x =>
|
|
136706
|
+
return mkMethodCall(args.map(x => emitEscapedExpression(x, "callArgument")), {
|
|
136268
136707
|
virtualIndex: -1,
|
|
136269
136708
|
callLocationIndex: markCallLocation(node),
|
|
136270
136709
|
noArgs
|
|
@@ -136360,6 +136799,8 @@ ${lbl}: .short 0xffff
|
|
|
136360
136799
|
}
|
|
136361
136800
|
}
|
|
136362
136801
|
function getCtor(decl) {
|
|
136802
|
+
if (!decl.members)
|
|
136803
|
+
return null;
|
|
136363
136804
|
return decl.members.filter(m => m.kind == pxtc.SK.Constructor)[0];
|
|
136364
136805
|
}
|
|
136365
136806
|
function isIfaceMemberUsed(name) {
|
|
@@ -136407,7 +136848,7 @@ ${lbl}: .short 0xffff
|
|
|
136407
136848
|
// let sig = checker.getResolvedSignature(node)
|
|
136408
136849
|
// TODO: can we have overloeads?
|
|
136409
136850
|
addDefaultParametersAndTypeCheck(checker.getResolvedSignature(node), args, ctorAttrs);
|
|
136410
|
-
let compiled = args.map((x) =>
|
|
136851
|
+
let compiled = args.map((x) => emitEscapedExpression(x, "callArgument"));
|
|
136411
136852
|
if (ctorAttrs.shim) {
|
|
136412
136853
|
// TODO need to deal with refMask and tagged ints here
|
|
136413
136854
|
// we drop 'obj' variable
|
|
@@ -136796,7 +137237,7 @@ ${lbl}: .short 0xffff
|
|
|
136796
137237
|
for (let f of initalizedFields) {
|
|
136797
137238
|
let idx = fieldIndexCore(proc.classInfo, getFieldInfo(proc.classInfo, getName(f)), false);
|
|
136798
137239
|
let trg2 = pxtc.ir.op(EK.FieldAccess, [emitLocalLoad(info.thisParameter)], idx);
|
|
136799
|
-
proc.emitExpr(pxtc.ir.op(EK.Store, [trg2,
|
|
137240
|
+
proc.emitExpr(pxtc.ir.op(EK.Store, [trg2, emitEscapedExpression(f.initializer, "initializer")]));
|
|
136800
137241
|
}
|
|
136801
137242
|
flushHoistedFunctionDefinitions();
|
|
136802
137243
|
if (node.body.kind == pxtc.SK.Block) {
|
|
@@ -137014,9 +137455,12 @@ ${lbl}: .short 0xffff
|
|
|
137014
137455
|
isRef: true,
|
|
137015
137456
|
shimName: attrs.shim,
|
|
137016
137457
|
classInfo: info,
|
|
137017
|
-
needsCheck
|
|
137458
|
+
needsCheck,
|
|
137018
137459
|
};
|
|
137019
137460
|
}
|
|
137461
|
+
function checkedFieldAccessCountKey(info) {
|
|
137462
|
+
return `${info.classInfo.id}:${info.name}:get`;
|
|
137463
|
+
}
|
|
137020
137464
|
function fieldIndex(pacc) {
|
|
137021
137465
|
const tp = typeOf(pacc.expression);
|
|
137022
137466
|
if (isPossiblyGenericClassType(tp)) {
|
|
@@ -137047,7 +137491,7 @@ ${lbl}: .short 0xffff
|
|
|
137047
137491
|
if (decl && (isGlobal || isVar(decl) || isParameter(decl))) {
|
|
137048
137492
|
let l = lookupCell(decl);
|
|
137049
137493
|
recordUse(decl, true);
|
|
137050
|
-
proc.emitExpr(l.storeByRef(
|
|
137494
|
+
proc.emitExpr(l.storeByRef(emitEscapedExpression(src, "assignment")));
|
|
137051
137495
|
}
|
|
137052
137496
|
else {
|
|
137053
137497
|
unhandled(trg, lf("bad target identifier"), 9248);
|
|
@@ -137063,7 +137507,10 @@ ${lbl}: .short 0xffff
|
|
|
137063
137507
|
}
|
|
137064
137508
|
proc.emitExpr(emitCallCore(trg, trg, [src], null, decl));
|
|
137065
137509
|
}
|
|
137066
|
-
else if (decl && (decl.kind == pxtc.SK.PropertySignature || decl.kind == pxtc.SK.PropertyAssignment
|
|
137510
|
+
else if (decl && (decl.kind == pxtc.SK.PropertySignature || decl.kind == pxtc.SK.PropertyAssignment)) {
|
|
137511
|
+
proc.emitExpr(emitCallCore(trg, trg, [src], null, decl));
|
|
137512
|
+
}
|
|
137513
|
+
else if (decl && isSlowField(decl)) {
|
|
137067
137514
|
proc.emitExpr(emitCallCore(trg, trg, [src], null, decl));
|
|
137068
137515
|
}
|
|
137069
137516
|
else {
|
|
@@ -137079,7 +137526,7 @@ ${lbl}: .short 0xffff
|
|
|
137079
137526
|
}
|
|
137080
137527
|
}
|
|
137081
137528
|
let trg2 = emitExpr(trg);
|
|
137082
|
-
proc.emitExpr(pxtc.ir.op(EK.Store, [trg2,
|
|
137529
|
+
proc.emitExpr(pxtc.ir.op(EK.Store, [trg2, emitEscapedExpression(src, "assignment")]));
|
|
137083
137530
|
break;
|
|
137084
137531
|
}
|
|
137085
137532
|
}
|
|
@@ -137490,7 +137937,7 @@ ${lbl}: .short 0xffff
|
|
|
137490
137937
|
let mask = getMask(args);
|
|
137491
137938
|
let convInfos = [];
|
|
137492
137939
|
let args2 = args.map((a, i) => {
|
|
137493
|
-
let r =
|
|
137940
|
+
let r = emitEscapedExpression(a, "runtimeCallArgument");
|
|
137494
137941
|
if (!needsNumberConversions())
|
|
137495
137942
|
return r;
|
|
137496
137943
|
let f = fmt[i + 1];
|
|
@@ -137828,6 +138275,43 @@ ${lbl}: .short 0xffff
|
|
|
137828
138275
|
emitExprAsStmt(node.expression);
|
|
137829
138276
|
}
|
|
137830
138277
|
function emitCondition(expr, inner = null) {
|
|
138278
|
+
// with `noBoolLower` flag, the generic paths below handle every condition
|
|
138279
|
+
const boolLower = !pxtc.target.switches.noBoolLower;
|
|
138280
|
+
// Lower `&&` / `||` used as a condition into short-circuiting jumps
|
|
138281
|
+
// that yield 0/1 directly, instead of boxing the intermediate
|
|
138282
|
+
// booleans and converting them back with a runtime helper.
|
|
138283
|
+
if (boolLower && !inner && !isStackMachine() && expr.kind == pxtc.SK.BinaryExpression) {
|
|
138284
|
+
const binary = expr;
|
|
138285
|
+
const op = binary.operatorToken.kind;
|
|
138286
|
+
if (op == pxtc.SK.AmpersandAmpersandToken || op == pxtc.SK.BarBarToken) {
|
|
138287
|
+
const shortCircuitLabel = proc.mkLabel("lazycond");
|
|
138288
|
+
const doneLabel = proc.mkLabel("lazycondfin");
|
|
138289
|
+
if (op == pxtc.SK.AmpersandAmpersandToken) {
|
|
138290
|
+
proc.emitJmp(shortCircuitLabel, emitCondition(binary.left), pxtc.ir.JmpMode.IfZero);
|
|
138291
|
+
proc.emitJmp(shortCircuitLabel, emitCondition(binary.right), pxtc.ir.JmpMode.IfZero);
|
|
138292
|
+
proc.emitJmp(doneLabel, pxtc.ir.numlit(1), pxtc.ir.JmpMode.Always);
|
|
138293
|
+
proc.emitLbl(shortCircuitLabel);
|
|
138294
|
+
proc.emitJmp(doneLabel, pxtc.ir.numlit(0), pxtc.ir.JmpMode.Always);
|
|
138295
|
+
}
|
|
138296
|
+
else {
|
|
138297
|
+
proc.emitJmp(shortCircuitLabel, emitCondition(binary.left), pxtc.ir.JmpMode.IfNotZero);
|
|
138298
|
+
proc.emitJmp(shortCircuitLabel, emitCondition(binary.right), pxtc.ir.JmpMode.IfNotZero);
|
|
138299
|
+
proc.emitJmp(doneLabel, pxtc.ir.numlit(0), pxtc.ir.JmpMode.Always);
|
|
138300
|
+
proc.emitLbl(shortCircuitLabel);
|
|
138301
|
+
proc.emitJmp(doneLabel, pxtc.ir.numlit(1), pxtc.ir.JmpMode.Always);
|
|
138302
|
+
}
|
|
138303
|
+
proc.emitLbl(doneLabel);
|
|
138304
|
+
return captureJmpValue();
|
|
138305
|
+
}
|
|
138306
|
+
}
|
|
138307
|
+
// Lower `!expr` used as a condition by negating the operand's
|
|
138308
|
+
// condition directly, avoiding a boxed boolean just to invert it.
|
|
138309
|
+
if (boolLower && !inner && !isStackMachine() && expr.kind == pxtc.SK.PrefixUnaryExpression) {
|
|
138310
|
+
const unary = expr;
|
|
138311
|
+
if (unary.operator == pxtc.SK.ExclamationToken) {
|
|
138312
|
+
return pxtc.ir.rtcall("Boolean_::bang", [emitCondition(unary.operand)]);
|
|
138313
|
+
}
|
|
138314
|
+
}
|
|
137831
138315
|
if (!inner && isThumb() && expr.kind == pxtc.SK.BinaryExpression) {
|
|
137832
138316
|
let be = expr;
|
|
137833
138317
|
let mapped = pxtc.U.lookup(pxtc.thumbCmpMap, simpleInstruction(be, be.operatorToken.kind));
|
|
@@ -138279,7 +138763,10 @@ ${lbl}: .short 0xffff
|
|
|
138279
138763
|
}
|
|
138280
138764
|
}
|
|
138281
138765
|
typeCheckSubtoSup(node.initializer, node);
|
|
138282
|
-
|
|
138766
|
+
const initializerExpr = isGlobalVar(node)
|
|
138767
|
+
? emitEscapedExpression(node.initializer, "initializer")
|
|
138768
|
+
: emitExpr(node.initializer);
|
|
138769
|
+
proc.emitExpr(loc.storeByRef(initializerExpr));
|
|
138283
138770
|
currJres = null;
|
|
138284
138771
|
proc.stackEmpty();
|
|
138285
138772
|
}
|
|
@@ -138304,8 +138791,17 @@ ${lbl}: .short 0xffff
|
|
|
138304
138791
|
exres = pxtc.ir.op(EK.FieldAccess, [objRef], fieldIndexCore(info, getFieldInfo(info, fieldName)));
|
|
138305
138792
|
}
|
|
138306
138793
|
else {
|
|
138794
|
+
// Dynamic field access on a non-class-typed receiver (any, structural type,
|
|
138795
|
+
// etc). The call site supplies exactly 1 arg (the receiver) but the target
|
|
138796
|
+
// proc resolved via iface dispatch may be a method that expects more --
|
|
138797
|
+
// the _args wrapper is what normally fills in the missing args. Flag this
|
|
138798
|
+
// ifaceIndex so canUseExactIfaceWrapper disqualifies the wrapper-skip
|
|
138799
|
+
// optimization for any proc reachable here. Mirrors the dynamic-method-call
|
|
138800
|
+
// path above.
|
|
138801
|
+
const ifaceIndex = getIfaceMemberId(fieldName, true);
|
|
138802
|
+
bin.dynamicIfaceCalls[ifaceIndex + ""] = true;
|
|
138307
138803
|
exres = mkMethodCall([objRef], {
|
|
138308
|
-
ifaceIndex
|
|
138804
|
+
ifaceIndex,
|
|
138309
138805
|
callLocationIndex: markCallLocation(node),
|
|
138310
138806
|
noArgs: true
|
|
138311
138807
|
});
|
|
@@ -138688,6 +139184,11 @@ ${lbl}: .short 0xffff
|
|
|
138688
139184
|
this.usedChars = new Uint32Array(0x10000 / 32);
|
|
138689
139185
|
this.explicitlyUsedIfaceMembers = {};
|
|
138690
139186
|
this.ifaceMemberMap = {};
|
|
139187
|
+
this.ifaceCallCounts = {};
|
|
139188
|
+
this.mapSetByFieldIdCounts = {};
|
|
139189
|
+
this.checkedFieldAccessCounts = {};
|
|
139190
|
+
this.dynamicIfaceCalls = {};
|
|
139191
|
+
this.usesMapSetByStringOnly = false;
|
|
138691
139192
|
this.strings = {};
|
|
138692
139193
|
this.hexlits = {};
|
|
138693
139194
|
this.doubles = {};
|
|
@@ -141138,9 +141639,9 @@ ${lbl}: ${snippets.obj_header("pxt::number_vt")}
|
|
|
141138
141639
|
uint32_t ifaceHashMult;
|
|
141139
141640
|
PVoid methods[2 or 4];
|
|
141140
141641
|
*/
|
|
141141
|
-
const ifaceInfo =
|
|
141142
|
-
|
|
141143
|
-
|
|
141642
|
+
const ifaceInfo = info.itable.length
|
|
141643
|
+
? computeHashMultiplier(info.itable.map(e => e.idx))
|
|
141644
|
+
: { mult: 0, mapping: new Uint16Array(0), size: 0 };
|
|
141144
141645
|
let ptrSz = pxtc.target.shortPointers ? ".short" : ".word";
|
|
141145
141646
|
let s = `
|
|
141146
141647
|
.object ${info.id}_VT
|
|
@@ -141174,6 +141675,11 @@ ${info.id}_IfaceVT:
|
|
|
141174
141675
|
`;
|
|
141175
141676
|
const descSize = 8;
|
|
141176
141677
|
const zeroOffset = ifaceInfo.mapping.length * 2;
|
|
141678
|
+
// Iface table is the one consumer that can elect the wrapper-skip
|
|
141679
|
+
// optimization. Procs with useExactIfaceWrapper get pointed at _iface
|
|
141680
|
+
// (which `b _nochk`); everyone else gets the usual _args wrapper entry.
|
|
141681
|
+
// canUseExactIfaceWrapper guarantees this is safe for the elected procs.
|
|
141682
|
+
const procIfaceLabel = (p) => (p.useExactIfaceWrapper ? p.label() + "_iface" : p.vtLabel()) + "@fn";
|
|
141177
141683
|
let descs = "";
|
|
141178
141684
|
let offset = zeroOffset;
|
|
141179
141685
|
let offsets = {};
|
|
@@ -141181,11 +141687,11 @@ ${info.id}_IfaceVT:
|
|
|
141181
141687
|
offsets[e.idx + ""] = offset;
|
|
141182
141688
|
const desc = !e.proc ? 0 : e.proc.isGetter() ? 1 : 2;
|
|
141183
141689
|
descs += ` .short ${e.idx}, ${desc} ; ${e.name}\n`;
|
|
141184
|
-
descs += ` .word ${e.proc ? e.proc
|
|
141690
|
+
descs += ` .word ${e.proc ? procIfaceLabel(e.proc) : e.info}\n`;
|
|
141185
141691
|
offset += descSize;
|
|
141186
141692
|
if (e.setProc) {
|
|
141187
141693
|
descs += ` .short ${e.idx}, 0 ; set ${e.name}\n`;
|
|
141188
|
-
descs += ` .word ${e.setProc
|
|
141694
|
+
descs += ` .word ${procIfaceLabel(e.setProc)}\n`;
|
|
141189
141695
|
offset += descSize;
|
|
141190
141696
|
}
|
|
141191
141697
|
}
|
|
@@ -141198,7 +141704,9 @@ ${info.id}_IfaceVT:
|
|
|
141198
141704
|
bin.itFullEntries++;
|
|
141199
141705
|
}
|
|
141200
141706
|
// offsets are relative to the position in the array
|
|
141201
|
-
|
|
141707
|
+
const hashOffsets = pxtc.U.toArray(map).map((e, i) => (offsets[e + ""] || zeroOffset) - (i * 2));
|
|
141708
|
+
if (hashOffsets.length)
|
|
141709
|
+
s += " .short " + hashOffsets.join(", ") + "\n";
|
|
141202
141710
|
s += descs;
|
|
141203
141711
|
s += "\n";
|
|
141204
141712
|
return s;
|