Skip to content
Open
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
109 changes: 109 additions & 0 deletions archive.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//===- archive.go - Bindings for archive writing --------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file defines bindings for llvm::writeArchive.
//
//===----------------------------------------------------------------------===//

package llvm

/*
#include "llvm-c/Core.h"
#include "backports.h"
#include <stdlib.h>
*/
import "C"

import (
"errors"
"fmt"
"unsafe"
)

// ArchiveMember is an object or bitcode file to add to an archive.
//
// A memory-buffer member borrows its buffer. The caller must keep the buffer
// alive until WriteArchive returns.
type ArchiveMember struct {
name string
path string
buffer MemoryBuffer
}

// NewArchiveMemberFromFile creates a path-backed archive member. The archive
// member name is the base name of path.
func NewArchiveMemberFromFile(path string) ArchiveMember {
return ArchiveMember{path: path}
}

// NewArchiveMemberFromMemoryBuffer creates a memory-backed archive member.
// name is the name stored in the archive.
func NewArchiveMemberFromMemoryBuffer(name string, buffer MemoryBuffer) ArchiveMember {
return ArchiveMember{name: name, buffer: buffer}
}

// WriteArchive creates a deterministic, non-thin archive with a symbol table.
// targetTriple determines the platform archive format.
func WriteArchive(path, targetTriple string, members []ArchiveMember) error {
if path == "" {
return errors.New("archive path is empty")
}
if len(members) == 0 {
return errors.New("archive has no members")
}

cTriple := C.CString(targetTriple)
defer C.free(unsafe.Pointer(cTriple))
writer := C.LLVMGoCreateArchiveWriter(cTriple)
if writer == nil {
return errors.New("failed to create archive writer")
}
defer C.LLVMGoDisposeArchiveWriter(writer)

for i, member := range members {
var cErr *C.char
switch {
case member.path != "":
cPath := C.CString(member.path)
var cName *C.char
if member.name != "" {
cName = C.CString(member.name)
}
cErr = C.LLVMGoArchiveWriterAddFile(writer, cPath, cName)
C.free(unsafe.Pointer(cPath))
C.free(unsafe.Pointer(cName))
case !member.buffer.IsNil():
if member.name == "" {
return fmt.Errorf("archive member %d has an empty name", i)
}
cName := C.CString(member.name)
cErr = C.LLVMGoArchiveWriterAddMemoryBuffer(writer, member.buffer.C, cName)
C.free(unsafe.Pointer(cName))
default:
return fmt.Errorf("archive member %d has no file or memory buffer", i)
}
if cErr != nil {
err := errors.New(C.GoString(cErr))
C.LLVMDisposeMessage(cErr)
label := member.name
if member.path != "" {
label = member.path
}
return fmt.Errorf("add archive member %d (%q): %w", i, label, err)
}
}

cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
if cErr := C.LLVMGoArchiveWriterWrite(writer, cPath); cErr != nil {
err := errors.New(C.GoString(cErr))
C.LLVMDisposeMessage(cErr)
return err
}
return nil
}
87 changes: 87 additions & 0 deletions archive_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//===- archive_test.go - Tests for archive writing ------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

package llvm

import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
)

func TestWriteArchiveMemoryAndFileMembers(t *testing.T) {
ctx := NewContext()
defer ctx.Dispose()

mod := ctx.NewModule("archive")
defer mod.Dispose()
mod.SetTarget("x86_64-unknown-linux-gnu")
AddFunction(mod, "answer", FunctionType(ctx.Int32Type(), nil, false))

memoryBuf := WriteBitcodeToMemoryBuffer(mod)
defer memoryBuf.Dispose()

fileBuf := WriteBitcodeToMemoryBuffer(mod)
defer fileBuf.Dispose()
filePath := filepath.Join(t.TempDir(), "from-file.bc")
if err := os.WriteFile(filePath, fileBuf.Bytes(), 0o644); err != nil {
t.Fatal(err)
}

archivePath := filepath.Join(t.TempDir(), "libarchive.a")
err := WriteArchive(archivePath, mod.Target(), []ArchiveMember{
NewArchiveMemberFromMemoryBuffer("from-memory.bc", memoryBuf),
NewArchiveMemberFromFile(filePath),
})
if err != nil {
t.Fatal(err)
}

data, err := os.ReadFile(archivePath)
if err != nil {
t.Fatal(err)
}
if !bytes.HasPrefix(data, []byte("!<arch>\n")) {
end := len(data)
if end > 8 {
end = 8
}
t.Fatalf("archive magic = %q", data[:end])
}
for _, name := range []string{"from-memory.bc", "from-file.bc"} {
if !bytes.Contains(data, []byte(name)) {
t.Errorf("archive does not contain member name %q", name)
}
}
}

