diff --git a/.github/workflows/coroutine.yml b/.github/workflows/coroutine.yml index 5193f80..97b443d 100644 --- a/.github/workflows/coroutine.yml +++ b/.github/workflows/coroutine.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - llvm: [19, 21, 22] + llvm: [19, 20, 21, 22] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 8df976c..66f886c 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -20,20 +20,15 @@ jobs: strategy: matrix: llvm: [14, 15, 16, 17, 18, 19, 20, 21, 22] - go-version: [1.18.x, 1.21.x, 1.22.x] steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: - go-version: ${{ matrix.go-version }} - #- name: Update Homebrew - # if: matrix.llvm == 17 # needed as long as LLVM 17 is still fresh - # run: brew update + go-version: '1.22' # Optional step when a LLVM version is very new. - name: Update Homebrew - if: matrix.llvm == 22 run: brew update - name: Install LLVM run: HOMEBREW_NO_AUTO_UPDATE=1 brew install llvm@${{ matrix.llvm }} @@ -69,3 +64,26 @@ jobs: if: matrix.llvm == 19 run: go test -v + test-linux-fedora: + # Fedora uses different paths than other systems, so testing it separately. + runs-on: ubuntu-24.04 + strategy: + matrix: + llvm: [19, 20, 21] + container: fedora:43 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install dependencies (default LLVM) + if: matrix.llvm == 21 + run: dnf install --assumeyes g++ golang llvm-devel + - name: Install dependencies (older LLVM) + if: matrix.llvm != 21 + run: dnf install --assumeyes g++ golang llvm${{ matrix.llvm }}-devel + - name: Test LLVM ${{ matrix.llvm }} + run: + go test -v -tags=llvm${{ matrix.llvm }} + - name: Test default LLVM + if: matrix.llvm == 19 + run: + go test -v diff --git a/captures_test.go b/captures_test.go new file mode 100644 index 0000000..5021b9a --- /dev/null +++ b/captures_test.go @@ -0,0 +1,48 @@ +package llvm + +import ( + "strconv" + "strings" + "testing" +) + +// TestCapturesAttribute checks that the 'captures' parameter attribute +// (which replaced the boolean 'nocapture' enum attribute starting with +// LLVM 21) round-trips through the generic enum-attribute API, and that a +// value of 0 corresponds to CaptureInfo::none(), i.e. captures(none). +func TestCapturesAttribute(t *testing.T) { + majorVersion, _ := strconv.Atoi(strings.SplitN(Version, ".", 2)[0]) + if majorVersion < 21 { + t.Skip("not llvm 21") + } + + ctx := NewContext() + defer ctx.Dispose() + mod := ctx.NewModule("") + defer mod.Dispose() + + ptrType := PointerType(ctx.Int8Type(), 0) + ftyp := FunctionType(ctx.VoidType(), []Type{ptrType}, false) + fn := AddFunction(mod, "foo", ftyp) + + kind := AttributeKindID("captures") + if kind == 0 { + t.Fatal("captures kind id not found") + } + + attr := ctx.CreateEnumAttribute(kind, 0) + fn.AddAttributeAtIndex(1, attr) + + got := fn.GetEnumAttributeAtIndex(1, kind) + if got.IsNil() { + t.Fatal("expected captures attribute on param 1, got nil") + } + if val := got.GetEnumValue(); val != 0 { + t.Errorf("expected captures value 0 (none), got %d", val) + } + + text := mod.String() + if !strings.Contains(text, "captures(none)") { + t.Errorf("expected 'captures(none)' in output, got:\n%s", text) + } +} diff --git a/ir.go b/ir.go index 573c531..99a0921 100644 --- a/ir.go +++ b/ir.go @@ -828,6 +828,18 @@ func (v Value) Operand(i int) (rv Value) { rv.C = C.LLVMGetOperand(v.C, C.unsi func (v Value) SetOperand(i int, op Value) { C.LLVMSetOperand(v.C, C.unsigned(i), op.C) } func (v Value) OperandsCount() int { return int(C.LLVMGetNumOperands(v.C)) } +// Operations on terminator instructions (br, switch, etc). Unlike operands, +// the number and meaning of successors has been stable across LLVM versions, +// making these a safe, version-independent way to enumerate the destination +// blocks of a switch instruction: successor 0 is the default destination, +// and successors 1..N-1 correspond to case 0..N-2 (see GetSwitchCaseValue for +// the matching case value). +func (v Value) SuccessorsCount() int { return int(C.LLVMGetNumSuccessors(v.C)) } +func (v Value) Successor(i int) (bb BasicBlock) { + bb.C = C.LLVMGetSuccessor(v.C, C.unsigned(i)) + return +} + // Operations on constants of any type // ConstTokenNone returns the empty token constant owned by c. func (c Context) ConstTokenNone() (v Value) { diff --git a/llvm_config_llvm19.go b/llvm_config_llvm19.go index 71cd53c..fea3f0b 100644 --- a/llvm_config_llvm19.go +++ b/llvm_config_llvm19.go @@ -1,4 +1,5 @@ //go:build !byollvm && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm20 && !llvm21 && !llvm22 +// +build !byollvm,!llvm14,!llvm15,!llvm16,!llvm17,!llvm18,!llvm20,!llvm21,!llvm22 package llvm @@ -11,9 +12,9 @@ package llvm // #cgo freebsd CPPFLAGS: -I/usr/local/llvm19/include -I/usr/local/llvm19/include/llvm-c -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS // #cgo freebsd CXXFLAGS: -std=c++17 // #cgo freebsd LDFLAGS: -L/usr/local/llvm19/lib -lLLVM -// #cgo linux CPPFLAGS: -I/usr/include/llvm-19 -I/usr/include/llvm-c-19 -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS +// #cgo linux CPPFLAGS: -I/usr/include/llvm-19 -I/usr/include/llvm-c-19 -I/usr/lib64/llvm19/include -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS // #cgo linux CXXFLAGS: -std=c++17 -// #cgo linux LDFLAGS: -L/usr/lib/llvm-19/lib -lLLVM-19 +// #cgo linux LDFLAGS: -L/usr/lib/llvm-19/lib -L/usr/lib64/llvm19/lib -lLLVM-19 import "C" type run_build_sh int diff --git a/llvm_config_llvm20.go b/llvm_config_llvm20.go index 090bdd6..d67281a 100644 --- a/llvm_config_llvm20.go +++ b/llvm_config_llvm20.go @@ -1,4 +1,5 @@ //go:build !byollvm && llvm20 +// +build !byollvm,llvm20 package llvm @@ -11,9 +12,9 @@ package llvm // #cgo freebsd CPPFLAGS: -I/usr/local/llvm20/include -I/usr/local/llvm20/include/llvm-c -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS // #cgo freebsd CXXFLAGS: -std=c++17 // #cgo freebsd LDFLAGS: -L/usr/local/llvm20/lib -lLLVM -// #cgo linux CPPFLAGS: -I/usr/include/llvm-20 -I/usr/include/llvm-c-20 -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS +// #cgo linux CPPFLAGS: -I/usr/include/llvm-20 -I/usr/include/llvm-c-20 -I/usr/lib64/llvm20/include -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS // #cgo linux CXXFLAGS: -std=c++17 -// #cgo linux LDFLAGS: -L/usr/lib/llvm-20/lib -lLLVM-20 +// #cgo linux LDFLAGS: -L/usr/lib/llvm-20/lib -L/usr/lib64/llvm20/lib64 -lLLVM-20 import "C" type run_build_sh int diff --git a/switch_llvm22.go b/switch_llvm22.go new file mode 100644 index 0000000..0c70b08 --- /dev/null +++ b/switch_llvm22.go @@ -0,0 +1,21 @@ +//go:build llvm22 + +package llvm + +/* +#include "llvm-c/Core.h" +*/ +import "C" + +// GetSwitchCaseValue obtains the case value for a successor of a switch +// instruction. i corresponds to the successor index; the first successor (0) +// is the default destination, so i must be greater than zero. +// +// LLVM 22 stopped exposing switch case values as regular instruction +// operands (only the condition and destination-block operands remain), so +// this is implemented via the new LLVMGetSwitchCaseValue C API added in the +// same release. +func (v Value) GetSwitchCaseValue(i int) (rv Value) { + rv.C = C.LLVMGetSwitchCaseValue(v.C, C.unsigned(i)) + return +} diff --git a/switch_pre22.go b/switch_pre22.go new file mode 100644 index 0000000..cfa381c --- /dev/null +++ b/switch_pre22.go @@ -0,0 +1,15 @@ +//go:build !llvm22 + +package llvm + +// GetSwitchCaseValue obtains the case value for a successor of a switch +// instruction. i corresponds to the successor index; the first successor (0) +// is the default destination, so i must be greater than zero. +// +// Before LLVM 22, switch case values were stored as regular instruction +// operands (alternating with their destination blocks, after the leading +// condition/default-destination pair), so this is implemented via Operand +// access instead of the LLVM 22+-only LLVMGetSwitchCaseValue. +func (v Value) GetSwitchCaseValue(i int) Value { + return v.Operand(2 * i) +} diff --git a/switch_test.go b/switch_test.go new file mode 100644 index 0000000..8cd9c8c --- /dev/null +++ b/switch_test.go @@ -0,0 +1,79 @@ +package llvm + +import ( + "os" + "testing" +) + +// TestSwitchCaseValue checks that GetSwitchCaseValue/SuccessorsCount/Successor +// correctly read a switch instruction's cases across LLVM versions. LLVM 22 +// stopped exposing case values as regular instruction operands (only the +// condition and destination-block operands remain), instead requiring the +// new LLVMGetSwitchCaseValue API; code that assumed the old operand layout +// silently reads a destination block where it expects a case value. +func TestSwitchCaseValue(t *testing.T) { + src := ` +define void @foo(i64 %callback) { +entry: + switch i64 %callback, label %default [ + i64 0, label %case0 + i64 5, label %case1 + ] +default: + ret void +case0: + ret void +case1: + ret void +} +` + f, err := os.CreateTemp("", "switchcase-*.ll") + if err != nil { + t.Fatal(err) + } + defer f.Close() + defer os.Remove(f.Name()) + if _, err := f.WriteString(src); err != nil { + t.Fatal(err) + } + if err := f.Close(); err != nil { + t.Fatal(err) + } + + ctx := NewContext() + defer ctx.Dispose() + + buf, err := NewMemoryBufferFromFile(f.Name()) + if err != nil { + t.Fatal(err) + } + + m, err := ctx.ParseIR(buf) + if err != nil { + t.Fatal(err) + } + defer m.Dispose() + + fn := m.NamedFunction("foo") + sw := fn.EntryBasicBlock().FirstInstruction() + + if n := sw.SuccessorsCount(); n != 3 { + t.Fatalf("expected 3 successors (default + 2 cases), got %d", n) + } + if got := sw.Successor(0).AsValue().Name(); got != "default" { + t.Errorf("expected default destination %q, got %q", "default", got) + } + + wantCaseValues := []uint64{0, 5} + wantCaseDests := []string{"case0", "case1"} + for i, want := range wantCaseValues { + successor := i + 1 + val := sw.GetSwitchCaseValue(successor) + if got := val.ZExtValue(); got != want { + t.Errorf("case %d: expected value %d, got %d", i, want, got) + } + if got := sw.Successor(successor).AsValue().Name(); got != wantCaseDests[i] { + t.Errorf("case %d: expected destination %q, got %q", i, wantCaseDests[i], got) + } + } +}