Files
sx/clang_shim.cpp
2026-02-22 17:24:04 +02:00

316 lines
11 KiB
C++

#include "clang_shim.h"
// clang C++ API
#include <clang/AST/ASTConsumer.h>
#include <clang/AST/ASTContext.h>
#include <clang/AST/Decl.h>
#include <clang/Basic/DiagnosticOptions.h>
#include <clang/CodeGen/CodeGenAction.h>
#include <clang/Driver/Compilation.h>
#include <clang/Driver/Driver.h>
#include <clang/Frontend/CompilerInstance.h>
#include <clang/Frontend/CompilerInvocation.h>
#include <clang/Frontend/FrontendAction.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/LegacyPassManager.h>
#include <llvm/IR/Module.h>
#include <llvm/MC/TargetRegistry.h>
#include <llvm/Support/TargetSelect.h>
#include <llvm/Support/raw_ostream.h>
#include <llvm/Target/TargetMachine.h>
#include <llvm/Target/TargetOptions.h>
#include <llvm/TargetParser/Host.h>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
/* ------------------------------------------------------------------ */
/* Header parsing via clang C++ AST */
/* ------------------------------------------------------------------ */
/// AST consumer that collects top-level function declarations.
class FunctionCollector : public clang::ASTConsumer {
public:
std::vector<SxCFunctionInfo> &functions;
clang::SourceManager *SM = nullptr;
clang::FileID mainFileID;
FunctionCollector(std::vector<SxCFunctionInfo> &funcs) : functions(funcs) {}
void Initialize(clang::ASTContext &ctx) override {
SM = &ctx.getSourceManager();
mainFileID = SM->getMainFileID();
}
bool HandleTopLevelDecl(clang::DeclGroupRef DG) override {
for (auto *D : DG) {
auto *FD = llvm::dyn_cast<clang::FunctionDecl>(D);
if (!FD) continue;
// Only include functions from the main file
clang::SourceLocation loc = FD->getLocation();
if (!SM->isInFileID(SM->getExpansionLoc(loc), mainFileID))
continue;
// Skip anonymous/internal functions
std::string name = FD->getNameAsString();
if (name.empty() || name[0] == '_') continue;
// Return type (canonical to resolve typedefs)
std::string ret_type = FD->getReturnType().getCanonicalType().getAsString();
// Parameters
unsigned num_params = FD->getNumParams();
auto *params = num_params > 0
? static_cast<SxCParamInfo *>(calloc(num_params, sizeof(SxCParamInfo)))
: nullptr;
for (unsigned i = 0; i < num_params; i++) {
const clang::ParmVarDecl *P = FD->getParamDecl(i);
std::string pname = P->getNameAsString();
std::string ptype = P->getType().getCanonicalType().getAsString();
params[i].name = strdup(pname.c_str());
params[i].type_spelling = strdup(ptype.c_str());
}
// Source location
clang::PresumedLoc PLoc = SM->getPresumedLoc(loc);
SxCFunctionInfo fi;
fi.name = strdup(name.c_str());
fi.return_type = strdup(ret_type.c_str());
fi.params = params;
fi.num_params = static_cast<int>(num_params);
fi.source_file = PLoc.isValid() ? strdup(PLoc.getFilename()) : nullptr;
fi.source_line = PLoc.isValid() ? PLoc.getLine() : 0;
functions.push_back(fi);
}
return true;
}
};
/// Frontend action that creates a FunctionCollector consumer.
class CollectFunctionsAction : public clang::ASTFrontendAction {
public:
std::vector<SxCFunctionInfo> &functions;
CollectFunctionsAction(std::vector<SxCFunctionInfo> &funcs) : functions(funcs) {}
std::unique_ptr<clang::ASTConsumer>
CreateASTConsumer(clang::CompilerInstance &, llvm::StringRef) override {
return std::make_unique<FunctionCollector>(functions);
}
};
/// Helper: build a CompilerInstance from user args + filename using the Driver.
/// Returns nullptr on failure (sets out_error).
static std::unique_ptr<clang::CompilerInstance>
buildCompilerInstance(const char *filename,
const char **args, int num_args,
const llvm::SmallVectorImpl<const char *> &extra_flags,
char **out_error)
{
auto diagOpts = new clang::DiagnosticOptions();
auto diagIDs = new clang::DiagnosticIDs();
clang::DiagnosticsEngine diags(diagIDs, diagOpts,
new clang::IgnoringDiagConsumer());
clang::driver::Driver drv("clang",
llvm::sys::getDefaultTargetTriple(), diags);
drv.setCheckInputsExist(false);
llvm::SmallVector<const char *, 32> driver_args;
driver_args.push_back("clang");
driver_args.push_back("-c");
driver_args.push_back("-w");
#ifdef SX_LLVM_PREFIX
static std::string resource_dir = std::string(SX_LLVM_PREFIX) + "/lib/clang/18";
driver_args.push_back("-resource-dir");
driver_args.push_back(resource_dir.c_str());
#endif
for (const auto *f : extra_flags)
driver_args.push_back(f);
for (int i = 0; i < num_args; i++)
driver_args.push_back(args[i]);
driver_args.push_back(filename);
std::unique_ptr<clang::driver::Compilation> comp(
drv.BuildCompilation(driver_args));
if (!comp || comp->getJobs().empty()) {
if (out_error) *out_error = strdup("failed to build compilation");
return nullptr;
}
const auto &cmd = llvm::cast<clang::driver::Command>(
*comp->getJobs().begin());
const auto &cc1_args = cmd.getArguments();
auto invocation = std::make_shared<clang::CompilerInvocation>();
bool ok = clang::CompilerInvocation::CreateFromArgs(
*invocation, cc1_args, diags);
if (!ok) {
if (out_error) *out_error = strdup("failed to create compiler invocation");
return nullptr;
}
auto CI = std::make_unique<clang::CompilerInstance>();
CI->setInvocation(std::move(invocation));
CI->createDiagnostics(new clang::IgnoringDiagConsumer());
return CI;
}
extern "C" SxCHeaderInfo *sx_clang_parse_header(
const char *filename,
const char **args, int num_args,
char **out_error)
{
// Parse with -fsyntax-only (no codegen needed)
llvm::SmallVector<const char *, 4> extra;
extra.push_back("-fsyntax-only");
auto CI = buildCompilerInstance(filename, args, num_args, extra, out_error);
if (!CI) return nullptr;
std::vector<SxCFunctionInfo> functions;
CollectFunctionsAction action(functions);
if (!CI->ExecuteAction(action)) {
if (out_error) *out_error = strdup("failed to parse header");
return nullptr;
}
// Convert to C struct
auto *info = static_cast<SxCHeaderInfo *>(malloc(sizeof(SxCHeaderInfo)));
info->num_functions = static_cast<int>(functions.size());
info->functions = static_cast<SxCFunctionInfo *>(
calloc(info->num_functions, sizeof(SxCFunctionInfo)));
for (int i = 0; i < info->num_functions; i++) {
info->functions[i] = functions[i];
}
return info;
}
extern "C" void sx_clang_free_header_info(SxCHeaderInfo *info) {
if (!info) return;
for (int i = 0; i < info->num_functions; i++) {
auto &f = info->functions[i];
free(const_cast<char *>(f.name));
free(const_cast<char *>(f.return_type));
if (f.source_file) free(const_cast<char *>(f.source_file));
for (int j = 0; j < f.num_params; j++) {
free(const_cast<char *>(f.params[j].name));
free(const_cast<char *>(f.params[j].type_spelling));
}
free(f.params);
}
free(info->functions);
free(info);
}
/* ------------------------------------------------------------------ */
/* C source compilation to LLVM module */
/* ------------------------------------------------------------------ */
extern "C" LLVMModuleRef sx_clang_compile_to_module(
LLVMContextRef ctx_ref,
const char *filename,
const char **args, int num_args,
char **out_error)
{
llvm::LLVMContext &ctx = *llvm::unwrap(ctx_ref);
llvm::SmallVector<const char *, 4> extra;
auto CI = buildCompilerInstance(filename, args, num_args, extra, out_error);
if (!CI) return nullptr;
clang::EmitLLVMOnlyAction action(&ctx);
if (!CI->ExecuteAction(action)) {
if (out_error) *out_error = strdup("clang compilation failed");
return nullptr;
}
std::unique_ptr<llvm::Module> mod = action.takeModule();
if (!mod) {
if (out_error) *out_error = strdup("no module produced");
return nullptr;
}
return llvm::wrap(mod.release());
}
/* ------------------------------------------------------------------ */
/* C source compilation to native object code */
/* ------------------------------------------------------------------ */
extern "C" LLVMMemoryBufferRef sx_clang_compile_to_object(
const char *filename,
const char **args, int num_args,
char **out_error)
{
// Initialize LLVM targets (idempotent)
llvm::InitializeAllTargets();
llvm::InitializeAllTargetMCs();
llvm::InitializeAllAsmPrinters();
llvm::InitializeAllAsmParsers();
// Use a local context — the module is temporary, only .o bytes are kept
llvm::LLVMContext ctx;
llvm::SmallVector<const char *, 4> extra;
extra.push_back("-fPIC");
auto CI = buildCompilerInstance(filename, args, num_args, extra, out_error);
if (!CI) return nullptr;
clang::EmitLLVMOnlyAction action(&ctx);
if (!CI->ExecuteAction(action)) {
if (out_error) *out_error = strdup("clang compilation failed");
return nullptr;
}
std::unique_ptr<llvm::Module> mod = action.takeModule();
if (!mod) {
if (out_error) *out_error = strdup("no module produced");
return nullptr;
}
// Compile LLVM module to native object code
std::string triple = mod->getTargetTriple();
std::string err_str;
const llvm::Target *target = llvm::TargetRegistry::lookupTarget(triple, err_str);
if (!target) {
if (out_error) *out_error = strdup(("target lookup failed: " + err_str).c_str());
return nullptr;
}
llvm::TargetOptions opts;
auto TM = std::unique_ptr<llvm::TargetMachine>(
target->createTargetMachine(triple, "generic", "", opts,
llvm::Reloc::PIC_));
if (!TM) {
if (out_error) *out_error = strdup("failed to create target machine");
return nullptr;
}
mod->setDataLayout(TM->createDataLayout());
llvm::SmallVector<char, 0> obj_buf;
llvm::raw_svector_ostream OS(obj_buf);
llvm::legacy::PassManager PM;
if (TM->addPassesToEmitFile(PM, OS, nullptr,
llvm::CodeGenFileType::ObjectFile)) {
if (out_error) *out_error = strdup("target cannot emit object file");
return nullptr;
}
PM.run(*mod);
// Return as LLVMMemoryBufferRef
auto buf = llvm::MemoryBuffer::getMemBufferCopy(
llvm::StringRef(obj_buf.data(), obj_buf.size()), "c_import.o");
return llvm::wrap(buf.release());
}