Skip to content

Commit afc887f

Browse files
erwei-xilinxclaude
andauthored
[aiecc] Downgrade LLVM 23 decimal bfloat16 literals for Peano compatibility (#3241)
Co-authored-by: Claude Opus 4 (1M context) <noreply@anthropic.com>
1 parent 62f0833 commit afc887f

2 files changed

Lines changed: 227 additions & 6 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//===- peano_compat_bfloat_decimal.mlir - downgradeIRForPeano bfloat -----===//
2+
//
3+
// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
// Copyright (C) 2026 Advanced Micro Devices, Inc.
8+
//
9+
//===----------------------------------------------------------------------===//
10+
11+
// Regression test: downgradeIRForPeano must rewrite decimal bfloat16 literals
12+
// (introduced in llvm/llvm-project@41c214f0b115, 2026-05-07) to the
13+
// 0xR-prefixed bit-exact hex form that Peano's LLVM 21 opt can parse.
14+
//
15+
// Without the fix, aiecc fails with:
16+
// opt: ...peano-compat.ll: error: floating point constant invalid for type
17+
// %r = fmul bfloat %v, 1.445310e+00
18+
//
19+
// The constant 1.445310e+00 is the bfloat16 approximation of log2(e), with
20+
// bit pattern 0xR3FB9. LLVM 23 emits it as a decimal string; Peano's LLVM 21
21+
// opt requires the hex form. After downgrade the peano-compat.ll must use
22+
// '0xR3FB9' and contain no bare decimal bfloat literals.
23+
24+
// REQUIRES: peano
25+
26+
// RUN: aiecc --no-xchesscc --no-xbridge --tmpdir %t %s
27+
// RUN: FileCheck %s --input-file %t/main_core_0_2.peano-compat.ll \
28+
// RUN: --implicit-check-not="bfloat {{[0-9][0-9]*\.[0-9]}}"
29+
// RUN: FileCheck %s --input-file %t/main_core_0_2.peano-compat.ll \
30+
// RUN: --check-prefix=CHECK-HEX
31+
32+
// CHECK: define void @core_0_2()
33+
// CHECK-HEX: 0xR3FB9
34+
35+
module {
36+
aie.device(npu2) {
37+
%tile = aie.tile(0, 2)
38+
%buf_in = aie.buffer(%tile) {sym_name = "in"} : memref<32xbf16>
39+
%buf_out = aie.buffer(%tile) {sym_name = "out"} : memref<32xbf16>
40+
41+
%core = aie.core(%tile) {
42+
%c0 = arith.constant 0 : index
43+
%c32 = arith.constant 32 : index
44+
%c1 = arith.constant 1 : index
45+
// bfloat16 approximation of log2(e) (bits 0x3FB9, decimal 1.445310e+00).
46+
// LLVM 23 emits this as 'bfloat 1.445310e+00'; the downgrade must
47+
// rewrite it to 'bfloat 0xR3FB9' for Peano's LLVM 21 to parse.
48+
%log2e = arith.constant 1.445310e+00 : bf16
49+
scf.for %i = %c0 to %c32 step %c1 {
50+
%v = memref.load %buf_in[%i] : memref<32xbf16>
51+
%r = arith.mulf %v, %log2e : bf16
52+
memref.store %r, %buf_out[%i] : memref<32xbf16>
53+
}
54+
aie.end
55+
}
56+
}
57+
}

tools/aiecc/aiecc.cpp

Lines changed: 170 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)