diff --git a/IRBindings.cpp b/IRBindings.cpp index 4788c92..86e64ed 100644 --- a/IRBindings.cpp +++ b/IRBindings.cpp @@ -12,6 +12,7 @@ #include "IRBindings.h" #include "llvm/ADT/SmallVector.h" +#include "llvm/Config/llvm-config.h" #include "llvm/IR/Attributes.h" #include "llvm/IR/DebugLoc.h" #include "llvm/IR/DebugInfoMetadata.h" @@ -23,6 +24,17 @@ using namespace llvm; +LLVMAttributeRef LLVMGoCreateConstantRangeAttribute( + LLVMContextRef C, unsigned KindID, unsigned NumBits, + const uint64_t *LowerWords, const uint64_t *UpperWords) { +#if LLVM_VERSION_MAJOR >= 19 + return LLVMCreateConstantRangeAttribute(C, KindID, NumBits, LowerWords, + UpperWords); +#else + return nullptr; +#endif +} + LLVMMetadataRef LLVMConstantAsMetadata(LLVMValueRef C) { return wrap(ConstantAsMetadata::get(unwrap(C))); } diff --git a/IRBindings.h b/IRBindings.h index 5428be7..f0145b2 100644 --- a/IRBindings.h +++ b/IRBindings.h @@ -35,6 +35,10 @@ struct LLVMDebugLocMetadata{ LLVMMetadataRef LLVMConstantAsMetadata(LLVMValueRef Val); +LLVMAttributeRef LLVMGoCreateConstantRangeAttribute( + LLVMContextRef C, unsigned KindID, unsigned NumBits, + const uint64_t *LowerWords, const uint64_t *UpperWords); + LLVMMetadataRef LLVMMDString2(LLVMContextRef C, const char *Str, unsigned SLen); LLVMMetadataRef LLVMMDNode2(LLVMContextRef C, LLVMMetadataRef *MDs, unsigned Count); diff --git a/constant_range_test.go b/constant_range_test.go new file mode 100644 index 0000000..bdbef4d --- /dev/null +++ b/constant_range_test.go @@ -0,0 +1,83 @@ +package llvm + +import ( + "fmt" + "strconv" + "strings" + "testing" +) + +func TestConstantRangeAttribute(t *testing.T) { + ctx := NewContext() + defer ctx.Dispose() + kind := AttributeKindID("range") + major, _ := strconv.Atoi(strings.SplitN(Version, ".", 2)[0]) + if major < 19 { + if !ctx.CreateConstantRangeAttribute(kind, 32, []uint64{0}, []uint64{1}).IsNil() { + t.Fatal("constant-range attributes must be unavailable before LLVM 19") + } + return + } + if kind == 0 { + t.Fatal("range attribute kind not found") + } + for _, test := range []struct { + bits int + lower, upper []uint64 + want string + }{ + {1, []uint64{0}, []uint64{1}, "range(i1 0, -1)"}, + {32, []uint64{0}, []uint64{1 << 31}, "range(i32 0, -2147483648)"}, + {64, []uint64{0}, []uint64{1 << 63}, "range(i64 0, -9223372036854775808)"}, + {65, []uint64{3, 0}, []uint64{9, 1}, "range(i65 3, -18446744073709551607)"}, + {128, []uint64{3, 2}, []uint64{9, 4}, "range(i128 36893488147419103235, 73786976294838206473)"}, + } { + t.Run(fmt.Sprint(test.bits), func(t *testing.T) { + mod := ctx.NewModule("range") + defer mod.Dispose() + fn := AddFunction(mod, "length", FunctionType(ctx.IntType(test.bits), nil, false)) + attr := ctx.CreateConstantRangeAttribute(kind, test.bits, test.lower, test.upper) + if attr.IsNil() { + t.Fatal("constant-range attribute is nil") + } + fn.AddAttributeAtIndex(0, attr) + if attrs := fn.GetAttributesAtIndex(0); len(attrs) != 1 || attrs[0] != attr { + t.Fatal("return attribute did not round-trip") + } + // The context owns the APInts; it must not retain Go slice storage. + for i := range test.lower { + test.lower[i] = 0 + test.upper[i] = 0 + } + if ir := mod.String(); !strings.Contains(ir, test.want) { + t.Fatalf("missing %q:\n%s", test.want, ir) + } + if err := VerifyModule(mod, ReturnStatusAction); err != nil { + t.Fatal(err) + } + }) + } +} + +func TestConstantRangeAttributeInvalidBounds(t *testing.T) { + ctx := NewContext() + defer ctx.Dispose() + for _, test := range []struct { + bits int + lower, upper []uint64 + }{ + {0, nil, nil}, {-1, nil, nil}, + {32, nil, []uint64{1}}, {32, []uint64{0}, nil}, + {65, []uint64{0}, []uint64{1}}, + {64, []uint64{0, 0}, []uint64{1}}, + } { + t.Run(fmt.Sprint(test.bits, "/", len(test.lower), "/", len(test.upper)), func(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("invalid bounds did not panic") + } + }() + ctx.CreateConstantRangeAttribute(AttributeKindID("range"), test.bits, test.lower, test.upper) + }) + } +} diff --git a/ir.go b/ir.go index 657d6da..9cf9449 100644 --- a/ir.go +++ b/ir.go @@ -411,6 +411,27 @@ func (c Context) CreateTypeAttribute(kind uint, t Type) (a Attribute) { return } +// CreateConstantRangeAttribute creates a constant-range attribute such as +// "range". Bounds are unsigned words in least-significant-word-first order; +// each slice must contain exactly ceil(numBits/64) words and numBits must be +// positive. Invalid widths or word counts panic. On LLVM before 19, which does +// not support constant-range attributes, it returns a nil Attribute. +func (c Context) CreateConstantRangeAttribute(kind uint, numBits int, lowerWords, upperWords []uint64) (a Attribute) { + if numBits <= 0 || uint64(numBits) > uint64(^uint32(0)) { + panic("llvm: invalid constant range bit width") + } + nwords := numBits / 64 + if numBits%64 != 0 { + nwords++ + } + if len(lowerWords) != nwords || len(upperWords) != nwords { + panic("llvm: constant range bounds have incorrect word counts") + } + a.C = C.LLVMGoCreateConstantRangeAttribute(c.C, C.unsigned(kind), C.unsigned(numBits), + (*C.uint64_t)(unsafe.Pointer(&lowerWords[0])), (*C.uint64_t)(unsafe.Pointer(&upperWords[0]))) + return +} + func (a Attribute) GetTypeValue() (t Type) { t.C = C.LLVMGetTypeAttributeValue(a.C) return