<table border="1" cellspacing="0" cellpadding="8">
<tr>
<th>Issue</th>
<td>
<a href=https://github.com/llvm/llvm-project/issues/202112>202112</a>
</td>
</tr>
<tr>
<th>Summary</th>
<td>
[AggressiveInstCombine] TruncInstCombine cannot narrow any expression that reaches a function Argument
</td>
</tr>
<tr>
<th>Labels</th>
<td>
</td>
</tr>
<tr>
<th>Assignees</th>
<td>
</td>
</tr>
<tr>
<th>Reporter</th>
<td>
ravn
</td>
</tr>
</table>
<pre>
I am currently working on replacing firmware and "bios" on an old Z80 machine with modern versions in C23 on a yet unsubmitted z80 backend. On the Z80 16-bit ints are much more expensive than 8-bit, and code space in my use case is at a premium. I have therefore spent quite some time looking for suboptimal code generation spacewise with the help of Claude Code, which has uncovered a few corner cases.
This is the first upstream bug I try to file here. I would appreciate gentle help in getting it right if for any reason this is not satisfactory.
In this process it was found that the "can we do the whole calculation in 8-bit" didn't work if it included a function argument declared not to be 8-bit (like in the K&R source I was using as a test case).
```c
typedef unsigned char uint8_t;
uint8_t rotl(x) uint8_t x; /* default argument promotion -> int */
{ return (x << 1) | (x >> 7); }
```
Claude suggests that this is because TruncInstCombine::buildTruncExpressionGraph() does not consider Arguments to be acceptable Instructions for this, so the narrowing is then not even considered:
https://github.com/llvm/llvm-project/blob/de59f9ed12db9d47ad41ad44d54ec604ef8841cb/llvm/lib/Transforms/AggressiveInstCombine/TruncInstCombine.cpp#L95-L105
The snippet shows how constants are processed. The suggestion is that Arguments are treated similarly in something looking like:
```cpp
auto *I = dyn_cast<Instruction>(Curr);
if (!I) {
// Function arguments (and other non-instruction values that are not
// Constants) can appear as operands in the expression graph. Treat
// them as leaves — they'll be explicitly truncated at narrowing time
// in getReducedOperand. Without this, expressions rooted at function
// parameters (e.g., K&R-style u8 parameters that get int-promoted at
// the ABI boundary on small-int targets) can never be narrowed back
// to their natural width.
if (isa<Argument>(Curr)) {
Worklist.pop_back();
continue;
}
return false;
}
```
We carry this as a local patch.
---
The deeper explanation below was written by Claude Code (claims verified against llvm-project `de59f9ed`); I have reviewed it and include it for completeness.
---
TruncInstCombine narrows `trunc(iN expr)` graphs to the destination width when the analysis proves it safe. Its expression walker ([`buildTruncExpressionGraph`](https://github.com/llvm/llvm-project/blob/de59f9ed12db9d47ad41ad44d54ec604ef8841cb/llvm/lib/Transforms/AggressiveInstCombine/TruncInstCombine.cpp#L87-L110)) accepts `Instruction` and `Constant` nodes; a function `Argument` is neither, so the walk aborts. Note the rejection happens while *building* the graph — before the min-bitwidth analysis ever runs — so it is not a safety conclusion; the safety machinery is simply never consulted.
The K&R function above lowers (clang -O1) to:
```llvm
define zeroext i8 @rotl(i16 noundef %x) {
%m = and i16 %x, 255
%s = shl nuw nsw i16 %m, 1
%r = lshr i16 %m, 7
%o = or disjoint i16 %s, %r
%t = trunc i16 %o to i8
ret i8 %t
}
```
Repro: `opt -passes=aggressive-instcombine -S` on current main returns this IR unchanged, although everything is computable in i8 (the `and 255` proves the value fits). The expected result is i8 shifts/or fronted by a single `trunc i16 %x to i8`.
The parameter is precisely the trigger: give the same function an ANSI prototype (`uint8_t rotl(uint8_t x)`) and the value enters the expression through a `zext` — an Instruction — and TruncInstCombine narrows it today. Likewise if `%x` came from a load.
An `Argument` imposes no width requirement of its own (the existing min-bitwidth analysis is driven by the instructions), so treating it as a leaf — analogous to the existing `Constant` handling, with one explicit `trunc` of the argument materialized at function entry — appears sufficient.
The real-world function the test case was reduced from is the inverse S-box of Ilya O. Levin's byte-oriented AES-256 implementation (literatecode.com, ISC-style license), in its legacy K&R form:
```c
uint8_t rj_sb_inv(x)
uint8_t x;
{
uint8_t y, sb;
y = x ^ 0x63;
sb = y = (y<<1)|(y>>7);
y = (y<<2)|(y>>6); sb ^= y; y = (y<<3)|(y>>5); sb ^= y;
return gf_mulinv(sb);
}
```
Every operation chains off the promoted parameter, so the entire body is stuck at i16. Measured on the Z80 backend: **147 B** for this K&R form vs **16 B** for the ANSI-prototype equivalent (~9x), pure 16-bit shift/mask/or traffic standing in for single-byte rotates. With the Argument-leaf patch it drops to 31 B; the remaining gap is a separate rotate-idiom-recognition issue on the already-narrowed IR, not this bug. The same shape appears in any legacy K&R C compiled for a 16-bit-int target.
The repro is target-independent.
</pre>
<img width="1" height="1" alt="" src="http://email.email.llvm.org/o/eJzUWE1v4ziT_jXMpWBDluOPHHJw3Mki2N5poHuAAfbSoMiSxAlFakjKH33Y376oomQ76byH9_g2BphYLBXJqqeeekoyRtM4xEexehKrL3dySK0Pj0Ee3F3l9fnxFWQHaggBXbJnOPrwZlwD3kHA3kpFP2oTuqMMCNJpEGVZGR9FWZKRdOCthv_dFtBJ1RqHcDSphc5rDA4OGKLxLoJxsC-X_AacMcHg4lB1JiXU8GtbQCXVGzo9B_jmILXIHhfrWWUSGJci0PbdoMhzQMBTjy6aA0JqpYMt2YlyzwdUXiPEXiqkXbszDBFByYhgIsgEEvqAnRm6OcArtJKdYMCaHMceXYJ_BpMQou8QkukQrPccldoHiEPl-2Q6afNODToMMhnv8qZHE8cY0DVatD34GvZWDhph7zXSOY-tUS20MsLglD9gQA0SajyC8sFh4PPGuSh2otj92ZpIZyd_tQkxwdDHFFB2UA0NvEIKZ0geamNpw4BzoJsd_WA1yL4PqIxMfNJkxyMZBw2mRJcyCYJp2gSm5gtKd4aAMnpKRN7Z-QRRJhNrqZIP5_Fgr6NFH7zCGMnTUUao_eA0JSbxkUVZKungiKA9Pzi23lJGrBpsDpy5pLAEbbQT5SYxFOlMjABlB51jNDjF78jQDB0lS6OykgJIp0weKszOQJRba94YBbTtf4ty_R2iH4JCCg8FP1IAZAQJCWPiqIvyYbyeWBf5PyWKXTr3qLEm4FJBaVCtDDAYl7Y_k1g-iWI3_oDgkxXl9iTKh8kATmL5BJd_onwR5Q401nKw6XqVPvjO8-1mYvlMwAdR7si62InNEwRMQ3B0sROI5V4s97CgXcRmPz18phc3onygDcXmy-098rVGKMahaTCmOCUqZ7pCJale_gyDU68upr3vKuNQLHdiuasGYzUvPZ_6gJFq-7-C7FtRbukc2mMGi_IuGo0BduPV4pgZqRT2SVYWgbyHgZMZGXh0BiqOmGHiZAj-yAhl7Dv2jAd0F_eo6Vx8qzalPtIvCu1LY1I7VHPlO1G-WHuY_jfrg_8bVRLlS2V9JcoXjauH-gH1otTVg77fSH2_kPr-Xq_uUa2Le6y32_uFqm78GPrxZ5Au1j50UZQvu6bhaBzwNmRk9D6Kc9X3olx-fVjNvi6K1VTeCNGZvscEsfXHCK0_8hWTnJhvrDAkgmT7nDyunTGD10jTC0QPxK3RdMbKYM9UBURoqaWIToRG9XEJ4RXvfS-KHQFVDskTBF9BLL-APrufSsYklvub5BHmyu1-CCGjbnzV1MCoWLxmhE7PR_S_wMvHSo70AjG4Jz4G593MXLeBg7QDjpelKzqfPrrcT0GjLYl0ZN-jDFThvscgnY4TG-AFwNAQgimwFLOPLlOLHb1vUR4wgnguxbYQD_e0cBblxlqCNZ56a5ShDpoo5xx7mW4wTH3ko-9Mwt9RDwr1t3zAOcBfJrV-SJd6uB41QvB-dD0R4UenvQyyw4SBw4nzZk4-mPxmMZ0twrC9NeJ4NshtdpYJiDf4JBCwe3qFithdhjP18thJa2dEU0mGBq9xd3jAQIHJAUDNDf43l1zoJoCTaQjSwtHo1M4vZhlCJkqx3E_wfo-2D8gC-MuHN2timve-_8l7MjNdYUn_lHfJuAFvn2aqzH-PNFtLG29sPiXTv6iRBeq_RKDcSaxX0kIvk2rHRjKbza6lrhF7DIwY6XL7q9D6IzekYyBJ5KA63yoGioKy0nSRBJWpDSWokVQccMtqINbFxGd0wtwERo0T8GCQMmESy6Sxp9JPIl_lu95iQocx_nbsDzQ2ZjXSfgx3ytIfjFPac13kkopjgkETVY135RTDkeiclqST9hyzhqD6MiQ0apzDa4q3NXqU9g0DM8rqSayLf92J1oVYfRHl9j-qIWw3s6-LRTFCOjdJDu8tz66LLMDXxcRz9Mh5jZHyfKONxLq41Mu6YAWHhkj1pr1SREFWPqQ4hz98YhUMASka5KMl7nSRpKolBObmb1xDyoVMOcc3hFhlBU1LnXGkwHKuLylmTgiDu2XR6FnfZdUgOffpTAWq7BC5uTyxx3FhnDHCmV6JpuvteeQa6peDTajn11LLiu-qGCt_IC1_HLlRWekamH1jCZX8732Q81vsNNYE-l8YPJ4SmC2I-2IUeWaxBkeMiMRVq9MtI4ly1XHf5HJbrEeDPZSr1WQQ2SC2FtxwBBePk2FHhovJLLCZjW14t76Z1j2v-wDaxL89EfJoxg2EHEyWiS25aicbT3VqtmwRMF-wXCXWnJ9Q3nfsAwWLUOb7BLNexkgQ_CIvqOfGrUa2mP0gEHo3jZnQSeNGko2ZOF-_0yDUSteg5jnOUgtsWsbMOWsWE5mkhqwejcvn3PKIsS4oxhTXdTFRCS2wZoDacGeas3Si0VFRiwtIgCG3ZguxNTUZvfgAdfCODKozIdK4xuKF6S55HGO2Lm7wdumqwISGykQkRdCSHjNNg4HC1uSxlTDd4Q06Hez--PFKp0-epg0mu3XxYaq4DhTMtEwXPG1Nl0U3tvV3Gie1geMp6Sa_8MS8cC1D6W7l-LsF_dsscKF_Q_OWluc5fDVvefCllk2nWp1oA8U3DL7jtiin2tz9zlBd7yPPDmODCPjPYALyVORpBozgj27KN55M5Nn1c6IxEXQwh9xHyf5GRkbm2MyCpPfGCTh3bpT1u7tL6xs_XPrYZdsPFNxKpy0z4z5P_t5dJeG1SVIR1LnrTQNfJxMGI6359V7UURbD-fYsLGQjxKGujTLo0g3uAko7O_pg9dUBg26aaVlbhKwzcz7GzwnGHTBEhB-zyp_odK_2LOHbHL7iwdAYHqE6J5z5QFuiht3zj1m5WlPCLGcnd3UetRMGmVB5jbnP7uH1x37UnNYodDxc03Mq3kSaupHqPNG0D90no8jtXP33z1j9NO4wDtc3S6cs065acFo4c66rcTmvnZkCTyBWz1Cc1surxIsVL2UDUW7PecimBiE2-_yABuzNO0n50bz8aL4elRi5Xz3zDvTz43vLj--tPn3vepFRpzb1z26wOS6xuhztU_J-Jj7NwxAnTrUkI8HXGZgX_X-hshvFgC6ZgFB5ndtvGtQbgdYs1nP4H5RxCKjBX7_fjR_1uFeUO1HuFvcbeMp_Xib-m_TDIU6G6w92yOQ4u5Ij8cNBWuRPJNv_eziN0OqHgNOHQyZ1Ub50Mr5lak9BUv0Ala7m0nf5ox6z_IywTkQrE8Y5z2F567FcZ0wQrOyJM3TwPXPDcgFPk1AJSN2NXDey5y-OEJGiefE8M9r4bhZQ-caZcYaPA06Rkzag1OfZZXR6_U4X469bFK9qaHIn4_4RW9njhR6M48937wprz23TWCp9H0CO0bkZ295RSR88swOvzIzT2KPTRDh3-nGpH5YP8g4fF5ttsV083Bf3d-3j-qGoHpbbYol6U64323JVynq9UbKuF_Viqe7MY1mU62JdbIptsSzv53WpFqgesNiolV6XW3FfUNzsnCTX3IfmjkNCry0W5Z2VFdo4fcMOj6zcq6GJ4r6gaS9e30smWf7a_bkEX335vZ0p6Si4Od4cv3eNUyYiWNVivJXYEybuhmAf_-1Zg29HcmO84OGx_P8AAAD__4XxswM">