func TestWriteArchiveRejectsInvalidMembers(t *testing.T) {
path := filepath.Join(t.TempDir(), "invalid.a")
missing := filepath.Join(t.TempDir(), "missing.o")
tests := []struct {
name string
path string
members []ArchiveMember
want string
}{
{name: "empty path", members: []ArchiveMember{{}}, want: "archive path is empty"},
{name: "no members", path: path, want: "archive has no members"},
{name: "empty member", path: path, members: []ArchiveMember{{}}, want: "has no file or memory buffer"},
{name: "missing file", path: path, members: []ArchiveMember{NewArchiveMemberFromFile(missing)}, want: missing},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := WriteArchive(tt.path, "x86_64-unknown-linux-gnu", tt.members)
if err == nil || !strings.Contains(err.Error(), tt.want) {
t.Fatalf("WriteArchive error = %v, want %q", err, tt.want)
}
})
}
}
101 changes: 101 additions & 0 deletions backports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,111 @@
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#endif
#include "llvm/IR/Module.h"
#include "llvm/Object/Archive.h"
#include "llvm/Object/ArchiveWriter.h"
#include "llvm/Pass.h"
#include "llvm/Support/Error.h"
#if LLVM_VERSION_MAJOR >= 18
#include "llvm/TargetParser/Host.h"
#else
#include "llvm/Support/Host.h"
#endif
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#if LLVM_VERSION_MAJOR >= 16
#include "llvm/TargetParser/Triple.h"
#else
#include "llvm/ADT/Triple.h"
#endif
#include "llvm/Transforms/IPO.h"
#include "llvm-c/Core.h"
#include "llvm-c/DebugInfo.h"
#include <deque>

struct LLVMGoArchiveWriterOpaque {
llvm::object::Archive::Kind Kind;
std::vector<llvm::NewArchiveMember> Members;
std::deque<std::string> Names;
};

static char *LLVMGoArchiveError(llvm::Error Err) {
if (!Err)
return nullptr;
return LLVMCreateMessage(llvm::toString(std::move(Err)).c_str());
}

LLVMGoArchiveWriterRef LLVMGoCreateArchiveWriter(const char *TargetTriple) {
auto TripleText = TargetTriple && TargetTriple[0]
? llvm::Triple::normalize(TargetTriple)
: llvm::sys::getDefaultTargetTriple();
llvm::Triple Triple(TripleText);
auto Kind = llvm::object::Archive::K_GNU;
if (Triple.isOSDarwin())
Kind = llvm::object::Archive::K_DARWIN;
else if (Triple.isOSAIX())
Kind = llvm::object::Archive::K_AIXBIG;
else if (Triple.isOSWindows())
Kind = llvm::object::Archive::K_COFF;
return new LLVMGoArchiveWriterOpaque{Kind, {}, {}};
}

char *LLVMGoArchiveWriterAddFile(LLVMGoArchiveWriterRef Writer,
const char *Path, const char *Name) {
if (!Writer)
return LLVMCreateMessage("archive writer is nil");
if (!Path || !Path[0])
return LLVMCreateMessage("archive member path is empty");

auto MemberOrErr = llvm::NewArchiveMember::getFile(Path, true);
if (!MemberOrErr)
return LLVMGoArchiveError(MemberOrErr.takeError());

Writer->Names.emplace_back(
Name && Name[0] ? Name
: llvm::sys::path::filename(Path).str());
MemberOrErr->MemberName = Writer->Names.back();
Writer->Members.push_back(std::move(*MemberOrErr));
return nullptr;
}

char *LLVMGoArchiveWriterAddMemoryBuffer(LLVMGoArchiveWriterRef Writer,
LLVMMemoryBufferRef Buffer,
const char *Name) {
if (!Writer)
return LLVMCreateMessage("archive writer is nil");
if (!Buffer)
return LLVMCreateMessage("archive member buffer is nil");
if (!Name || !Name[0])
return LLVMCreateMessage("archive member name is empty");

Writer->Names.emplace_back(Name);
Writer->Members.emplace_back(llvm::unwrap(Buffer)->getMemBufferRef());
Writer->Members.back().MemberName = Writer->Names.back();
return nullptr;
}

char *LLVMGoArchiveWriterWrite(LLVMGoArchiveWriterRef Writer,
const char *ArchivePath) {
if (!Writer)
return LLVMCreateMessage("archive writer is nil");
if (!ArchivePath || !ArchivePath[0])
return LLVMCreateMessage("archive path is empty");
if (Writer->Members.empty())
return LLVMCreateMessage("archive has no members");

#if LLVM_VERSION_MAJOR >= 18
auto WriteSymtab = llvm::SymtabWritingMode::NormalSymtab;
#else
auto WriteSymtab = true;
#endif
return LLVMGoArchiveError(llvm::writeArchive(
ArchivePath, Writer->Members, WriteSymtab, Writer->Kind,
/*Deterministic=*/true, /*Thin=*/false));
}

