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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When targetTriple is empty, the C side silently falls back to Archive::getDefaultKind() (host default) rather than failing (backports.cpp:47-51). WriteArchive doesn't reject an empty triple, so this fallback is reachable. Worth documenting the empty-triple behavior here so callers know an unset triple produces a host-default archive format rather than an error.

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 != "":

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The switch silently prefers the file branch when a member has both path and a buffer set, ignoring the buffer with no diagnostic — unlike the clear per-index error in the default case. The exported constructors never produce such a member, but fields are package-visible and internal callers (e.g. the raw ArchiveMember{} literals in the tests) could set both. Consider either documenting the precedence on ArchiveMember or rejecting an ambiguous member (both path and buffer) with an explicit error, mirroring the empty-member check below.

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
Loading