pxt-core 13.2.1 → 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/README.md +1 -1
- package/built/pxt.js +646 -137
- package/built/pxtcompiler.d.ts +19 -1
- package/built/pxtcompiler.js +519 -64
- package/built/pxtlib.d.ts +1 -1
- package/built/pxtlib.js +127 -73
- package/built/react-common/components/controls/DropdownModal.d.ts +6 -0
- package/built/react-common/components/controls/Input.d.ts +1 -1
- package/built/target.js +1 -1
- package/built/targetlight.js +1 -1
- package/built/tests/thumb-test/asmchecks.d.ts +73 -0
- package/built/tests/thumb-test/thumbrunner.d.ts +3 -0
- package/built/web/main.js +30 -30
- package/built/web/pxtapp.js +1 -1
- package/built/web/pxtasseteditor.js +20 -20
- package/built/web/pxtcompiler.js +1 -1
- package/built/web/pxtembed.js +4 -4
- package/built/web/pxtlib.js +1 -1
- package/built/web/pxtworker.js +1 -1
- package/built/web/react-common-authcode.css +1 -1
- package/built/web/react-common-multiplayer.css +1 -1
- package/built/web/react-common-skillmap.css +1 -1
- package/built/web/rtlreact-common-authcode.css +1 -1
- package/built/web/rtlreact-common-multiplayer.css +1 -1
- package/built/web/rtlreact-common-skillmap.css +1 -1
- package/built/web/rtlsemantic.css +2 -2
- package/built/web/runnerembed.js +5 -5
- package/built/web/semantic.css +2 -2
- package/common-docs/blocks-embed.md +2 -2
- package/common-docs/github/commit.md +1 -1
- package/common-docs/github/private-assignments.md +1 -1
- package/common-docs/github/test-extension.md +1 -1
- package/common-docs/online-learning.md +2 -2
- package/common-docs/reference/arrays/pick-random.md +1 -1
- package/common-docs/reference/text/char-code-at.md +1 -1
- package/common-docs/share.md +2 -2
- package/common-docs/types/function/define.md +1 -1
- package/localtypings/pxtarget.d.ts +3 -1
- package/package.json +3 -2
- package/react-common/components/controls/DropdownModal.tsx +66 -0
- package/react-common/components/controls/Input.tsx +1 -1
- package/react-common/styles/controls/DropdownModal.less +17 -0
- package/react-common/styles/react-common.less +1 -0
- package/react-common/styles/theming/ThemePickerModal.less +5 -0
- package/theme/monaco.less +2 -0
- package/theme/music-editor/gallery.less +10 -1
- package/theme/piano-roll/piano-roll.less +33 -2
- package/theme/pxtjson.less +0 -12
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) {
|
|
@@ -113528,7 +113581,7 @@ var pxt;
|
|
|
113528
113581
|
}]
|
|
113529
113582
|
},
|
|
113530
113583
|
{
|
|
113531
|
-
name: lf("
|
|
113584
|
+
name: lf("boom kick"),
|
|
113532
113585
|
startFrequency: 100,
|
|
113533
113586
|
startVolume: 1024,
|
|
113534
113587
|
steps: [{
|
|
@@ -119033,10 +119086,12 @@ var pxt;
|
|
|
119033
119086
|
* @param skipIDs string[] a list of string ids (block id, asset id, or file name) to ignore
|
|
119034
119087
|
**/
|
|
119035
119088
|
isAssetUsed(asset, files, skipIDs) {
|
|
119036
|
-
var _a, _b, _c;
|
|
119037
|
-
|
|
119038
|
-
|
|
119039
|
-
|
|
119089
|
+
var _a, _b, _c, _d, _e;
|
|
119090
|
+
if (!((_a = asset.meta) === null || _a === void 0 ? void 0 : _a.displayName) || !files) {
|
|
119091
|
+
let blockIds = ((_c = (_b = asset.meta) === null || _b === void 0 ? void 0 : _b.blockIDs) === null || _c === void 0 ? void 0 : _c.filter(id => !skipIDs || (skipIDs === null || skipIDs === void 0 ? void 0 : skipIDs.indexOf(id)) < 0)) || [];
|
|
119092
|
+
if (blockIds.length > 0)
|
|
119093
|
+
return true;
|
|
119094
|
+
}
|
|
119040
119095
|
if (asset.type == "tile" /* pxt.AssetType.Tile */) {
|
|
119041
119096
|
for (const tm of this.getAssets("tilemap" /* AssetType.Tilemap */)) {
|
|
119042
119097
|
if ((skipIDs === null || skipIDs === void 0 ? void 0 : skipIDs.indexOf(tm.id)) >= 0) {
|
|
@@ -119048,84 +119103,83 @@ var pxt;
|
|
|
119048
119103
|
}
|
|
119049
119104
|
}
|
|
119050
119105
|
if (files) {
|
|
119051
|
-
const
|
|
119052
|
-
const
|
|
119053
|
-
|
|
119054
|
-
|
|
119055
|
-
|
|
119056
|
-
|
|
119057
|
-
if (
|
|
119058
|
-
|
|
119059
|
-
|
|
119060
|
-
|
|
119061
|
-
|
|
119062
|
-
|
|
119063
|
-
|
|
119064
|
-
|
|
119065
|
-
|
|
119066
|
-
assetTsRefs += `|assets.animation\`${displayName}\``;
|
|
119067
|
-
break;
|
|
119068
|
-
case "song" /* pxt.AssetType.Song */:
|
|
119069
|
-
assetTsRefs = `assets.song\`${shortId}\``;
|
|
119070
|
-
if (displayName)
|
|
119071
|
-
assetTsRefs += `|assets.song\`${displayName}\``;
|
|
119072
|
-
break;
|
|
119073
|
-
case "json" /* pxt.AssetType.Json */:
|
|
119074
|
-
assetTsRefs = `assets.json\`${shortId}\``;
|
|
119075
|
-
if (displayName)
|
|
119076
|
-
assetTsRefs += `|assets.json\`${displayName}\``;
|
|
119077
|
-
break;
|
|
119078
|
-
default:
|
|
119079
|
-
assetTsRefs = `assets.image\`${shortId}\``;
|
|
119080
|
-
if (displayName)
|
|
119081
|
-
assetTsRefs += `|assets.image\`${displayName}\``;
|
|
119082
|
-
break;
|
|
119106
|
+
const config = pxt.U.jsonTryParse((_d = files["pxt.json"]) === null || _d === void 0 ? void 0 : _d.content);
|
|
119107
|
+
const filesToCheck = [];
|
|
119108
|
+
if (config === null || config === void 0 ? void 0 : config.files) {
|
|
119109
|
+
for (const file of config.files) {
|
|
119110
|
+
if (file.endsWith(".g.ts"))
|
|
119111
|
+
continue;
|
|
119112
|
+
if (skipIDs && (skipIDs.indexOf(file) !== -1))
|
|
119113
|
+
continue;
|
|
119114
|
+
if (file.endsWith(".ts") && file !== pxt.MAIN_TS || file.endsWith(".py") && file !== pxt.MAIN_PY) {
|
|
119115
|
+
filesToCheck.push(file);
|
|
119116
|
+
}
|
|
119117
|
+
}
|
|
119118
|
+
}
|
|
119119
|
+
if ((config === null || config === void 0 ? void 0 : config.preferredEditor) === pxt.BLOCKS_PROJECT_NAME) {
|
|
119120
|
+
filesToCheck.push(pxt.MAIN_BLOCKS);
|
|
119083
119121
|
}
|
|
119084
|
-
|
|
119085
|
-
|
|
119122
|
+
else if ((config === null || config === void 0 ? void 0 : config.preferredEditor) === pxt.JAVASCRIPT_PROJECT_NAME) {
|
|
119123
|
+
filesToCheck.push(pxt.MAIN_TS);
|
|
119124
|
+
}
|
|
119125
|
+
else if ((config === null || config === void 0 ? void 0 : config.preferredEditor) === pxt.PYTHON_PROJECT_NAME) {
|
|
119126
|
+
filesToCheck.push(pxt.MAIN_PY);
|
|
119127
|
+
}
|
|
119128
|
+
const references = [];
|
|
119129
|
+
const shortId = pxt.Util.escapeForRegex(getShortIDForAsset(asset));
|
|
119130
|
+
const displayName = pxt.Util.escapeForRegex((_e = asset.meta) === null || _e === void 0 ? void 0 : _e.displayName) || "";
|
|
119131
|
+
const addNamespaceReference = (namespace, fun, id) => {
|
|
119132
|
+
references.push(`${namespace}\\s*\\.\\s*${fun}\`\\s*${id}\\s*\``);
|
|
119133
|
+
};
|
|
119086
119134
|
switch (asset.type) {
|
|
119087
119135
|
case "tile" /* pxt.AssetType.Tile */:
|
|
119088
|
-
|
|
119136
|
+
references.push(`myTiles\\s*\\.\\s*${shortId}\\s*`);
|
|
119137
|
+
addNamespaceReference("assets", "tile", shortId);
|
|
119089
119138
|
if (displayName)
|
|
119090
|
-
|
|
119139
|
+
addNamespaceReference("assets", "tile", displayName);
|
|
119091
119140
|
break;
|
|
119092
119141
|
case "tilemap" /* pxt.AssetType.Tilemap */:
|
|
119093
|
-
|
|
119142
|
+
references.push(`tilemap\`\\s*${shortId}\\s*\``);
|
|
119143
|
+
addNamespaceReference("assets", "tilemap", shortId);
|
|
119144
|
+
if (displayName) {
|
|
119145
|
+
references.push(`tilemap\`\\s*${displayName}\\s*\``);
|
|
119146
|
+
addNamespaceReference("assets", "tilemap", displayName);
|
|
119147
|
+
}
|
|
119094
119148
|
break;
|
|
119095
119149
|
case "animation" /* pxt.AssetType.Animation */:
|
|
119096
|
-
|
|
119150
|
+
addNamespaceReference("assets", "animation", shortId);
|
|
119097
119151
|
if (displayName)
|
|
119098
|
-
|
|
119152
|
+
addNamespaceReference("assets", "animation", displayName);
|
|
119099
119153
|
break;
|
|
119100
119154
|
case "song" /* pxt.AssetType.Song */:
|
|
119101
|
-
|
|
119155
|
+
addNamespaceReference("assets", "song", shortId);
|
|
119102
119156
|
if (displayName)
|
|
119103
|
-
|
|
119157
|
+
addNamespaceReference("assets", "song", displayName);
|
|
119104
119158
|
break;
|
|
119105
119159
|
case "json" /* pxt.AssetType.Json */:
|
|
119106
|
-
|
|
119160
|
+
addNamespaceReference("assets", "json", shortId);
|
|
119107
119161
|
if (displayName)
|
|
119108
|
-
|
|
119162
|
+
addNamespaceReference("assets", "json", displayName);
|
|
119109
119163
|
break;
|
|
119110
119164
|
default:
|
|
119111
|
-
|
|
119165
|
+
addNamespaceReference("assets", "image", shortId);
|
|
119112
119166
|
if (displayName)
|
|
119113
|
-
|
|
119167
|
+
addNamespaceReference("assets", "image", displayName);
|
|
119114
119168
|
break;
|
|
119115
119169
|
}
|
|
119116
|
-
const
|
|
119117
|
-
|
|
119118
|
-
|
|
119119
|
-
|
|
119120
|
-
|
|
119121
|
-
|
|
119122
|
-
if (filename.match(/((?!\.g).{2}|^.{0,1})\.ts$/i)) {
|
|
119123
|
-
if (f.content.match(assetTsRegex))
|
|
119170
|
+
const regex = new RegExp(references.map(r => `(?:${r})`).join("|"));
|
|
119171
|
+
const pyRegex = new RegExp(references.map(r => `(?:${r})`).join("|").replace(/`/g, '"""'));
|
|
119172
|
+
for (const file of filesToCheck) {
|
|
119173
|
+
const content = files[file].content;
|
|
119174
|
+
if (file.endsWith(".py")) {
|
|
119175
|
+
if (pyRegex.test(content)) {
|
|
119124
119176
|
return true;
|
|
119177
|
+
}
|
|
119125
119178
|
}
|
|
119126
|
-
else
|
|
119127
|
-
if (
|
|
119179
|
+
else {
|
|
119180
|
+
if (regex.test(content)) {
|
|
119128
119181
|
return true;
|
|
119182
|
+
}
|
|
119129
119183
|
}
|
|
119130
119184
|
}
|
|
119131
119185
|
}
|
|
@@ -126068,6 +126122,10 @@ ${baseLabel}_nochk:
|
|
|
126068
126122
|
}
|
|
126069
126123
|
emitFieldAccess(e, store = false) {
|
|
126070
126124
|
let info = e.data;
|
|
126125
|
+
if (!store && this.shouldSpecializeCheckedFieldLoad(info)) {
|
|
126126
|
+
this.emitCheckedFieldLoad(info);
|
|
126127
|
+
return;
|
|
126128
|
+
}
|
|
126071
126129
|
let pref = store ? "st" : "ld";
|
|
126072
126130
|
let lbl = pref + "fld_" + info.classInfo.id + "_" + info.name;
|
|
126073
126131
|
if (info.needsCheck && !pxtc.target.switches.skipClassCheck) {
|
|
@@ -126086,6 +126144,32 @@ ${baseLabel}_nochk:
|
|
|
126086
126144
|
this.write(`ldr r0, [r0, ${xoff}]`);
|
|
126087
126145
|
return;
|
|
126088
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
|
+
}
|
|
126089
126173
|
writeFailBranch() {
|
|
126090
126174
|
this.write(`.fail:`);
|
|
126091
126175
|
this.write(`mov r1, lr`);
|
|
@@ -126306,12 +126390,50 @@ ${baseLabel}_nochk:
|
|
|
126306
126390
|
}
|
|
126307
126391
|
emitIfaceCall(procid, numargs, getset = "") {
|
|
126308
126392
|
pxtc.U.assert(procid.ifaceIndex > 0);
|
|
126309
|
-
|
|
126310
|
-
this.
|
|
126393
|
+
const helperKey = "ifacecall" + numargs + "_" + getset;
|
|
126394
|
+
const helper = this.ensureLabelledHelper(helperKey, () => {
|
|
126311
126395
|
this.write(`ldr r0, [sp, #0] ; ld-this`);
|
|
126312
126396
|
this.loadVTable();
|
|
126313
126397
|
this.ifaceCallCore(numargs, getset);
|
|
126314
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;
|
|
126315
126437
|
}
|
|
126316
126438
|
// vtable in r3; clobber r2
|
|
126317
126439
|
checkSubtype(info, failLbl = ".fail", r2 = "r2") {
|
|
@@ -126409,6 +126531,8 @@ ${baseLabel}_nochk:
|
|
|
126409
126531
|
}
|
|
126410
126532
|
emitRtCall(topExpr, genCall = null) {
|
|
126411
126533
|
let name = topExpr.data;
|
|
126534
|
+
const mapSetFieldId = this.mapSetByFieldId(topExpr);
|
|
126535
|
+
const specializeMapSetFieldId = mapSetFieldId !== undefined && this.shouldSpecializeMapSetByFieldId(mapSetFieldId);
|
|
126412
126536
|
if (name == "pxt::beginTry") {
|
|
126413
126537
|
return this.emitBeginTry(topExpr);
|
|
126414
126538
|
}
|
|
@@ -126421,6 +126545,8 @@ ${baseLabel}_nochk:
|
|
|
126421
126545
|
isRef: (maskInfo.refMask & (1 << i)) != 0,
|
|
126422
126546
|
conv: convs.find(c => c.argIdx == i)
|
|
126423
126547
|
}));
|
|
126548
|
+
if (specializeMapSetFieldId)
|
|
126549
|
+
allArgs = allArgs.filter(a => a.idx != 1);
|
|
126424
126550
|
pxtc.U.assert(allArgs.length <= 4);
|
|
126425
126551
|
let seenUpdate = false;
|
|
126426
126552
|
for (let a of pxtc.U.reversed(allArgs)) {
|
|
@@ -126523,33 +126649,69 @@ ${baseLabel}_nochk:
|
|
|
126523
126649
|
genCall();
|
|
126524
126650
|
}
|
|
126525
126651
|
else {
|
|
126526
|
-
if (
|
|
126652
|
+
if (specializeMapSetFieldId)
|
|
126653
|
+
this.emitMapSetByFieldIdCall(mapSetFieldId);
|
|
126654
|
+
else if (name != "langsupp::ignore")
|
|
126527
126655
|
this.alignedCall(name, "", 0, true);
|
|
126528
126656
|
}
|
|
126529
126657
|
if (clearStack) {
|
|
126530
126658
|
this.clearArgs(complexArgs.filter(a => !a.isRef).map(a => a.expr), complexArgs.filter(a => a.isRef).map(a => a.expr));
|
|
126531
126659
|
}
|
|
126532
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
|
+
}
|
|
126533
126683
|
alignedCall(name, cmt = "", off = 0, saveStack = false) {
|
|
126534
126684
|
if (pxtc.U.startsWith(name, "_cmp_") || pxtc.U.startsWith(name, "_pxt_"))
|
|
126535
126685
|
saveStack = false;
|
|
126536
126686
|
this.write(this.t.call_lbl(name, saveStack, this.stackAlignmentNeeded(off)) + cmt);
|
|
126537
126687
|
}
|
|
126538
|
-
|
|
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) {
|
|
126539
126693
|
if (!this.labelledHelpers[lbl]) {
|
|
126540
126694
|
let outp = this.redirectOutput(generate);
|
|
126541
|
-
this.
|
|
126542
|
-
this.labelledHelpers[lbl] = this.bin.codeHelpers[outp];
|
|
126543
|
-
}
|
|
126544
|
-
else {
|
|
126545
|
-
this.write(this.t.call_lbl(this.labelledHelpers[lbl]));
|
|
126695
|
+
this.labelledHelpers[lbl] = this.helperLabel(outp, lbl);
|
|
126546
126696
|
}
|
|
126697
|
+
return this.labelledHelpers[lbl];
|
|
126547
126698
|
}
|
|
126548
|
-
|
|
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") {
|
|
126549
126707
|
if (!this.bin.codeHelpers[asm]) {
|
|
126550
126708
|
let len = Object.keys(this.bin.codeHelpers).length;
|
|
126551
126709
|
this.bin.codeHelpers[asm] = `_${baseName}_${len}`;
|
|
126552
126710
|
}
|
|
126711
|
+
return this.bin.codeHelpers[asm];
|
|
126712
|
+
}
|
|
126713
|
+
emitHelper(asm, baseName = "hlp") {
|
|
126714
|
+
this.helperLabel(asm, baseName);
|
|
126553
126715
|
this.write(this.t.call_lbl(this.bin.codeHelpers[asm]));
|
|
126554
126716
|
}
|
|
126555
126717
|
pushToExprStack(a) {
|
|
@@ -126586,48 +126748,85 @@ ${baseLabel}_nochk:
|
|
|
126586
126748
|
}
|
|
126587
126749
|
}
|
|
126588
126750
|
}
|
|
126589
|
-
|
|
126590
|
-
|
|
126591
|
-
this.write(`
|
|
126751
|
+
emitFieldMethod(op) {
|
|
126752
|
+
this.write(`
|
|
126592
126753
|
${this.helperObject(op)}
|
|
126593
126754
|
.section code
|
|
126594
126755
|
_pxt_map_${op}:
|
|
126595
126756
|
`);
|
|
126596
|
-
|
|
126597
|
-
|
|
126598
|
-
|
|
126599
|
-
|
|
126600
|
-
|
|
126601
|
-
|
|
126602
|
-
|
|
126603
|
-
|
|
126604
|
-
|
|
126605
|
-
|
|
126606
|
-
|
|
126607
|
-
|
|
126608
|
-
|
|
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(`
|
|
126609
126770
|
push {r0, r2, lr}
|
|
126610
126771
|
mov r0, r1
|
|
126611
126772
|
`);
|
|
126612
|
-
|
|
126613
|
-
|
|
126614
|
-
|
|
126773
|
+
}
|
|
126774
|
+
else {
|
|
126775
|
+
this.write(`
|
|
126615
126776
|
push {r0, lr}
|
|
126616
126777
|
mov r0, r1
|
|
126617
126778
|
`);
|
|
126618
|
-
|
|
126619
|
-
|
|
126779
|
+
}
|
|
126780
|
+
this.write(`
|
|
126620
126781
|
bl pxtrt::lookupMapKey
|
|
126621
126782
|
mov r1, r0 ; put key index in r1
|
|
126622
126783
|
ldr r0, [sp, #0] ; restore obj pointer
|
|
126623
126784
|
mov r3, r4 ; restore vt
|
|
126624
126785
|
bl .dowork
|
|
126625
126786
|
`);
|
|
126626
|
-
|
|
126627
|
-
|
|
126628
|
-
|
|
126629
|
-
|
|
126630
|
-
|
|
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
|
+
`);
|
|
126631
126830
|
}
|
|
126632
126831
|
emitArrayMethod(op, isBuffer) {
|
|
126633
126832
|
this.write(`
|
|
@@ -126713,6 +126912,12 @@ ${baseLabel}_nochk:
|
|
|
126713
126912
|
this.emitArrayMethod(op, false);
|
|
126714
126913
|
}
|
|
126715
126914
|
}
|
|
126915
|
+
emitFieldMethods() {
|
|
126916
|
+
for (let op of ["get", "set"]) {
|
|
126917
|
+
this.emitFieldMethod(op);
|
|
126918
|
+
}
|
|
126919
|
+
this.emitStringMapSetMethod();
|
|
126920
|
+
}
|
|
126716
126921
|
emitLambdaTrampoline() {
|
|
126717
126922
|
let r3 = pxtc.target.stackAlign ? "r3," : "";
|
|
126718
126923
|
this.write(`
|
|
@@ -127060,6 +127265,12 @@ ${baseLabel}_nochk:
|
|
|
127060
127265
|
this.write(`.short 0, 0 ; no captured vars`);
|
|
127061
127266
|
this.write(`.word ${this.proc.label()}_args@fn`);
|
|
127062
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
|
+
}
|
|
127063
127274
|
this.write(`${this.proc.label()}_args:`);
|
|
127064
127275
|
let numargs = this.proc.args.length;
|
|
127065
127276
|
if (numargs == 0)
|
|
@@ -127828,6 +128039,17 @@ var ts;
|
|
|
127828
128039
|
"pxt::fromInt": "_numops_fromInt",
|
|
127829
128040
|
"pxt::switch_eq": "_pxt_switch_eq",
|
|
127830
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
|
+
}
|
|
127831
128053
|
// snippets for ARM Thumb assembly
|
|
127832
128054
|
class ThumbSnippets extends pxtc.AssemblerSnippets {
|
|
127833
128055
|
stackAligned() {
|
|
@@ -127935,6 +128157,8 @@ ${lbl}:`;
|
|
|
127935
128157
|
}
|
|
127936
128158
|
call_lbl(lbl, saveStack, stackAlign) {
|
|
127937
128159
|
let o = pxtc.U.lookup(inlineArithmetic, lbl);
|
|
128160
|
+
if (!o && boolLowerEnabled())
|
|
128161
|
+
o = pxtc.U.lookup(inlineBoolOps, lbl);
|
|
127938
128162
|
if (o) {
|
|
127939
128163
|
lbl = o;
|
|
127940
128164
|
saveStack = false;
|
|
@@ -128106,6 +128330,99 @@ _numops_fromInt:
|
|
|
128106
128330
|
blx lr
|
|
128107
128331
|
.over2:
|
|
128108
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")}
|
|
128109
128426
|
`;
|
|
128110
128427
|
for (let op of Object.keys(pxtc.thumbCmpMap)) {
|
|
128111
128428
|
op = op.replace(/.*::/, "");
|
|
@@ -128130,9 +128447,10 @@ _cmp_${op}:
|
|
|
128130
128447
|
// the cmp isn't really needed, given how toBoolDecr() is compiled,
|
|
128131
128448
|
// but better not rely on it
|
|
128132
128449
|
// Also, cmp isn't needed when ref-counting (it ends with movs r0, r4)
|
|
128450
|
+
const toBoolDecr = boolLowerEnabled() ? "_numops_toBoolDecr" : "numops::toBoolDecr";
|
|
128133
128451
|
r += boxedOp(`
|
|
128134
128452
|
bl numops::${op}
|
|
128135
|
-
bl
|
|
128453
|
+
bl ${toBoolDecr}
|
|
128136
128454
|
cmp r0, #0`);
|
|
128137
128455
|
}
|
|
128138
128456
|
// switch(x){case k:} compiles to equality tests via pxt::switch_eq.
|
|
@@ -133578,6 +133896,7 @@ var ts;
|
|
|
133578
133896
|
this.classInfo = null;
|
|
133579
133897
|
this.perfCounterName = null;
|
|
133580
133898
|
this.perfCounterNo = 0;
|
|
133899
|
+
this.useExactIfaceWrapper = false;
|
|
133581
133900
|
this.body = [];
|
|
133582
133901
|
this.lblNo = 0;
|
|
133583
133902
|
this.action = null;
|
|
@@ -133595,6 +133914,11 @@ var ts;
|
|
|
133595
133914
|
isGetter() {
|
|
133596
133915
|
return this.action && this.action.kind == ts.SyntaxKind.GetAccessor;
|
|
133597
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.
|
|
133598
133922
|
vtLabel() {
|
|
133599
133923
|
return this.label() + (pxtc.isStackMachine() ? "" : "_args");
|
|
133600
133924
|
}
|
|
@@ -134137,6 +134461,11 @@ var ts;
|
|
|
134137
134461
|
argsFmt: ["T", "T", "S", "T"],
|
|
134138
134462
|
value: 0
|
|
134139
134463
|
},
|
|
134464
|
+
"pxtrt::mapSetByStringOnly": {
|
|
134465
|
+
name: "_pxt_map_set_by_string",
|
|
134466
|
+
argsFmt: ["T", "T", "S", "T"],
|
|
134467
|
+
value: 0
|
|
134468
|
+
},
|
|
134140
134469
|
};
|
|
134141
134470
|
let EK = pxtc.ir.EK;
|
|
134142
134471
|
pxtc.SK = ts.SyntaxKind;
|
|
@@ -134870,6 +135199,87 @@ var ts;
|
|
|
134870
135199
|
bin.breakpoints = opts.breakpoints;
|
|
134871
135200
|
bin.name = opts.name;
|
|
134872
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
|
+
}
|
|
134873
135283
|
function reset() {
|
|
134874
135284
|
bin.reset();
|
|
134875
135285
|
proc = null;
|
|
@@ -134950,7 +135360,13 @@ var ts;
|
|
|
134950
135360
|
reset();
|
|
134951
135361
|
needsUsingInfo = false;
|
|
134952
135362
|
bin.finalPass = true;
|
|
135363
|
+
bin.ifaceCallCounts = {};
|
|
135364
|
+
bin.mapSetByFieldIdCounts = {};
|
|
135365
|
+
bin.checkedFieldAccessCounts = {};
|
|
135366
|
+
bin.dynamicIfaceCalls = {};
|
|
135367
|
+
bin.usesMapSetByStringOnly = false;
|
|
134953
135368
|
emit(rootFunction);
|
|
135369
|
+
markExactIfaceWrappers();
|
|
134954
135370
|
pxtc.U.assert(usedWorkList.length == 0);
|
|
134955
135371
|
res.configData = [];
|
|
134956
135372
|
for (let k of Object.keys(configEntries)) {
|
|
@@ -135640,7 +136056,7 @@ ${lbl}: .short 0xffff
|
|
|
135640
136056
|
let coll = pxtc.ir.shared(pxtc.ir.rtcall("Array_::mk", []));
|
|
135641
136057
|
for (let elt of node.elements) {
|
|
135642
136058
|
let mask = isRefCountedExpr(elt) ? (1 << 1) : 0;
|
|
135643
|
-
proc.emitExpr(pxtc.ir.rtcall("Array_::push", [coll,
|
|
136059
|
+
proc.emitExpr(pxtc.ir.rtcall("Array_::push", [coll, emitEscapedExpression(elt, "arrayElement")], mask));
|
|
135644
136060
|
}
|
|
135645
136061
|
if (node.elements.length > 0)
|
|
135646
136062
|
// Make sure there is at least one use of the array, so it doesn't get GC-ed away in the last push.
|
|
@@ -135682,8 +136098,11 @@ ${lbl}: .short 0xffff
|
|
|
135682
136098
|
if (isRefCountedExpr(p.initializer))
|
|
135683
136099
|
mask |= 1 << 2;
|
|
135684
136100
|
}
|
|
136101
|
+
const nativeFieldId = pxtc.target.isNative ? getIfaceMemberId(keyName) : undefined;
|
|
136102
|
+
if (nativeFieldId !== undefined)
|
|
136103
|
+
bin.mapSetByFieldIdCounts[nativeFieldId + ""] = (bin.mapSetByFieldIdCounts[nativeFieldId + ""] || 0) + 1;
|
|
135685
136104
|
const fieldId = pxtc.target.isNative
|
|
135686
|
-
? pxtc.ir.numlit(
|
|
136105
|
+
? pxtc.ir.numlit(nativeFieldId)
|
|
135687
136106
|
: pxtc.ir.ptrlit(null, JSON.stringify(keyName));
|
|
135688
136107
|
const args = [
|
|
135689
136108
|
expr,
|
|
@@ -135743,6 +136162,9 @@ ${lbl}: .short 0xffff
|
|
|
135743
136162
|
return emitShim(fld, decl, [node.expression]);
|
|
135744
136163
|
}
|
|
135745
136164
|
else {
|
|
136165
|
+
if (idx.needsCheck && !pxtc.target.switches.skipClassCheck) {
|
|
136166
|
+
bin.checkedFieldAccessCounts[checkedFieldAccessCountKey(idx)] = (bin.checkedFieldAccessCounts[checkedFieldAccessCountKey(idx)] || 0) + 1;
|
|
136167
|
+
}
|
|
135746
136168
|
return pxtc.ir.op(EK.FieldAccess, [emitExpr(node.expression)], idx);
|
|
135747
136169
|
}
|
|
135748
136170
|
}
|
|
@@ -135796,7 +136218,17 @@ ${lbl}: .short 0xffff
|
|
|
135796
136218
|
indexer = assign ? attrs.indexerSet : attrs.indexerGet;
|
|
135797
136219
|
}
|
|
135798
136220
|
if (!indexer && (t.flags & (ts.TypeFlags.Any | ts.TypeFlags.StructuredOrTypeVariable))) {
|
|
135799
|
-
|
|
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";
|
|
135800
136232
|
stringOk = true;
|
|
135801
136233
|
}
|
|
135802
136234
|
if (indexer) {
|
|
@@ -136133,7 +136565,7 @@ ${lbl}: .short 0xffff
|
|
|
136133
136565
|
lst.push(tracked);
|
|
136134
136566
|
}
|
|
136135
136567
|
function emitPlain() {
|
|
136136
|
-
let r = mkProcCall(decl, node, args.map(
|
|
136568
|
+
let r = mkProcCall(decl, node, args.map(x => emitEscapedExpression(x, "callArgument")));
|
|
136137
136569
|
let pp = r.data;
|
|
136138
136570
|
if (args[0] && pp.proc && pp.proc.classInfo)
|
|
136139
136571
|
pp.isThis = args[0].kind == pxtc.SK.ThisKeyword;
|
|
@@ -136160,7 +136592,7 @@ ${lbl}: .short 0xffff
|
|
|
136160
136592
|
baseCtor = p.ctor;
|
|
136161
136593
|
if (!baseCtor && bin.finalPass)
|
|
136162
136594
|
throw userError(9280, lf("super() call requires an explicit constructor in base class"));
|
|
136163
|
-
let ctorArgs = args.map((x) =>
|
|
136595
|
+
let ctorArgs = args.map((x) => emitEscapedExpression(x, "callArgument"));
|
|
136164
136596
|
ctorArgs.unshift(emitThis(funcExpr));
|
|
136165
136597
|
return mkProcCallCore(baseCtor, node, ctorArgs);
|
|
136166
136598
|
}
|
|
@@ -136176,9 +136608,11 @@ ${lbl}: .short 0xffff
|
|
|
136176
136608
|
// TODO in VT accessor/field/method -> different
|
|
136177
136609
|
pxtc.U.assert(funcExpr.kind == pxtc.SK.PropertyAccessExpression);
|
|
136178
136610
|
const fieldName = funcExpr.name.text;
|
|
136611
|
+
const ifaceIndex = getIfaceMemberId(fieldName, true);
|
|
136612
|
+
bin.dynamicIfaceCalls[ifaceIndex + ""] = true;
|
|
136179
136613
|
// completely dynamic dispatch
|
|
136180
|
-
return mkMethodCall(args.map((x) =>
|
|
136181
|
-
ifaceIndex
|
|
136614
|
+
return mkMethodCall(args.map((x) => emitEscapedExpression(x, "callArgument")), {
|
|
136615
|
+
ifaceIndex,
|
|
136182
136616
|
callLocationIndex: markCallLocation(node),
|
|
136183
136617
|
noArgs
|
|
136184
136618
|
});
|
|
@@ -136202,7 +136636,7 @@ ${lbl}: .short 0xffff
|
|
|
136202
136636
|
pxtc.U.assert(false);
|
|
136203
136637
|
}
|
|
136204
136638
|
pxtc.U.assert(!bin.finalPass || info.virtualIndex != null, "!bin.finalPass || info.virtualIndex != null");
|
|
136205
|
-
return mkMethodCall(args.map((x) =>
|
|
136639
|
+
return mkMethodCall(args.map((x) => emitEscapedExpression(x, "callArgument")), {
|
|
136206
136640
|
classInfo: info.parentClassInfo,
|
|
136207
136641
|
virtualIndex: info.virtualIndex,
|
|
136208
136642
|
noArgs,
|
|
@@ -136242,9 +136676,15 @@ ${lbl}: .short 0xffff
|
|
|
136242
136676
|
return emitPlain();
|
|
136243
136677
|
}
|
|
136244
136678
|
else if (needsVCall || pxtc.target.switches.slowMethods || !forceMethod) {
|
|
136245
|
-
|
|
136246
|
-
|
|
136247
|
-
|
|
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,
|
|
136248
136688
|
callLocationIndex: markCallLocation(node),
|
|
136249
136689
|
noArgs
|
|
136250
136690
|
});
|
|
@@ -136263,7 +136703,7 @@ ${lbl}: .short 0xffff
|
|
|
136263
136703
|
// here's where we will recurse to generate funcExpr
|
|
136264
136704
|
args.unshift(funcExpr);
|
|
136265
136705
|
pxtc.U.assert(!noArgs);
|
|
136266
|
-
return mkMethodCall(args.map(x =>
|
|
136706
|
+
return mkMethodCall(args.map(x => emitEscapedExpression(x, "callArgument")), {
|
|
136267
136707
|
virtualIndex: -1,
|
|
136268
136708
|
callLocationIndex: markCallLocation(node),
|
|
136269
136709
|
noArgs
|
|
@@ -136359,6 +136799,8 @@ ${lbl}: .short 0xffff
|
|
|
136359
136799
|
}
|
|
136360
136800
|
}
|
|
136361
136801
|
function getCtor(decl) {
|
|
136802
|
+
if (!decl.members)
|
|
136803
|
+
return null;
|
|
136362
136804
|
return decl.members.filter(m => m.kind == pxtc.SK.Constructor)[0];
|
|
136363
136805
|
}
|
|
136364
136806
|
function isIfaceMemberUsed(name) {
|
|
@@ -136406,7 +136848,7 @@ ${lbl}: .short 0xffff
|
|
|
136406
136848
|
// let sig = checker.getResolvedSignature(node)
|
|
136407
136849
|
// TODO: can we have overloeads?
|
|
136408
136850
|
addDefaultParametersAndTypeCheck(checker.getResolvedSignature(node), args, ctorAttrs);
|
|
136409
|
-
let compiled = args.map((x) =>
|
|
136851
|
+
let compiled = args.map((x) => emitEscapedExpression(x, "callArgument"));
|
|
136410
136852
|
if (ctorAttrs.shim) {
|
|
136411
136853
|
// TODO need to deal with refMask and tagged ints here
|
|
136412
136854
|
// we drop 'obj' variable
|
|
@@ -136795,7 +137237,7 @@ ${lbl}: .short 0xffff
|
|
|
136795
137237
|
for (let f of initalizedFields) {
|
|
136796
137238
|
let idx = fieldIndexCore(proc.classInfo, getFieldInfo(proc.classInfo, getName(f)), false);
|
|
136797
137239
|
let trg2 = pxtc.ir.op(EK.FieldAccess, [emitLocalLoad(info.thisParameter)], idx);
|
|
136798
|
-
proc.emitExpr(pxtc.ir.op(EK.Store, [trg2,
|
|
137240
|
+
proc.emitExpr(pxtc.ir.op(EK.Store, [trg2, emitEscapedExpression(f.initializer, "initializer")]));
|
|
136799
137241
|
}
|
|
136800
137242
|
flushHoistedFunctionDefinitions();
|
|
136801
137243
|
if (node.body.kind == pxtc.SK.Block) {
|
|
@@ -137013,9 +137455,12 @@ ${lbl}: .short 0xffff
|
|
|
137013
137455
|
isRef: true,
|
|
137014
137456
|
shimName: attrs.shim,
|
|
137015
137457
|
classInfo: info,
|
|
137016
|
-
needsCheck
|
|
137458
|
+
needsCheck,
|
|
137017
137459
|
};
|
|
137018
137460
|
}
|
|
137461
|
+
function checkedFieldAccessCountKey(info) {
|
|
137462
|
+
return `${info.classInfo.id}:${info.name}:get`;
|
|
137463
|
+
}
|
|
137019
137464
|
function fieldIndex(pacc) {
|
|
137020
137465
|
const tp = typeOf(pacc.expression);
|
|
137021
137466
|
if (isPossiblyGenericClassType(tp)) {
|
|
@@ -137046,7 +137491,7 @@ ${lbl}: .short 0xffff
|
|
|
137046
137491
|
if (decl && (isGlobal || isVar(decl) || isParameter(decl))) {
|
|
137047
137492
|
let l = lookupCell(decl);
|
|
137048
137493
|
recordUse(decl, true);
|
|
137049
|
-
proc.emitExpr(l.storeByRef(
|
|
137494
|
+
proc.emitExpr(l.storeByRef(emitEscapedExpression(src, "assignment")));
|
|
137050
137495
|
}
|
|
137051
137496
|
else {
|
|
137052
137497
|
unhandled(trg, lf("bad target identifier"), 9248);
|
|
@@ -137062,7 +137507,10 @@ ${lbl}: .short 0xffff
|
|
|
137062
137507
|
}
|
|
137063
137508
|
proc.emitExpr(emitCallCore(trg, trg, [src], null, decl));
|
|
137064
137509
|
}
|
|
137065
|
-
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)) {
|
|
137066
137514
|
proc.emitExpr(emitCallCore(trg, trg, [src], null, decl));
|
|
137067
137515
|
}
|
|
137068
137516
|
else {
|
|
@@ -137078,7 +137526,7 @@ ${lbl}: .short 0xffff
|
|
|
137078
137526
|
}
|
|
137079
137527
|
}
|
|
137080
137528
|
let trg2 = emitExpr(trg);
|
|
137081
|
-
proc.emitExpr(pxtc.ir.op(EK.Store, [trg2,
|
|
137529
|
+
proc.emitExpr(pxtc.ir.op(EK.Store, [trg2, emitEscapedExpression(src, "assignment")]));
|
|
137082
137530
|
break;
|
|
137083
137531
|
}
|
|
137084
137532
|
}
|
|
@@ -137489,7 +137937,7 @@ ${lbl}: .short 0xffff
|
|
|
137489
137937
|
let mask = getMask(args);
|
|
137490
137938
|
let convInfos = [];
|
|
137491
137939
|
let args2 = args.map((a, i) => {
|
|
137492
|
-
let r =
|
|
137940
|
+
let r = emitEscapedExpression(a, "runtimeCallArgument");
|
|
137493
137941
|
if (!needsNumberConversions())
|
|
137494
137942
|
return r;
|
|
137495
137943
|
let f = fmt[i + 1];
|
|
@@ -137827,6 +138275,43 @@ ${lbl}: .short 0xffff
|
|
|
137827
138275
|
emitExprAsStmt(node.expression);
|
|
137828
138276
|
}
|
|
137829
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
|
+
}
|
|
137830
138315
|
if (!inner && isThumb() && expr.kind == pxtc.SK.BinaryExpression) {
|
|
137831
138316
|
let be = expr;
|
|
137832
138317
|
let mapped = pxtc.U.lookup(pxtc.thumbCmpMap, simpleInstruction(be, be.operatorToken.kind));
|
|
@@ -138278,7 +138763,10 @@ ${lbl}: .short 0xffff
|
|
|
138278
138763
|
}
|
|
138279
138764
|
}
|
|
138280
138765
|
typeCheckSubtoSup(node.initializer, node);
|
|
138281
|
-
|
|
138766
|
+
const initializerExpr = isGlobalVar(node)
|
|
138767
|
+
? emitEscapedExpression(node.initializer, "initializer")
|
|
138768
|
+
: emitExpr(node.initializer);
|
|
138769
|
+
proc.emitExpr(loc.storeByRef(initializerExpr));
|
|
138282
138770
|
currJres = null;
|
|
138283
138771
|
proc.stackEmpty();
|
|
138284
138772
|
}
|
|
@@ -138303,8 +138791,17 @@ ${lbl}: .short 0xffff
|
|
|
138303
138791
|
exres = pxtc.ir.op(EK.FieldAccess, [objRef], fieldIndexCore(info, getFieldInfo(info, fieldName)));
|
|
138304
138792
|
}
|
|
138305
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;
|
|
138306
138803
|
exres = mkMethodCall([objRef], {
|
|
138307
|
-
ifaceIndex
|
|
138804
|
+
ifaceIndex,
|
|
138308
138805
|
callLocationIndex: markCallLocation(node),
|
|
138309
138806
|
noArgs: true
|
|
138310
138807
|
});
|
|
@@ -138687,6 +139184,11 @@ ${lbl}: .short 0xffff
|
|
|
138687
139184
|
this.usedChars = new Uint32Array(0x10000 / 32);
|
|
138688
139185
|
this.explicitlyUsedIfaceMembers = {};
|
|
138689
139186
|
this.ifaceMemberMap = {};
|
|
139187
|
+
this.ifaceCallCounts = {};
|
|
139188
|
+
this.mapSetByFieldIdCounts = {};
|
|
139189
|
+
this.checkedFieldAccessCounts = {};
|
|
139190
|
+
this.dynamicIfaceCalls = {};
|
|
139191
|
+
this.usesMapSetByStringOnly = false;
|
|
138690
139192
|
this.strings = {};
|
|
138691
139193
|
this.hexlits = {};
|
|
138692
139194
|
this.doubles = {};
|
|
@@ -141137,9 +141639,9 @@ ${lbl}: ${snippets.obj_header("pxt::number_vt")}
|
|
|
141137
141639
|
uint32_t ifaceHashMult;
|
|
141138
141640
|
PVoid methods[2 or 4];
|
|
141139
141641
|
*/
|
|
141140
|
-
const ifaceInfo =
|
|
141141
|
-
|
|
141142
|
-
|
|
141642
|
+
const ifaceInfo = info.itable.length
|
|
141643
|
+
? computeHashMultiplier(info.itable.map(e => e.idx))
|
|
141644
|
+
: { mult: 0, mapping: new Uint16Array(0), size: 0 };
|
|
141143
141645
|
let ptrSz = pxtc.target.shortPointers ? ".short" : ".word";
|
|
141144
141646
|
let s = `
|
|
141145
141647
|
.object ${info.id}_VT
|
|
@@ -141173,6 +141675,11 @@ ${info.id}_IfaceVT:
|
|
|
141173
141675
|
`;
|
|
141174
141676
|
const descSize = 8;
|
|
141175
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";
|
|
141176
141683
|
let descs = "";
|
|
141177
141684
|
let offset = zeroOffset;
|
|
141178
141685
|
let offsets = {};
|
|
@@ -141180,11 +141687,11 @@ ${info.id}_IfaceVT:
|
|
|
141180
141687
|
offsets[e.idx + ""] = offset;
|
|
141181
141688
|
const desc = !e.proc ? 0 : e.proc.isGetter() ? 1 : 2;
|
|
141182
141689
|
descs += ` .short ${e.idx}, ${desc} ; ${e.name}\n`;
|
|
141183
|
-
descs += ` .word ${e.proc ? e.proc
|
|
141690
|
+
descs += ` .word ${e.proc ? procIfaceLabel(e.proc) : e.info}\n`;
|
|
141184
141691
|
offset += descSize;
|
|
141185
141692
|
if (e.setProc) {
|
|
141186
141693
|
descs += ` .short ${e.idx}, 0 ; set ${e.name}\n`;
|
|
141187
|
-
descs += ` .word ${e.setProc
|
|
141694
|
+
descs += ` .word ${procIfaceLabel(e.setProc)}\n`;
|
|
141188
141695
|
offset += descSize;
|
|
141189
141696
|
}
|
|
141190
141697
|
}
|
|
@@ -141197,7 +141704,9 @@ ${info.id}_IfaceVT:
|
|
|
141197
141704
|
bin.itFullEntries++;
|
|
141198
141705
|
}
|
|
141199
141706
|
// offsets are relative to the position in the array
|
|
141200
|
-
|
|
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";
|
|
141201
141710
|
s += descs;
|
|
141202
141711
|
s += "\n";
|
|
141203
141712
|
return s;
|