-
Notifications
You must be signed in to change notification settings - Fork 7
ir: bind constant-range attribute creation #52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) | ||
| }) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P3] Note the nwords>=1 invariant that keeps &lowerWords[0] safe The |
||
| (*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 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P3] Doc says pre-19 returns nil, but validation runs on all versions first
The comment states "On LLVM before 19 ... it returns a nil Attribute," but the width/word-count validation (which can panic) runs unconditionally before the version-gated C call. So on a pre-19 build an invalid-argument call panics rather than returning nil. The behavior is arguably better, but the doc reads as a pure "always nil" path. Consider clarifying, e.g. "a nil Attribute is returned for otherwise-valid arguments." Non-blocking.