@@ -2093,12 +2093,15 @@ static std::string downgradeIRForPeano(StringRef ir) {
20932093 else
20942094 pos = end;
20952095 }
2096- // Rewrite LLVM 23+ 'f0x<8hex>' typed float literals to the double-widened
2097- // '0x<16hex>' form that Peano's LLVM 21 opt can parse.
2098- // LLVM 23 introduced a compact syntax for 32-bit float constants (the 'f'
2099- // prefix encodes the type and the 8 hex digits encode the IEEE-754 bits
2100- // directly). Older LLVM (including Peano's LLVM 21) only accepts float
2101- // constants as their value widened to double, written as 0x<16 hex digits>.
2096+ // Rewrite 'f0x<8hex>' typed float literals to the double-widened '0x<16hex>'
2097+ // form that Peano's LLVM 21 opt can parse.
2098+ // Introduced by llvm/llvm-project@41c214f0b115 (2026-05-07,
2099+ // "[AsmWriter] Change the output syntax of floating-point literals.",
2100+ // https://github.com/llvm/llvm-project/pull/190649): "the hexadecimal
2101+ // output generally changes to f0x... notation, and is used when 6 decimal
2102+ // digits are insufficient to accurately represent the number."
2103+ // Older LLVM (including Peano's LLVM 21) only accepts float constants
2104+ // widened to double, written as 0x<16 hex digits>.
21022105 // Only match at token boundaries: the character before 'f' must not be an
21032106 // identifier character (to avoid matching inside %f0xDEAD or similar), and
21042107 // the character after the 8 hex digits must not be a hex digit (to avoid
@@ -2144,6 +2147,167 @@ static std::string downgradeIRForPeano(StringRef ir) {
21442147 }
21452148 }
21462149 }
2150+ // Rewrite decimal bfloat16 literals ('bfloat N.NNe+NN') to the hexadecimal
2151+ // form ('bfloat 0xR<4hex>') that Peano's LLVM 21 opt can parse.
2152+ // Also introduced by llvm/llvm-project@41c214f0b115 (2026-05-07,
2153+ // "[AsmWriter] Change the output syntax of floating-point literals.",
2154+ // https://github.com/llvm/llvm-project/pull/190649): "extends the base
2155+ // decimal output literal to support non-double types." Peano's LLVM 21 can
2156+ // only parse bfloat constants in the 0xR-prefixed bit-exact hex form. The
2157+ // conversion uses round-to-nearest-even so that the encoded bits match the
2158+ // original bfloat16 constant exactly.
2159+ {
2160+ // Match "bfloat" followed by a decimal number (not already 0x-prefixed).
2161+ const std::string bfPfx = " bfloat " ;
2162+ pos = 0 ;
2163+ while ((pos = result.find (bfPfx, pos)) != std::string::npos) {
2164+ size_t numStart = pos + bfPfx.size ();
2165+ // Skip if this is already a hex constant (0x / 0xR / 0xH …).
2166+ if (numStart + 1 < result.size () && result[numStart] == ' 0' &&
2167+ result[numStart + 1 ] == ' x' ) {
2168+ pos = numStart;
2169+ continue ;
2170+ }
2171+ // Collect an optional leading '-' and then digits/dot/exponent chars.
2172+ size_t numEnd = numStart;
2173+ if (numEnd < result.size () && result[numEnd] == ' -' )
2174+ ++numEnd;
2175+ // Must start with a digit.
2176+ if (numEnd >= result.size () ||
2177+ !std::isdigit (static_cast <unsigned char >(result[numEnd]))) {
2178+ pos = numStart;
2179+ continue ;
2180+ }
2181+ while (numEnd < result.size () &&
2182+ (std::isdigit (static_cast <unsigned char >(result[numEnd])) ||
2183+ result[numEnd] == ' .' || result[numEnd] == ' e' ||
2184+ result[numEnd] == ' E' || result[numEnd] == ' +' ||
2185+ result[numEnd] == ' -' ))
2186+ ++numEnd;
2187+ std::string numStr = result.substr (numStart, numEnd - numStart);
2188+ // Parse as float32 and convert to bfloat16 via round-to-nearest-even.
2189+ // bfloat16 shares the float32 exponent; its 16 bits are the top 16 bits
2190+ // of float32 (after RNE rounding).
2191+ char *endp = nullptr ;
2192+ float fval = std::strtof (numStr.c_str (), &endp);
2193+ // Require that strtof consumed the *entire* numStr; if it stopped early
2194+ // (e.g. on an unexpected character) we must not rewrite the token using
2195+ // a partially-parsed value.
2196+ if (!endp || endp != numStr.c_str () + numStr.size ()) {
2197+ pos = numEnd;
2198+ continue ;
2199+ }
2200+ uint32_t f32bits;
2201+ std::memcpy (&f32bits, &fval, sizeof (f32bits));
2202+ // Round-to-nearest-even: add 0x7FFF + the LSB of the bfloat16 position.
2203+ uint32_t lsb = (f32bits >> 16 ) & 1u ;
2204+ uint16_t bf16bits =
2205+ static_cast <uint16_t >((f32bits + 0x7FFFu + lsb) >> 16 );
2206+ // Format as "bfloat 0xR" followed by 4 uppercase hex digits.
2207+ std::string replacement = " bfloat 0xR" ;
2208+ for (int shift = 12 ; shift >= 0 ; shift -= 4 )
2209+ replacement += " 0123456789ABCDEF" [(bf16bits >> shift) & 0xFu ];
2210+ result.replace (pos, numEnd - pos, replacement);
2211+ pos += replacement.size ();
2212+ }
2213+ }
2214+ // Second pass: rewrite bare decimal bfloat constants that appear without an
2215+ // explicit type prefix (e.g. 'fmul bfloat %x, 1.445310e+00'). In such
2216+ // instructions LLVM 23 omits the type keyword before the constant operand;
2217+ // Peano's LLVM 21 cannot parse the decimal form in this context either.
2218+ // Strategy: scan line-by-line; for any line whose instruction type is
2219+ // 'bfloat', convert every bare decimal float operand on that line.
2220+ {
2221+ auto convertDecimalBf = [&](uint32_t f32bits) -> std::string {
2222+ uint32_t lsb = (f32bits >> 16 ) & 1u ;
2223+ uint16_t bf16bits =
2224+ static_cast <uint16_t >((f32bits + 0x7FFFu + lsb) >> 16 );
2225+ std::string r = " 0xR" ;
2226+ for (int sh = 12 ; sh >= 0 ; sh -= 4 )
2227+ r += " 0123456789ABCDEF" [(bf16bits >> sh) & 0xFu ];
2228+ return r;
2229+ };
2230+ // We need to process line-by-line, so work on a copy split into lines.
2231+ std::string out;
2232+ out.reserve (result.size ());
2233+ size_t lineStart = 0 ;
2234+ while (lineStart <= result.size ()) {
2235+ size_t lineEnd = result.find (' \n ' , lineStart);
2236+ bool hasNewline = (lineEnd != std::string::npos);
2237+ if (!hasNewline)
2238+ lineEnd = result.size ();
2239+ std::string line = result.substr (lineStart, lineEnd - lineStart);
2240+ // Only process lines where 'bfloat' appears as a type (i.e., the word
2241+ // 'bfloat' is in the instruction line, not as part of an identifier).
2242+ // Simple heuristic: look for " bfloat " or " bfloat," or "= bfloat ".
2243+ bool hasBfloatType = line.find (" bfloat " ) != std::string::npos ||
2244+ line.find (" bfloat," ) != std::string::npos ||
2245+ line.find (" = bfloat\n " ) != std::string::npos;
2246+ if (hasBfloatType) {
2247+ // Scan for bare decimal float literals: must be preceded by ", " (or
2248+ // "( ") and start with an optional '-' then a digit.
2249+ std::string newLine;
2250+ newLine.reserve (line.size ());
2251+ size_t lp = 0 ;
2252+ while (lp < line.size ()) {
2253+ // Look for ", " or "( " before a potential decimal.
2254+ size_t sep = line.find (" , " , lp);
2255+ size_t paren = line.find (" ( " , lp);
2256+ size_t next =
2257+ (sep < paren ? sep : paren); // take whichever comes first
2258+ if (next == std::string::npos) {
2259+ newLine += line.substr (lp);
2260+ break ;
2261+ }
2262+ size_t afterSep = next + 2 ; // skip ", " or "( "
2263+ newLine += line.substr (lp, afterSep - lp);
2264+ lp = afterSep;
2265+ // Try to parse a decimal float starting here.
2266+ size_t numStart = lp;
2267+ size_t numEnd = numStart;
2268+ if (numEnd < line.size () && line[numEnd] == ' -' )
2269+ ++numEnd;
2270+ if (numEnd >= line.size () ||
2271+ !std::isdigit (static_cast <unsigned char >(line[numEnd]))) {
2272+ continue ; // not a decimal, keep scanning
2273+ }
2274+ while (numEnd < line.size () &&
2275+ (std::isdigit (static_cast <unsigned char >(line[numEnd])) ||
2276+ line[numEnd] == ' .' || line[numEnd] == ' e' ||
2277+ line[numEnd] == ' E' || line[numEnd] == ' +' ||
2278+ line[numEnd] == ' -' ))
2279+ ++numEnd;
2280+ std::string numStr = line.substr (numStart, numEnd - numStart);
2281+ // Skip if it already looks like an integer (no '.', 'e', or 'E').
2282+ bool isFloat = numStr.find (' .' ) != std::string::npos ||
2283+ numStr.find (' e' ) != std::string::npos ||
2284+ numStr.find (' E' ) != std::string::npos;
2285+ if (!isFloat) {
2286+ newLine += numStr;
2287+ lp = numEnd;
2288+ continue ;
2289+ }
2290+ char *ep = nullptr ;
2291+ float fv = std::strtof (numStr.c_str (), &ep);
2292+ if (!ep || ep != numStr.c_str () + numStr.size ()) {
2293+ newLine += numStr;
2294+ lp = numEnd;
2295+ continue ;
2296+ }
2297+ uint32_t f32bits;
2298+ std::memcpy (&f32bits, &fv, sizeof (f32bits));
2299+ newLine += convertDecimalBf (f32bits);
2300+ lp = numEnd;
2301+ }
2302+ line = std::move (newLine);
2303+ }
2304+ out += line;
2305+ if (hasNewline)
2306+ out += ' \n ' ;
2307+ lineStart = lineEnd + (hasNewline ? 1 : result.size () + 1 );
2308+ }
2309+ result = std::move (out);
2310+ }
21472311 return result;
21482312}
21492313
0 commit comments