void LLVMGoDisposeArchiveWriter(LLVMGoArchiveWriterRef Writer) {
delete Writer;
}

void LLVMGlobalObjectAddMetadata(LLVMValueRef Global, unsigned KindID, LLVMMetadataRef MD) {
llvm::MDNode *N = MD ? llvm::unwrap<llvm::MDNode>(MD) : nullptr;
Expand Down
16 changes: 16 additions & 0 deletions backports.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ LLVMMemoryBufferRef LLVMGoWriteThinLTOBitcodeToMemoryBuffer(LLVMModuleRef M);
LLVMMemoryBufferRef LLVMGoWriteFullLTOBitcodeToMemoryBuffer(
LLVMModuleRef M, LLVMBool EnableSplitLTOUnit);

typedef struct LLVMGoArchiveWriterOpaque *LLVMGoArchiveWriterRef;

LLVMGoArchiveWriterRef LLVMGoCreateArchiveWriter(const char *TargetTriple);

char *LLVMGoArchiveWriterAddFile(LLVMGoArchiveWriterRef Writer,
const char *Path, const char *Name);

char *LLVMGoArchiveWriterAddMemoryBuffer(LLVMGoArchiveWriterRef Writer,
LLVMMemoryBufferRef Buffer,
const char *Name);

char *LLVMGoArchiveWriterWrite(LLVMGoArchiveWriterRef Writer,
const char *ArchivePath);

void LLVMGoDisposeArchiveWriter(LLVMGoArchiveWriterRef Writer);

void LLVMGoDIBuilderInsertDbgValueRecordAtEnd(
LLVMDIBuilderRef Builder, LLVMValueRef Val, LLVMMetadataRef VarInfo,
LLVMMetadataRef Expr, LLVMMetadataRef DebugLoc, LLVMBasicBlockRef Block);
Expand Down
9 changes: 9 additions & 0 deletions ir.go
Original file line number Diff line number Diff line change
Expand Up @@ -1329,6 +1329,15 @@ func (v Value) InstructionCallConv() CallConv {
func (v Value) AddCallSiteAttribute(i int, a Attribute) {
C.LLVMAddCallSiteAttribute(v.C, C.LLVMAttributeIndex(i), a.C)
}
func (v Value) GetCallSiteAttributesAtIndex(i int) (attrs []Attribute) {
n := C.LLVMGetCallSiteAttributeCount(v.C, C.LLVMAttributeIndex(i))
if n == 0 {
return
}
attrs = make([]Attribute, n)
C.LLVMGetCallSiteAttributes(v.C, C.LLVMAttributeIndex(i), &attrs[0].C)
return
}
func (v Value) GetCallSiteEnumAttribute(i int, kind uint) (a Attribute) {
a.C = C.LLVMGetCallSiteEnumAttribute(v.C, C.LLVMAttributeIndex(i), C.unsigned(kind))
return
Expand Down
39 changes: 39 additions & 0 deletions ir_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,45 @@ func TestAttributes(t *testing.T) {
}
}

func TestCallSiteAttributesAtIndex(t *testing.T) {
ctx := NewContext()
defer ctx.Dispose()
mod := ctx.NewModule("")
defer mod.Dispose()

ftyp := FunctionType(ctx.VoidType(), nil, false)
callee := AddFunction(mod, "callee", ftyp)
caller := AddFunction(mod, "caller", ftyp)
b := ctx.NewBuilder()
defer b.Dispose()
b.SetInsertPointAtEnd(AddBasicBlock(caller, "entry"))
call := b.CreateCall(ftyp, callee, nil, "")
b.CreateRetVoid()

nounwind := ctx.CreateEnumAttribute(AttributeKindID("nounwind"), 0)
metadata := ctx.CreateStringAttribute("llgo.test", "value")
call.AddCallSiteAttribute(-1, nounwind)
call.AddCallSiteAttribute(-1, metadata)

attrs := call.GetCallSiteAttributesAtIndex(-1)
if len(attrs) != 2 {
t.Fatalf("got %d function-index call-site attributes, want 2", len(attrs))
}
gotNounwind := false
gotMetadata := false
for _, attr := range attrs {
if attr.GetEnumKind() == int(AttributeKindID("nounwind")) {
gotNounwind = true
}
if attr.GetStringKind() == "llgo.test" && attr.GetStringValue() == "value" {
gotMetadata = true
}
}
if !gotNounwind || !gotMetadata {
t.Fatalf("enumerated attributes missing nounwind=%v metadata=%v", gotNounwind, gotMetadata)
}
}

func TestDebugLoc(t *testing.T) {
ctx := NewContext()
mod := ctx.NewModule("")
Expand Down
Loading