--- /dev/null
+# This reproduces a bug where profile collected from perf without LBRs and
+# converted into fdata-no-lbr format is reported to not contain profile for any
+# functions.
+
+# REQUIRES: system-linux
+
+# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-unknown \
+# RUN: %s -o %t.o
+# RUN: link_fdata --no-lbr %s %t.o %t.fdata
+# RUN: FileCheck %s --input-file %t.fdata --check-prefix=CHECK-FDATA
+# RUN: llvm-strip --strip-unneeded %t.o
+# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q -nostdlib
+# RUN: llvm-bolt %t.exe -o %t.out --data %t.fdata --dyno-stats -nl \
+# RUN: --print-only=_start 2>&1 | FileCheck %s --check-prefix=CHECK-BOLT
+
+# CHECK-FDATA: no_lbr
+# CHECK-FDATA-NEXT: 1 _start [[#]] 1
+
+# CHECK-BOLT: BOLT-INFO: pre-processing profile using branch profile reader
+# CHECK-BOLT: BOLT-INFO: operating with basic samples profiling data (no LBR).
+# CHECK-BOLT: BOLT-INFO: 1 out of 1 functions in the binary (100.0%) have non-empty execution profile
+
+ .globl _start
+ .type _start, %function
+_start:
+ pushq %rbp
+ movq %rsp, %rbp
+ cmpl $0x0, %eax
+a:
+# FDATA: 1 _start #a# 1
+ je b
+ movl $0x0, %eax
+ jmp c
+b:
+ movl $0x1, %eax
+c:
+ popq %rbp
+ retq
+.size _start, .-_start
parser.add_argument("output")
parser.add_argument("prefix", nargs="?", default="FDATA", help="Custom FDATA prefix")
parser.add_argument("--nmtool", default="nm", help="Path to nm tool")
+parser.add_argument("--no-lbr", action='store_true')
args = parser.parse_args()
# [<mispred_count>]
preagg_pat = re.compile(r"(?P<type>[BFf]) (?P<offsets_count>.*)")
+# No-LBR profile:
+# <is symbol?> <closest elf symbol or DSO name> <relative address> <count>
+nolbr_pat = re.compile(r"([01].*) (?P<count>\d+)")
+
# Replacement symbol: #symname#
replace_pat = re.compile(r"#(?P<symname>[^#]+)#")
profile_line = prefix_match.group(1)
fdata_match = fdata_pat.match(profile_line)
preagg_match = preagg_pat.match(profile_line)
+ nolbr_match = nolbr_pat.match(profile_line)
if fdata_match:
src_dst, execnt, mispred = fdata_match.groups()
# Split by whitespaces not preceded by a backslash (negative lookbehind)
# exactly matches the format.
assert len(chunks) == 6, f"ERROR: wrong format/whitespaces must be escaped:\n{line}"
exprs.append(('FDATA', (*chunks, execnt, mispred)))
+ elif nolbr_match:
+ loc, count = nolbr_match.groups()
+ # Split by whitespaces not preceded by a backslash (negative lookbehind)
+ chunks = re.split(r'(?<!\\) +', loc)
+ # Check if the number of records separated by non-escaped whitespace
+ # exactly matches the format.
+ assert len(chunks) == 3, f"ERROR: wrong format/whitespaces must be escaped:\n{line}"
+ exprs.append(('NOLBR', (*chunks, count)))
elif preagg_match:
exprs.append(('PREAGG', preagg_match.groups()))
else:
return symbols[symname]
with open(args.output, 'w', newline='\n') as f:
+ if args.no_lbr:
+ print('no_lbr', file = f)
for etype, expr in exprs:
if etype == 'FDATA':
issym1, anchor1, offsym1, issym2, anchor2, offsym2, execnt, mispred = expr
print(evaluate_symbol(issym1, anchor1, offsym1),
evaluate_symbol(issym2, anchor2, offsym2),
execnt, mispred, file = f)
+ elif etype == 'NOLBR':
+ issym, anchor, offsym, count = expr
+ print(evaluate_symbol(issym, anchor, offsym), count, file = f)
elif etype == 'PREAGG':
# Replace all symbols enclosed in ##
print(expr[0], re.sub(replace_pat, replace_symbol, expr[1]),