Fix more LLVM-C test bugs

This commit is contained in:
Duncan Ogilvie
2026-02-19 23:30:30 +01:00
parent 31235208a3
commit 11a0daaef6
9 changed files with 115 additions and 54 deletions
+17 -14
View File
@@ -4,14 +4,14 @@ Last updated: February 19, 2026
This document tracks issues relevant to llvm-c-test parity work.
## 1. Poison Constant ValueKind Mismatch
## 1. Poison/Undef Predicate Ordering in Echo (Historical)
- Status: Reproduced in both C and Python implementations.
- Status: Fixed in both vendored C llvm-c-test and Python implementation.
- Scope: `--echo`
- Symptom: `LLVMGetValueKind` reports poison as the wrong kind during cloning.
- Error text: `LLVMGetValueKind returned incorrect type`
- Lit coverage: `llvm-c/llvm-c-test/inputs/echo_poison_known_bug.ll`
- Notes: This currently appears to be an upstream LLVM-C API behavior issue.
- Root cause: `LLVMIsUndef(poison)` is true, so checking `undef` before
`poison` misclassifies poison constants.
- Fix summary: check poison before undef in constant cloning.
- Lit coverage: `llvm-c/llvm-c-test/inputs/poison.ll`
## 2. BitCast Echo Crash (Historical)
@@ -35,14 +35,17 @@ This document tracks issues relevant to llvm-c-test parity work.
- Scope: `--echo`
- Lit coverage: `llvm-c/llvm-c-test/inputs/types_extended.ll`
## 5. Object Symbol Section Name Crash in C llvm-c-test
## 5. Object Symbol Size Crash in C llvm-c-test
- Status: Fixed in vendored `llvm-c-test` code.
- Scope: `--object-list-symbols`
- Root cause: C test code called `LLVMGetSectionName` after
`LLVMMoveToContainingSection` without checking section-iterator end.
- Fix: Guard with `LLVMObjectFileIsSectionIteratorAtEnd` and print `"(null)"`
when no containing section exists.
- Root cause: C test code called `LLVMGetSymbolSize` for symbols without a
containing section (for example file symbols), which can assert in debug
builds.
- Fix:
- Guard section iterator end after `LLVMMoveToContainingSection`.
- For symbols with no containing section, print section as `"(null)"` and
size as `0` without calling `LLVMGetSymbolSize`.
- Files:
- Fix: `llvm-c/llvm-c-test/object.c`
- Coverage: `llvm-c/llvm-c-test/inputs/object_symbols.test`
@@ -50,8 +53,8 @@ This document tracks issues relevant to llvm-c-test parity work.
## Upstreaming Plan
1. Upstream llvm-c-test fixes that are API-call correctness issues
(`object.c` section-iterator end guard).
(`object.c` section-iterator and symbol-size guard).
2. Keep regression lit tests for previously failing behaviors
(`echo_constant_fp.ll`, `echo_bitcast.ll`, `object_symbols.test`).
3. For unresolved upstream API behavior (`echo_poison_known_bug.ll`), keep a
negative test and reference it in upstream discussion.
3. Keep regression lit tests for historical failures and ensure they pass on
both C and Python implementations (`poison.ll`).
+29 -12
View File
@@ -359,18 +359,18 @@ static LLVMValueRef clone_constant_impl(LLVMValueRef Cst, LLVMModuleRef M) {
return LLVMConstNull(Ty);
}
// Try undef
if (LLVMIsUndef(Cst)) {
check_value_kind(Cst, LLVMUndefValueValueKind);
return LLVMGetUndef(TypeCloner(M).Clone(Cst));
}
// Try poison
if (LLVMIsPoison(Cst)) {
check_value_kind(Cst, LLVMPoisonValueValueKind);
return LLVMGetPoison(TypeCloner(M).Clone(Cst));
}
// Try undef
if (LLVMIsUndef(Cst)) {
check_value_kind(Cst, LLVMUndefValueValueKind);
return LLVMGetUndef(TypeCloner(M).Clone(Cst));
}
// Try null
if (LLVMIsNull(Cst)) {
check_value_kind(Cst, LLVMConstantTokenNoneValueKind);
@@ -566,15 +566,29 @@ struct FunCloner {
if (i != VMap.end())
return i->second;
if (LLVMGetNumOperands(Src) != 1)
report_fatal_error("BlockAddress should have exactly one operand");
unsigned NumOps = LLVMGetNumOperands(Src);
if (NumOps != 1 && NumOps != 2)
report_fatal_error("BlockAddress should have one or two operands");
LLVMValueRef BBVal = LLVMGetOperand(Src, 0);
unsigned BBOpIndex = NumOps - 1;
LLVMValueRef BBVal = LLVMGetOperand(Src, BBOpIndex);
if (!LLVMValueIsBasicBlock(BBVal))
report_fatal_error("BlockAddress operand is not a basic block");
LLVMBasicBlockRef SrcBB = LLVMValueAsBasicBlock(BBVal);
LLVMValueRef SrcFn = LLVMGetBasicBlockParent(SrcBB);
LLVMValueRef SrcFn = nullptr;
if (NumOps == 2) {
LLVMValueRef FnOp = LLVMGetOperand(Src, 0);
if (!LLVMIsAFunction(FnOp))
report_fatal_error("BlockAddress function operand is not a function");
SrcFn = FnOp;
} else {
SrcFn = LLVMGetBasicBlockParent(SrcBB);
}
if (LLVMGetBasicBlockParent(SrcBB) != SrcFn)
report_fatal_error("BlockAddress function/block mismatch");
size_t FnNameLen;
const char *FnName = LLVMGetValueName2(SrcFn, &FnNameLen);
LLVMValueRef DstFn = LLVMGetNamedFunction(M, FnName);
@@ -592,10 +606,13 @@ struct FunCloner {
// Try to clone everything in the llvm::Value hierarchy.
LLVMValueRef CloneValue(LLVMValueRef Src) {
// Handle BlockAddress by value kind first. Some LLVM revisions may not
// classify BlockAddress as LLVMIsAConstant().
if (LLVMGetValueKind(Src) == LLVMBlockAddressValueKind)
return CloneBlockAddressConstant(Src);
// First, the value may be constant.
if (LLVMIsAConstant(Src)) {
if (LLVMGetValueKind(Src) == LLVMBlockAddressValueKind)
return CloneBlockAddressConstant(Src);
return clone_constant(Src, M);
}
@@ -1,12 +0,0 @@
; RUN: set +o pipefail; llvm-as < %s | llvm-c-test --echo 2>&1 | FileCheck %s
;
; Known upstream LLVM-C limitation:
; LLVMGetValueKind reports poison constants as UndefValue, causing echo to fail
; when it validates ValueKind before cloning.
define i32 @test_poison() {
entry:
ret i32 poison
}
; CHECK: LLVMGetValueKind returned incorrect type
+11
View File
@@ -0,0 +1,11 @@
; RUN: llvm-as < %s | llvm-dis > %t.orig
; RUN: llvm-as < %s | llvm-c-test --echo > %t.echo
; RUN: diff -w %t.orig %t.echo
;
; Regression: poison constants are also reported by LLVMIsUndef(), so echo must
; check poison before undef when cloning constants.
define i32 @test_poison() {
entry:
ret i32 poison
}
+1 -1
View File
@@ -23,7 +23,7 @@ define %NamedStruct @test_named_struct(%NamedStruct %s) {
%1 = extractvalue %NamedStruct %s, 0
%2 = extractvalue %NamedStruct %s, 1
%3 = extractvalue %NamedStruct %s, 2
%4 = insertvalue %NamedStruct undef, i32 %1, 0
%4 = insertvalue %NamedStruct %s, i32 %1, 0
%5 = insertvalue %NamedStruct %4, i64 %2, 1
%6 = insertvalue %NamedStruct %5, ptr %3, 2
ret %NamedStruct %6
+5 -2
View File
@@ -79,12 +79,15 @@ int llvm_object_list_symbols(void) {
sym = LLVMObjectFileCopySymbolIterator(O);
while (sect && sym && !LLVMObjectFileIsSymbolIteratorAtEnd(O, sym)) {
const char *SectionName = NULL;
uint64_t SymbolSize = 0;
LLVMMoveToContainingSection(sect, sym);
if (!LLVMObjectFileIsSectionIteratorAtEnd(O, sect))
if (!LLVMObjectFileIsSectionIteratorAtEnd(O, sect)) {
SectionName = LLVMGetSectionName(sect);
SymbolSize = LLVMGetSymbolSize(sym);
}
printf("%s @0x%08" PRIx64 " +%" PRIu64 " (%s)\n", LLVMGetSymbolName(sym),
LLVMGetSymbolAddress(sym), LLVMGetSymbolSize(sym),
LLVMGetSymbolAddress(sym), SymbolSize,
SectionName ? SectionName : "(null)");
LLVMMoveToNextSymbol(sym);
+29 -11
View File
@@ -212,16 +212,16 @@ def clone_constant_impl(cst: llvm.Value, m: llvm.Module) -> llvm.Value:
ty = TypeCloner(m).clone(cst)
return ty.null()
# Try undef
if cst.is_undef:
check_value_kind(cst, llvm.ValueKind.UndefValue)
return TypeCloner(m).clone(cst).undef()
# Try poison
if cst.is_poison:
check_value_kind(cst, llvm.ValueKind.PoisonValue)
return TypeCloner(m).clone(cst).poison()
# Try undef
if cst.is_undef:
check_value_kind(cst, llvm.ValueKind.UndefValue)
return TypeCloner(m).clone(cst).undef()
# Try null
if cst.is_null:
check_value_kind(cst, llvm.ValueKind.ConstantTokenNone)
@@ -447,19 +447,34 @@ class FunCloner:
if src in self.vmap:
return self.vmap[src]
if src.num_operands != 1:
raise RuntimeError("BlockAddress should have exactly one operand")
num_ops = src.num_operands
if num_ops not in (1, 2):
raise RuntimeError("BlockAddress should have one or two operands")
bb_val = src.get_operand(0)
bb_op_index = num_ops - 1
bb_val = src.get_operand(bb_op_index)
if not bb_val.value_is_basic_block:
raise RuntimeError("BlockAddress operand is not a basic block")
src_bb = bb_val.value_as_basic_block()
src_fn = src_bb.function
if num_ops == 2:
fn_op = src.get_operand(0)
if not fn_op.is_function:
raise RuntimeError("BlockAddress function operand is not a function")
src_fn = fn_op
else:
src_fn = src_bb.function
if src_bb.function != src_fn:
raise RuntimeError("BlockAddress function/block mismatch")
dst_fn = self.module.get_function(src_fn.name)
if not dst_fn:
raise RuntimeError("Could not find function for block address")
if dst_fn != self.fun:
raise RuntimeError("Cross-function blockaddress is not supported")
dst_bb = self.declare_bb(src_bb)
dst = dst_fn.block_address(dst_bb)
self.vmap[src] = dst
@@ -467,10 +482,13 @@ class FunCloner:
def clone_value(self, src: llvm.Value) -> llvm.Value:
"""Clone a value, handling constants, params, and instructions."""
# Handle BlockAddress by value kind first. Some LLVM revisions may not
# classify BlockAddress as a constant in the IsA helpers.
if src.value_kind == llvm.ValueKind.BlockAddress:
return self.clone_block_address_constant(src)
# First, the value may be constant
if src.is_constant:
if src.value_kind == llvm.ValueKind.BlockAddress:
return self.clone_block_address_constant(src)
return clone_constant(src, self.module)
# Function argument should always be in the map
+8 -2
View File
@@ -63,9 +63,15 @@ def object_list_symbols() -> int:
sym_name = sym.name
sym_address = sym.address
sym_size = sym.size
# Some symbols have no containing section; match C tool output.
sect_name = "(null)" if sect.is_at_end() else sect.name
if sect.is_at_end():
sect_name = "(null)"
# Avoid LLVMGetSymbolSize on symbols without a containing
# section (e.g. file symbols), which can assert in debug builds.
sym_size = 0
else:
sect_name = sect.name
sym_size = sym.size
print(f"{sym_name} @0x{sym_address:08x} +{sym_size} ({sect_name})")
+15
View File
@@ -8105,6 +8105,21 @@ struct LLVMSymbolIteratorWrapper : NoMoveCopy {
uint64_t get_size() const {
check_valid();
check_not_at_end("SymbolIterator.size");
// Some symbol kinds (e.g. file symbols) have no containing section.
// LLVMGetSymbolSize can assert on those in debug builds.
LLVMSectionIteratorRef sect = LLVMObjectFileCopySectionIterator(m_binary_ref);
if (!sect)
return 0;
LLVMMoveToContainingSection(sect, m_ref);
bool HasContainingSection =
!LLVMObjectFileIsSectionIteratorAtEnd(m_binary_ref, sect);
LLVMDisposeSectionIterator(sect);
if (!HasContainingSection)
return 0;
return LLVMGetSymbolSize(m_ref);
}