Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/coroutine.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
llvm: [19, 21, 22]
llvm: [19, 20, 21, 22]
steps:
- uses: actions/checkout@v4

Expand Down
30 changes: 24 additions & 6 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down Expand Up @@ -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
48 changes: 48 additions & 0 deletions captures_test.go
Original file line number Diff line number Diff line change
@@ -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()
Comment thread
cpunion marked this conversation as resolved.

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)
}
}
12 changes: 12 additions & 0 deletions ir.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
5 changes: 3 additions & 2 deletions llvm_config_llvm19.go
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
5 changes: 3 additions & 2 deletions llvm_config_llvm20.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//go:build !byollvm && llvm20
// +build !byollvm,llvm20

package llvm

Expand All @@ -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
Comment thread
cpunion marked this conversation as resolved.
import "C"

type run_build_sh int
21 changes: 21 additions & 0 deletions switch_llvm22.go
Original file line number Diff line number Diff line change
@@ -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
}
15 changes: 15 additions & 0 deletions switch_pre22.go
Original file line number Diff line number Diff line change
@@ -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)
}
79 changes: 79 additions & 0 deletions switch_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
Loading