-
Notifications
You must be signed in to change notification settings - Fork 7
llvm: expose callee reuse and non-consuming IR parsing #55
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
base: xgo
Are you sure you want to change the base?
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,31 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| #include "CApiBindings.h" | ||
| #include "llvm-c/IRReader.h" | ||
| #include "llvm/Config/llvm-config.h" | ||
| #include "llvm/IR/Module.h" | ||
| #include "llvm/Support/MemoryBuffer.h" | ||
|
|
||
| LLVMValueRef LLVMGoGetOrInsertFunction(LLVMModuleRef M, const char *Name, | ||
| size_t NameLen, LLVMTypeRef FunctionTy) { | ||
| #if LLVM_VERSION_MAJOR >= 22 | ||
| return LLVMGetOrInsertFunction(M, Name, NameLen, FunctionTy); | ||
| #else | ||
| return llvm::wrap(llvm::unwrap(M)->getOrInsertFunction( | ||
| llvm::StringRef(Name, NameLen), llvm::unwrap<llvm::FunctionType>(FunctionTy)) | ||
| .getCallee()); | ||
| #endif | ||
| } | ||
|
|
||
| LLVMBool LLVMGoParseIRInContext(LLVMContextRef Context, LLVMMemoryBufferRef Buffer, | ||
| LLVMModuleRef *OutModule, char **OutMessage) { | ||
| #if LLVM_VERSION_MAJOR >= 22 | ||
| return LLVMParseIRInContext2(Context, Buffer, OutModule, OutMessage); | ||
| #else | ||
| // The legacy parser consumes its input even on failure. Give it an owned | ||
| // copy so the caller keeps the same ownership contract on older LLVMs. | ||
| LLVMMemoryBufferRef Copy = LLVMCreateMemoryBufferWithMemoryRangeCopy( | ||
| LLVMGetBufferStart(Buffer), LLVMGetBufferSize(Buffer), | ||
| llvm::unwrap(Buffer)->getBufferIdentifier().str().c_str()); | ||
| return LLVMParseIRInContext(Context, Copy, OutModule, OutMessage); | ||
| #endif | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| #ifndef LLVM_BINDINGS_GO_C_API_BINDINGS_H | ||
| #define LLVM_BINDINGS_GO_C_API_BINDINGS_H | ||
|
|
||
| #include "llvm-c/Core.h" | ||
|
|
||
| #ifdef __cplusplus | ||
| extern "C" { | ||
| #endif | ||
|
|
||
| LLVMValueRef LLVMGoGetOrInsertFunction(LLVMModuleRef M, const char *Name, | ||
| size_t NameLen, LLVMTypeRef FunctionTy); | ||
| LLVMBool LLVMGoParseIRInContext(LLVMContextRef Context, LLVMMemoryBufferRef Buffer, | ||
| LLVMModuleRef *OutModule, char **OutMessage); | ||
|
|
||
| #ifdef __cplusplus | ||
| } | ||
| #endif | ||
| #endif | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| package llvm | ||
|
|
||
| /* | ||
| #include "CApiBindings.h" | ||
| #include <stdlib.h> | ||
| */ | ||
| import "C" | ||
| import "unsafe" | ||
|
|
||
| // GetOrInsertFunction returns the callee for name, creating an external function | ||
| // declaration with type ft if the name is absent. Existing symbols, types, and | ||
| // attributes are preserved. The result can be an alias or, with typed pointers, | ||
| // a constant-expression cast rather than a Function; callers should use ft when | ||
|
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] GetOrInsertFunction doc describes an unreachable typed-pointer case The comment says the result "can be an alias or, with typed pointers, a constant-expression cast rather than a Function." All LLVM versions this binding compiles against use opaque pointers, so the constant-expression-cast case (typed-pointer mode) can no longer occur. The clause is hedged and not strictly wrong, but it documents a path unreachable here and could lead readers to expect a cast expression. Consider dropping the typed-pointer clause; the alias caveat and |
||
| // constructing calls and check IsAFunction before accessing function attributes. | ||
| func (m Module) GetOrInsertFunction(name string, ft Type) (v Value) { | ||
| cname := C.CString(name) | ||
| defer C.free(unsafe.Pointer(cname)) | ||
| v.C = C.LLVMGoGetOrInsertFunction(m.C, cname, C.size_t(len(name)), ft.C) | ||
| return | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| package llvm | ||
|
|
||
| import "testing" | ||
|
|
||
| func TestGetOrInsertFunction(t *testing.T) { | ||
| ctx := NewContext() | ||
| defer ctx.Dispose() | ||
| m := ctx.NewModule("callee") | ||
| defer m.Dispose() | ||
| ft := FunctionType(ctx.VoidType(), nil, false) | ||
| fn := m.GetOrInsertFunction("target", ft) | ||
| if fn.IsAFunction().IsNil() || fn.GlobalValueType() != ft || fn.Linkage() != ExternalLinkage { | ||
| t.Fatalf("unexpected declaration: %s", fn) | ||
| } | ||
| attr := ctx.CreateEnumAttribute(AttributeKindID("nounwind"), 0) | ||
| fn.AddFunctionAttr(attr) | ||
| if got := m.GetOrInsertFunction("target", ft); got != fn || got.GetEnumFunctionAttribute(AttributeKindID("nounwind")) != attr { | ||
| t.Fatal("existing function or attributes were not preserved") | ||
| } | ||
| // Reusing a name with a different function type must preserve the original | ||
| // declaration and return a callable value, without creating target.1. | ||
| otherType := FunctionType(ctx.VoidType(), []Type{ctx.Int32Type()}, false) | ||
| callee := m.GetOrInsertFunction("target", otherType) | ||
| if m.NamedFunction("target") != fn || fn.GlobalValueType() != ft || !m.NamedFunction("target.1").IsNil() { | ||
| t.Fatal("type mismatch changed or duplicated the existing declaration") | ||
| } | ||
| caller := AddFunction(m, "caller", ft) | ||
| b := ctx.NewBuilder() | ||
| defer b.Dispose() | ||
| b.SetInsertPointAtEnd(ctx.AddBasicBlock(caller, "entry")) | ||
| b.CreateCall(otherType, callee, []Value{ConstInt(ctx.Int32Type(), 7, false)}, "") | ||
| b.CreateRetVoid() | ||
| if err := VerifyModule(m, ReturnStatusAction); err != nil { | ||
| t.Fatalf("callee is not callable with requested type: %v\n%s", err, m) | ||
| } | ||
| } | ||
|
|
||
| func TestGetOrInsertFunctionAlias(t *testing.T) { | ||
| ctx := NewContext() | ||
| defer ctx.Dispose() | ||
| m := ctx.NewModule("alias") | ||
| defer m.Dispose() | ||
| ft := FunctionType(ctx.VoidType(), nil, false) | ||
| fn := AddFunction(m, "implementation", ft) | ||
| b := ctx.NewBuilder() | ||
| defer b.Dispose() | ||
| b.SetInsertPointAtEnd(ctx.AddBasicBlock(fn, "entry")) | ||
| b.CreateRetVoid() | ||
| alias := AddAlias(m, ft, 0, fn, "entrypoint") | ||
| if got := m.GetOrInsertFunction("entrypoint", ft); got != alias || got.IsAGlobalAlias().IsNil() { | ||
| t.Fatalf("expected existing alias, got %s", got) | ||
| } | ||
| if !m.NamedFunction("entrypoint.1").IsNil() { | ||
| t.Fatal("created a duplicate declaration for an existing alias") | ||
| } | ||
| if err := VerifyModule(m, ReturnStatusAction); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ package llvm | |
| /* | ||
| #include "llvm-c/Core.h" | ||
| #include "llvm-c/IRReader.h" | ||
| #include "CApiBindings.h" | ||
| #include <stdlib.h> | ||
| */ | ||
| import "C" | ||
|
|
@@ -23,8 +24,9 @@ import ( | |
| "errors" | ||
| ) | ||
|
|
||
| // ParseIR parses the textual IR given in the memory buffer and returns a new | ||
| // LLVM module in this context. | ||
| // ParseIR parses LLVM assembly or bitcode into a new module in this context. | ||
| // It consumes buf on both success and failure. The caller must not access or | ||
| // dispose buf after calling ParseIR. Use ParseIRBuffer to retain ownership. | ||
| func (c *Context) ParseIR(buf MemoryBuffer) (Module, error) { | ||
| var m Module | ||
| var errmsg *C.char | ||
|
|
@@ -35,3 +37,17 @@ func (c *Context) ParseIR(buf MemoryBuffer) (Module, error) { | |
| } | ||
| return m, nil | ||
| } | ||
|
|
||
| // ParseIRBuffer parses LLVM assembly or bitcode into a new module in this | ||
| // context without consuming buf, on either success or failure. The caller may | ||
| // reuse buf and is responsible for calling buf.Dispose when finished. | ||
| func (c *Context) ParseIRBuffer(buf MemoryBuffer) (Module, error) { | ||
| var m Module | ||
| var errmsg *C.char | ||
| if C.LLVMGoParseIRInContext(c.C, buf.C, &m.C, &errmsg) != 0 { | ||
|
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] No nil-buffer guard in ParseIRBuffer The legacy shim dereferences |
||
| err := errors.New(C.GoString(errmsg)) | ||
| C.LLVMDisposeMessage(errmsg) | ||
| return Module{}, err | ||
| } | ||
| return m, nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| package llvm | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "os" | ||
| "path/filepath" | ||
| "strings" | ||
| "testing" | ||
| ) | ||
|
|
||
| func TestParseIRBufferOwnership(t *testing.T) { | ||
| for _, format := range []string{"assembly", "bitcode", "invalid"} { | ||
| t.Run(format, func(t *testing.T) { | ||
| ctx := NewContext() | ||
| defer ctx.Dispose() | ||
| var buf MemoryBuffer | ||
| var inputPath string | ||
| if format == "bitcode" { | ||
| m := ctx.NewModule("source") | ||
| AddFunction(m, "target", FunctionType(ctx.VoidType(), nil, false)) | ||
| buf = WriteBitcodeToMemoryBuffer(m) | ||
| m.Dispose() | ||
| } else { | ||
| src := "declare void @target()\n" | ||
| if format == "invalid" { | ||
| src = "define void @broken( {\n" | ||
| } | ||
| path := filepath.Join(t.TempDir(), "input.ll") | ||
| inputPath = path | ||
| if err := os.WriteFile(path, []byte(src), 0600); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| var err error | ||
| buf, err = NewMemoryBufferFromFile(path) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| } | ||
| defer buf.Dispose() | ||
| want := buf.Bytes() | ||
| // Parse twice, with an explicit read after each module is disposed. | ||
| // This covers ownership on success and on the diagnostic path. | ||
| for i := 0; i < 2; i++ { | ||
| m, err := ctx.ParseIRBuffer(buf) | ||
| if format == "invalid" { | ||
| if err == nil || err.Error() == "" || !m.IsNil() { | ||
| t.Fatalf("invalid IR: error=%v", err) | ||
| } | ||
| if !strings.Contains(err.Error(), inputPath) { | ||
| t.Fatalf("diagnostic lost buffer identifier: %v", err) | ||
| } | ||
| } else { | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| if m.NamedFunction("target").IsNil() { | ||
| t.Fatal("parsed module is missing target") | ||
| } | ||
| if err := VerifyModule(m, ReturnStatusAction); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| if format == "assembly" && !strings.Contains(m.String(), `source_filename = "`+inputPath+`"`) { | ||
| t.Fatalf("module lost buffer identifier: %s", m) | ||
| } | ||
| m.Dispose() | ||
| } | ||
| if !bytes.Equal(buf.Bytes(), want) { | ||
| t.Fatal("parser changed the caller's buffer") | ||
| } | ||
| } | ||
| }) | ||
| } | ||
| } |
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] Header files miss the LLVM banner/license block used repo-wide
Every other bindings source in this repo (
IRBindings.h/.cpp,backports.cpp,SupportBindings.h, ...) opens with the LLVM//===- File - description -*- C++ -*-===//banner plus the full license block.CApiBindings.handCApiBindings.cppuse only a bare// SPDX-License-Identifierline. Consider adding the standard banner for consistency with the surrounding files.