use std::ffi::{CStr, CString};
use super::driver::ClangDriver;
use super::{CLangStandard, ClangOptions};
pub const LLVM_NATIVE_ABI_VERSION_MAJOR: u32 = 1;
pub const LLVM_NATIVE_ABI_VERSION_MINOR: u32 = 0;
pub const LLVM_NATIVE_ABI_VERSION_PATCH: u32 = 0;
pub const LLVM_NATIVE_MAX_PATH: usize = 4096;
pub const LLVM_NATIVE_MAX_OPTIONS: usize = 65536;
pub const LLVM_NATIVE_MAX_ERROR: usize = 2048;
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CLangStandardFFI {
C89 = 0,
C99 = 1,
C11 = 2,
C17 = 3,
C23 = 4,
Gnu89 = 5,
Gnu99 = 6,
Gnu11 = 7,
Gnu17 = 8,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CompileFlags {
pub bits: u64,
}
impl CompileFlags {
pub const fn none() -> Self {
Self { bits: 0 }
}
pub const fn from_bit(bit: u64) -> Self {
Self { bits: bit }
}
pub fn has(&self, bit: u64) -> bool {
self.bits & bit != 0
}
pub fn set(&mut self, bit: u64, val: bool) {
if val {
self.bits |= bit;
} else {
self.bits &= !bit;
}
}
pub fn merge(&self, other: &Self) -> Self {
Self {
bits: self.bits | other.bits,
}
}
}
pub mod compile_flag_bits {
pub const OPTIMIZE: u64 = 1 << 0;
pub const DEBUG_INFO: u64 = 1 << 1;
pub const WALL: u64 = 1 << 2;
pub const WERROR: u64 = 1 << 3;
pub const PEDANTIC: u64 = 1 << 4;
pub const VERBOSE: u64 = 1 << 5;
pub const NO_WARNINGS: u64 = 1 << 6;
pub const PIC: u64 = 1 << 7;
pub const EMIT_LLVM: u64 = 1 << 8;
pub const COMPILE_ONLY: u64 = 1 << 9;
pub const OPTIMIZE_MORE: u64 = 1 << 10;
pub const OPTIMIZE_SIZE: u64 = 1 << 11;
pub const LTO: u64 = 1 << 12;
pub const PIE: u64 = 1 << 13;
pub const STATIC: u64 = 1 << 14;
pub const SANITIZE_ADDRESS: u64 = 1 << 15;
pub const SANITIZE_UNDEFINED: u64 = 1 << 16;
pub const SANITIZE_THREAD: u64 = 1 << 17;
pub const SANITIZE_MEMORY: u64 = 1 << 18;
pub const COVERAGE: u64 = 1 << 19;
pub const PGO_GEN: u64 = 1 << 20;
pub const PGO_USE: u64 = 1 << 21;
pub const CFI: u64 = 1 << 22;
pub const SAFE_STACK: u64 = 1 << 23;
pub const SHADOW_CALL_STACK: u64 = 1 << 24;
pub const SHARED: u64 = 1 << 25;
pub const WEXTRA: u64 = 1 << 26;
pub const AST_DUMP: u64 = 1 << 27;
pub const TIME_REPORT: u64 = 1 << 28;
pub const CRASH_DIAG: u64 = 1 << 29;
pub const MODULES: u64 = 1 << 30;
pub const SPLIT_DWARF: u64 = 1 << 31;
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CompileOptionsFFI {
pub standard: CLangStandardFFI,
pub flags: CompileFlags,
pub target_triple: *const std::ffi::c_char,
pub output_file: *const std::ffi::c_char,
pub include_paths: *const std::ffi::c_char,
pub defines: *const std::ffi::c_char,
pub opt_level_override: u8,
pub _reserved: [u64; 4],
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CompileResult {
pub status: i32,
pub error_count: u32,
pub _reserved: [u64; 4],
}
impl CompileResult {
pub const fn success() -> Self {
Self {
status: 0,
error_count: 0,
_reserved: [0; 4],
}
}
pub const fn error(count: u32) -> Self {
Self {
status: -1,
error_count: count,
_reserved: [0; 4],
}
}
pub fn is_ok(&self) -> bool {
self.status == 0
}
pub fn is_err(&self) -> bool {
self.status != 0
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct VersionInfoFFI {
pub major: u32,
pub minor: u32,
pub patch: u32,
pub abi_version: u32,
pub _reserved: [u64; 4],
}
impl VersionInfoFFI {
pub const fn current() -> Self {
Self {
major: LLVM_NATIVE_ABI_VERSION_MAJOR,
minor: LLVM_NATIVE_ABI_VERSION_MINOR,
patch: LLVM_NATIVE_ABI_VERSION_PATCH,
abi_version: 1,
_reserved: [0; 4],
}
}
}
pub mod abi_layout {
use super::*;
pub const COMPILE_OPTIONS_FFI_SIZE: usize = core::mem::size_of::<CompileOptionsFFI>();
pub const COMPILE_RESULT_SIZE: usize = core::mem::size_of::<CompileResult>();
pub const VERSION_INFO_FFI_SIZE: usize = core::mem::size_of::<VersionInfoFFI>();
pub const COMPILE_FLAGS_SIZE: usize = core::mem::size_of::<CompileFlags>();
pub const COMPILE_OPTIONS_FFI_ALIGN: usize = core::mem::align_of::<CompileOptionsFFI>();
pub const COMPILE_RESULT_ALIGN: usize = core::mem::align_of::<CompileResult>();
pub const VERSION_INFO_FFI_ALIGN: usize = core::mem::align_of::<VersionInfoFFI>();
}
std::thread_local! {
static LAST_ERROR: std::cell::RefCell<Option<CString>> = const { std::cell::RefCell::new(None) };
}
static DRIVER_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[allow(unreachable_patterns)]
pub fn standard_ffi_to_rust(val: CLangStandardFFI) -> CLangStandard {
match val {
CLangStandardFFI::C89 => CLangStandard::C89,
CLangStandardFFI::C99 => CLangStandard::C99,
CLangStandardFFI::C11 => CLangStandard::C11,
CLangStandardFFI::C17 => CLangStandard::C17,
CLangStandardFFI::C23 => CLangStandard::C23,
CLangStandardFFI::Gnu89 => CLangStandard::Gnu89,
CLangStandardFFI::Gnu99 => CLangStandard::Gnu99,
CLangStandardFFI::Gnu11 => CLangStandard::Gnu11,
CLangStandardFFI::Gnu17 => CLangStandard::Gnu17,
_ => CLangStandard::C17,
}
}
pub fn standard_rust_to_ffi(val: CLangStandard) -> CLangStandardFFI {
match val {
CLangStandard::C89 => CLangStandardFFI::C89,
CLangStandard::C99 => CLangStandardFFI::C99,
CLangStandard::C11 => CLangStandardFFI::C11,
CLangStandard::C17 => CLangStandardFFI::C17,
CLangStandard::C23 => CLangStandardFFI::C23,
CLangStandard::Gnu89 => CLangStandardFFI::Gnu89,
CLangStandard::Gnu99 => CLangStandardFFI::Gnu99,
CLangStandard::Gnu11 => CLangStandardFFI::Gnu11,
CLangStandard::Gnu17 => CLangStandardFFI::Gnu17,
}
}
pub unsafe fn compile_options_ffi_to_rust(opts: *const CompileOptionsFFI) -> ClangOptions { unsafe {
if opts.is_null() {
return ClangOptions::default();
}
let opts = &*opts;
let flags = opts.flags;
let mut rust_opts = ClangOptions::default();
rust_opts.standard = standard_ffi_to_rust(opts.standard);
rust_opts.optimize =
flags.has(compile_flag_bits::OPTIMIZE) || flags.has(compile_flag_bits::OPTIMIZE_MORE);
rust_opts.debug_info = flags.has(compile_flag_bits::DEBUG_INFO);
rust_opts.warnings = !flags.has(compile_flag_bits::NO_WARNINGS);
rust_opts.pedantic = flags.has(compile_flag_bits::PEDANTIC);
rust_opts.wall = flags.has(compile_flag_bits::WALL);
rust_opts.werror = flags.has(compile_flag_bits::WERROR);
rust_opts.verbose = flags.has(compile_flag_bits::VERBOSE);
if opts.opt_level_override != 0xFF {
rust_opts.optimize = match opts.opt_level_override {
b'0' | b'd' => false,
b'1' | b'2' | b'3' | b'g' | b's' | b'z' => true,
_ => rust_opts.optimize,
};
}
if !opts.target_triple.is_null() {
let cstr = CStr::from_ptr(opts.target_triple);
if let Ok(s) = cstr.to_str() {
rust_opts.target_triple = s.to_string();
}
}
if !opts.output_file.is_null() {
let cstr = CStr::from_ptr(opts.output_file);
if let Ok(s) = cstr.to_str() {
rust_opts.output_file = Some(s.to_string());
}
}
if !opts.include_paths.is_null() {
let cstr = CStr::from_ptr(opts.include_paths);
if let Ok(s) = cstr.to_str() {
for path in s.split(';') {
let trimmed = path.trim();
if !trimmed.is_empty() {
rust_opts.includes.push(trimmed.to_string());
}
}
}
}
if !opts.defines.is_null() {
let cstr = CStr::from_ptr(opts.defines);
if let Ok(s) = cstr.to_str() {
for def in s.split(';') {
let trimmed = def.trim();
if trimmed.is_empty() {
continue;
}
if let Some(eq_pos) = trimmed.find('=') {
let name = trimmed[..eq_pos].to_string();
let value = trimmed[eq_pos + 1..].to_string();
rust_opts.defines.push((name, Some(value)));
} else {
rust_opts.defines.push((trimmed.to_string(), None));
}
}
}
}
rust_opts
}}
pub fn crate_version_string() -> String {
format!(
"llvm-native {}.{}.{} (ABI v{}.{}.{})",
LLVM_NATIVE_ABI_VERSION_MAJOR,
LLVM_NATIVE_ABI_VERSION_MINOR,
LLVM_NATIVE_ABI_VERSION_PATCH,
env!("CARGO_PKG_VERSION_MAJOR"),
env!("CARGO_PKG_VERSION_MINOR"),
env!("CARGO_PKG_VERSION_PATCH"),
)
}
pub fn ffi_version_info() -> VersionInfoFFI {
VersionInfoFFI::current()
}
pub fn validate_ffi_options(opts: *const CompileOptionsFFI) -> Result<(), String> {
if opts.is_null() {
return Ok(());
}
let opts = unsafe { &*opts };
for (i, &word) in opts._reserved.iter().enumerate() {
if word != 0 {
return Err(format!(
"CompileOptionsFFI._reserved[{}] is non-zero (0x{:016x}); must be zero for ABI compat",
i, word
));
}
}
if !opts.target_triple.is_null() {
let cstr = unsafe { CStr::from_ptr(opts.target_triple) };
if cstr.to_str().is_err() {
return Err("target_triple is not valid UTF-8".into());
}
}
if !opts.output_file.is_null() {
let cstr = unsafe { CStr::from_ptr(opts.output_file) };
if cstr.to_str().is_err() {
return Err("output_file is not valid UTF-8".into());
}
}
if !opts.include_paths.is_null() {
let cstr = unsafe { CStr::from_ptr(opts.include_paths) };
if cstr.to_str().is_err() {
return Err("include_paths is not valid UTF-8".into());
}
}
if !opts.defines.is_null() {
let cstr = unsafe { CStr::from_ptr(opts.defines) };
if cstr.to_str().is_err() {
return Err("defines is not valid UTF-8".into());
}
}
Ok(())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn llvm_native_compile_file(
path: *const std::ffi::c_char,
options: *const CompileOptionsFFI,
) -> i32 { unsafe {
if path.is_null() {
set_last_error("llvm_native_compile_file: path is null".into());
return -1;
}
let path_str = match CStr::from_ptr(path).to_str() {
Ok(s) => s.to_string(),
Err(_) => {
set_last_error("llvm_native_compile_file: path is not valid UTF-8".into());
return -1;
}
};
if let Err(e) = validate_ffi_options(options) {
set_last_error(format!("llvm_native_compile_file: invalid options: {}", e));
return -1;
}
let rust_opts = unsafe { compile_options_ffi_to_rust(options) };
let mut driver = ClangDriver::new(rust_opts);
let _lock = match DRIVER_LOCK.lock() {
Ok(guard) => guard,
Err(_poisoned) => {
set_last_error("llvm_native_compile_file: internal lock poisoned".into());
return -1;
}
};
match driver.compile_file(&path_str) {
Ok(_module) => {
clear_last_error();
0
}
Err(errors) => {
let msg = errors.join("; ");
set_last_error(msg);
-1
}
}
}}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn llvm_native_compile_string(
source: *const std::ffi::c_char,
options: *const CompileOptionsFFI,
) -> i32 { unsafe {
if source.is_null() {
set_last_error("llvm_native_compile_string: source is null".into());
return -1;
}
let source_str = match CStr::from_ptr(source).to_str() {
Ok(s) => s.to_string(),
Err(_) => {
set_last_error("llvm_native_compile_string: source is not valid UTF-8".into());
return -1;
}
};
if let Err(e) = validate_ffi_options(options) {
set_last_error(format!("llvm_native_compile_string: invalid options: {}", e));
return -1;
}
let rust_opts = unsafe { compile_options_ffi_to_rust(options) };
let mut driver = ClangDriver::new(rust_opts);
let _lock = match DRIVER_LOCK.lock() {
Ok(guard) => guard,
Err(_poisoned) => {
set_last_error("llvm_native_compile_string: internal lock poisoned".into());
return -1;
}
};
match driver.compile_string(&source_str) {
Ok(_module) => {
clear_last_error();
0
}
Err(errors) => {
let msg = errors.join("; ");
set_last_error(msg);
-1
}
}
}}
#[unsafe(no_mangle)]
pub extern "C" fn llvm_native_get_error() -> *const std::ffi::c_char {
LAST_ERROR.with(|cell| {
let borrow = cell.borrow();
match &*borrow {
Some(cstr) => cstr.as_ptr(),
None => std::ptr::null(),
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn llvm_native_version() -> *const std::ffi::c_char {
static VERSION_STRING: std::sync::OnceLock<std::ffi::CString> = std::sync::OnceLock::new();
VERSION_STRING.get_or_init(|| {
let ver = crate_version_string();
std::ffi::CString::new(ver).expect("version string contains NUL")
}).as_ptr()
}
#[unsafe(no_mangle)]
pub extern "C" fn llvm_native_version_info(
out: *mut VersionInfoFFI,
) -> i32 {
if out.is_null() {
return -1;
}
unsafe {
*out = ffi_version_info();
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn llvm_native_clear_error() {
clear_last_error();
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn llvm_native_validate_options(
options: *const CompileOptionsFFI,
) -> i32 {
match validate_ffi_options(options) {
Ok(()) => {
clear_last_error();
0
}
Err(e) => {
set_last_error(format!("llvm_native_validate_options: {}", e));
-1
}
}
}
fn set_last_error(msg: String) {
LAST_ERROR.with(|cell| {
*cell.borrow_mut() = Some(
CString::new(msg).unwrap_or_else(|_| CString::new("internal error: NUL in error").unwrap())
);
});
}
fn clear_last_error() {
LAST_ERROR.with(|cell| {
*cell.borrow_mut() = None;
});
}
pub mod llvm_native_abi_header {
pub const HEADER_C: &str = r#"/*
* llvm_native.h — C ABI for the llvm-native compiler pipeline.
*
* Auto-generated. Do not edit by hand.
* ABI version: 1.0.0
*
* This header declares the C-compatible interface to llvm-native's
* compilation pipeline. Link against libllvm_native_core.a or
* the shared library build of llvm-native.
*
* Thread safety: All functions are thread-safe. Error state is
* thread-local; concurrent callers do not interfere.
*/
#ifndef LLVM_NATIVE_ABI_H
#define LLVM_NATIVE_ABI_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include <stdint.h>
/* ═══════════════════════════════════════════════════════════════════════
* Constants
* ═══════════════════════════════════════════════════════════════════════ */
#define LLVM_NATIVE_ABI_VERSION_MAJOR 1
#define LLVM_NATIVE_ABI_VERSION_MINOR 0
#define LLVM_NATIVE_ABI_VERSION_PATCH 0
#define LLVM_NATIVE_MAX_PATH 4096
#define LLVM_NATIVE_MAX_OPTIONS 65536
#define LLVM_NATIVE_MAX_ERROR 2048
/* ═══════════════════════════════════════════════════════════════════════
* Language Standards
* ═══════════════════════════════════════════════════════════════════════ */
typedef enum llvm_native_standard {
LLVM_NATIVE_C89 = 0,
LLVM_NATIVE_C99 = 1,
LLVM_NATIVE_C11 = 2,
LLVM_NATIVE_C17 = 3,
LLVM_NATIVE_C23 = 4,
LLVM_NATIVE_GNU89 = 5,
LLVM_NATIVE_GNU99 = 6,
LLVM_NATIVE_GNU11 = 7,
LLVM_NATIVE_GNU17 = 8,
} llvm_native_standard_t;
/* ═══════════════════════════════════════════════════════════════════════
* Flag Bits (for use with compile_flags_t)
* ═══════════════════════════════════════════════════════════════════════ */
#define LLVM_NATIVE_OPTIMIZE (1ULL << 0)
#define LLVM_NATIVE_DEBUG_INFO (1ULL << 1)
#define LLVM_NATIVE_WALL (1ULL << 2)
#define LLVM_NATIVE_WERROR (1ULL << 3)
#define LLVM_NATIVE_PEDANTIC (1ULL << 4)
#define LLVM_NATIVE_VERBOSE (1ULL << 5)
#define LLVM_NATIVE_NO_WARNINGS (1ULL << 6)
#define LLVM_NATIVE_PIC (1ULL << 7)
#define LLVM_NATIVE_EMIT_LLVM (1ULL << 8)
#define LLVM_NATIVE_COMPILE_ONLY (1ULL << 9)
#define LLVM_NATIVE_OPTIMIZE_MORE (1ULL << 10)
#define LLVM_NATIVE_OPTIMIZE_SIZE (1ULL << 11)
#define LLVM_NATIVE_LTO (1ULL << 12)
#define LLVM_NATIVE_PIE (1ULL << 13)
#define LLVM_NATIVE_STATIC (1ULL << 14)
#define LLVM_NATIVE_SANITIZE_ADDRESS (1ULL << 15)
#define LLVM_NATIVE_SANITIZE_UNDEFINED (1ULL << 16)
#define LLVM_NATIVE_SANITIZE_THREAD (1ULL << 17)
#define LLVM_NATIVE_SANITIZE_MEMORY (1ULL << 18)
#define LLVM_NATIVE_COVERAGE (1ULL << 19)
#define LLVM_NATIVE_PGO_GEN (1ULL << 20)
#define LLVM_NATIVE_PGO_USE (1ULL << 21)
#define LLVM_NATIVE_CFI (1ULL << 22)
#define LLVM_NATIVE_SAFE_STACK (1ULL << 23)
#define LLVM_NATIVE_SHADOW_CALL_STACK (1ULL << 24)
#define LLVM_NATIVE_SHARED (1ULL << 25)
#define LLVM_NATIVE_WEXTRA (1ULL << 26)
#define LLVM_NATIVE_AST_DUMP (1ULL << 27)
#define LLVM_NATIVE_TIME_REPORT (1ULL << 28)
#define LLVM_NATIVE_CRASH_DIAG (1ULL << 29)
#define LLVM_NATIVE_MODULES (1ULL << 30)
#define LLVM_NATIVE_SPLIT_DWARF (1ULL << 31)
/* ═══════════════════════════════════════════════════════════════════════
* Data Structures
* ═══════════════════════════════════════════════════════════════════════ */
/* Flags bitmask */
typedef struct llvm_native_compile_flags {
uint64_t bits;
} llvm_native_compile_flags_t;
/* Compilation options passed to compile functions */
typedef struct llvm_native_compile_options {
llvm_native_standard_t standard;
llvm_native_compile_flags_t flags;
const char* target_triple; /* NULL = host default */
const char* output_file; /* NULL = infer */
const char* include_paths; /* semicolon-separated, NULL = none */
const char* defines; /* semicolon-separated, NULL = none */
uint8_t opt_level_override;/* '0'-'3','z','s','d','g', 0xFF=use flags */
uint64_t _reserved[4]; /* must be zero */
} llvm_native_compile_options_t;
/* Compilation result */
typedef struct llvm_native_compile_result {
int32_t status; /* 0 = success, -1 = error */
uint32_t error_count; /* number of errors on failure */
uint64_t _reserved[4]; /* must be zero */
} llvm_native_compile_result_t;
/* Version information */
typedef struct llvm_native_version_info {
uint32_t major;
uint32_t minor;
uint32_t patch;
uint32_t abi_version;
uint64_t _reserved[4]; /* must be zero */
} llvm_native_version_info_t;
/* ═══════════════════════════════════════════════════════════════════════
* Compilation Functions
* ═══════════════════════════════════════════════════════════════════════ */
/*
* Compile a C source file.
*
* @param path Path to the C source file.
* @param options Compilation options, or NULL for defaults.
* @return 0 on success, -1 on error (call llvm_native_get_error()).
*/
int llvm_native_compile_file(const char* path,
const llvm_native_compile_options_t* options);
/*
* Compile a C source string.
*
* @param source C source code as a NUL-terminated string.
* @param options Compilation options, or NULL for defaults.
* @return 0 on success, -1 on error (call llvm_native_get_error()).
*/
int llvm_native_compile_string(const char* source,
const llvm_native_compile_options_t* options);
/*
* Get the last error message (thread-local).
*
* @return Pointer to a NUL-terminated error string, or NULL if no error.
* The pointer is valid until the next llvm_native_* call on the
* same thread. Do NOT free.
*/
const char* llvm_native_get_error(void);
/*
* Clear the thread-local error state.
*/
void llvm_native_clear_error(void);
/*
* Get the version string.
*
* @return Pointer to a static NUL-terminated version string.
* Valid for the lifetime of the process. Do NOT free.
*/
const char* llvm_native_version(void);
/*
* Get structured version information.
*
* @param out Pointer to a version_info_t to fill.
* @return 0 on success, -1 if out is NULL.
*/
int llvm_native_version_info(llvm_native_version_info_t* out);
/*
* Validate compilation options without compiling.
*
* @param options Options to validate, or NULL (always valid).
* @return 0 if valid, -1 if invalid (call llvm_native_get_error()).
*/
int llvm_native_validate_options(const llvm_native_compile_options_t* options);
#ifdef __cplusplus
}
#endif
#endif /* LLVM_NATIVE_ABI_H */
"#;
pub fn header_bytes() -> &'static [u8] {
HEADER_C.as_bytes()
}
pub fn header_len() -> usize {
HEADER_C.len()
}
pub fn write_header(path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
std::fs::write(path.as_ref(), HEADER_C.as_bytes())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
#[test]
fn test_abi_version_constants() {
assert_eq!(LLVM_NATIVE_ABI_VERSION_MAJOR, 1);
assert_eq!(LLVM_NATIVE_ABI_VERSION_MINOR, 0);
assert_eq!(LLVM_NATIVE_ABI_VERSION_PATCH, 0);
assert!(LLVM_NATIVE_MAX_PATH > 0);
assert!(LLVM_NATIVE_MAX_OPTIONS > 0);
assert!(LLVM_NATIVE_MAX_ERROR > 0);
}
#[test]
fn test_version_string_format() {
let ver = crate_version_string();
assert!(ver.starts_with("llvm-native "));
assert!(ver.contains("ABI v"));
}
#[test]
fn test_compile_flags_size() {
assert_eq!(std::mem::size_of::<CompileFlags>(), 8);
}
#[test]
fn test_compile_options_ffi_layout() {
let opts = CompileOptionsFFI {
standard: CLangStandardFFI::C17,
flags: CompileFlags::none(),
target_triple: std::ptr::null(),
output_file: std::ptr::null(),
include_paths: std::ptr::null(),
defines: std::ptr::null(),
opt_level_override: 0xFF,
_reserved: [0; 4],
};
let base = &opts as *const _ as *const u8;
let offset_standard = &opts.standard as *const _ as usize - base as usize;
let offset_flags = &opts.flags as *const _ as usize - base as usize;
let offset_target = &opts.target_triple as *const _ as usize - base as usize;
let offset_output = &opts.output_file as *const _ as usize - base as usize;
assert_eq!(offset_standard, 0);
assert!(offset_flags >= 4);
assert!(offset_target >= offset_flags + 8);
assert_eq!(offset_target % std::mem::size_of::<*const u8>(), 0);
assert_eq!(offset_output % std::mem::size_of::<*const u8>(), 0);
}
#[test]
fn test_compile_result_layout() {
assert_eq!(std::mem::size_of::<CompileResult>(), 40);
let r = CompileResult::success();
assert_eq!(r.status, 0);
assert_eq!(r.error_count, 0);
}
#[test]
fn test_version_info_ffi_layout() {
let vi = VersionInfoFFI::current();
assert_eq!(vi.major, 1);
assert_eq!(vi.minor, 0);
assert_eq!(vi.patch, 0);
assert_eq!(vi.abi_version, 1);
assert_eq!(std::mem::size_of::<VersionInfoFFI>(), 48);
}
#[test]
fn test_compile_flags_none() {
let f = CompileFlags::none();
assert_eq!(f.bits, 0);
assert!(!f.has(compile_flag_bits::OPTIMIZE));
assert!(!f.has(compile_flag_bits::DEBUG_INFO));
assert!(!f.has(compile_flag_bits::WALL));
}
#[test]
fn test_compile_flags_set_and_has() {
let mut f = CompileFlags::none();
assert!(!f.has(compile_flag_bits::OPTIMIZE));
f.set(compile_flag_bits::OPTIMIZE, true);
assert!(f.has(compile_flag_bits::OPTIMIZE));
f.set(compile_flag_bits::OPTIMIZE, false);
assert!(!f.has(compile_flag_bits::OPTIMIZE));
}
#[test]
fn test_compile_flags_merge() {
let f1 = CompileFlags::from_bit(compile_flag_bits::OPTIMIZE);
let f2 = CompileFlags::from_bit(compile_flag_bits::DEBUG_INFO);
let merged = f1.merge(&f2);
assert!(merged.has(compile_flag_bits::OPTIMIZE));
assert!(merged.has(compile_flag_bits::DEBUG_INFO));
}
#[test]
fn test_compile_flags_multiple_bits() {
let mut f = CompileFlags::none();
f.set(compile_flag_bits::WALL, true);
f.set(compile_flag_bits::WERROR, true);
f.set(compile_flag_bits::PEDANTIC, true);
assert!(f.has(compile_flag_bits::WALL));
assert!(f.has(compile_flag_bits::WERROR));
assert!(f.has(compile_flag_bits::PEDANTIC));
assert!(!f.has(compile_flag_bits::OPTIMIZE));
let expected = compile_flag_bits::WALL
| compile_flag_bits::WERROR
| compile_flag_bits::PEDANTIC;
assert_eq!(f.bits, expected);
}
#[test]
fn test_compile_flags_from_bit_const() {
let f = CompileFlags::from_bit(compile_flag_bits::LTO);
assert!(f.has(compile_flag_bits::LTO));
assert!(!f.has(compile_flag_bits::OPTIMIZE));
}
#[test]
fn test_compile_result_success() {
let r = CompileResult::success();
assert!(r.is_ok());
assert!(!r.is_err());
assert_eq!(r.status, 0);
assert_eq!(r.error_count, 0);
}
#[test]
fn test_compile_result_error() {
let r = CompileResult::error(3);
assert!(!r.is_ok());
assert!(r.is_err());
assert_eq!(r.status, -1);
assert_eq!(r.error_count, 3);
}
#[test]
fn test_standard_ffi_to_rust_full_roundtrip() {
let standards = [
(CLangStandardFFI::C89, CLangStandard::C89),
(CLangStandardFFI::C99, CLangStandard::C99),
(CLangStandardFFI::C11, CLangStandard::C11),
(CLangStandardFFI::C17, CLangStandard::C17),
(CLangStandardFFI::C23, CLangStandard::C23),
(CLangStandardFFI::Gnu89, CLangStandard::Gnu89),
(CLangStandardFFI::Gnu99, CLangStandard::Gnu99),
(CLangStandardFFI::Gnu11, CLangStandard::Gnu11),
(CLangStandardFFI::Gnu17, CLangStandard::Gnu17),
];
for (ffi, rust) in &standards {
assert_eq!(standard_ffi_to_rust(*ffi), *rust);
assert_eq!(standard_rust_to_ffi(*rust), *ffi);
}
}
#[test]
fn test_standard_ffi_to_rust_unknown_defaults_to_c17() {
let unknown = unsafe { std::mem::transmute::<i32, CLangStandardFFI>(42) };
assert_eq!(standard_ffi_to_rust(unknown), CLangStandard::C17);
}
#[test]
fn test_compile_options_ffi_to_rust_null() {
let opts = unsafe { compile_options_ffi_to_rust(std::ptr::null()) };
assert_eq!(opts.standard, CLangStandard::C17);
assert!(opts.optimize);
assert_eq!(opts.target_triple, "x86_64-unknown-linux-gnu");
}
#[test]
fn test_compile_options_ffi_to_rust_full() {
let target = CString::new("aarch64-unknown-linux-gnu").unwrap();
let output = CString::new("out.o").unwrap();
let includes = CString::new("/usr/include;/usr/local/include").unwrap();
let defines = CString::new("FOO=1;BAR").unwrap();
let mut flags = CompileFlags::none();
flags.set(compile_flag_bits::OPTIMIZE, true);
flags.set(compile_flag_bits::DEBUG_INFO, true);
flags.set(compile_flag_bits::WALL, true);
let ffi_opts = CompileOptionsFFI {
standard: CLangStandardFFI::C23,
flags,
target_triple: target.as_ptr(),
output_file: output.as_ptr(),
include_paths: includes.as_ptr(),
defines: defines.as_ptr(),
opt_level_override: 0xFF,
_reserved: [0; 4],
};
let rust_opts = unsafe { compile_options_ffi_to_rust(&ffi_opts as *const _) };
assert_eq!(rust_opts.standard, CLangStandard::C23);
assert!(rust_opts.optimize);
assert!(rust_opts.debug_info);
assert!(rust_opts.wall);
assert_eq!(rust_opts.target_triple, "aarch64-unknown-linux-gnu");
assert_eq!(rust_opts.output_file, Some("out.o".into()));
assert_eq!(rust_opts.includes.len(), 2);
assert_eq!(rust_opts.includes[0], "/usr/include");
assert_eq!(rust_opts.includes[1], "/usr/local/include");
assert_eq!(rust_opts.defines.len(), 2);
assert_eq!(rust_opts.defines[0], ("FOO".into(), Some("1".into())));
assert_eq!(rust_opts.defines[1], ("BAR".into(), None));
}
#[test]
fn test_compile_options_ffi_to_rust_optimization_override() {
let mut flags = CompileFlags::none();
flags.set(compile_flag_bits::OPTIMIZE, false);
let ffi_opts = CompileOptionsFFI {
standard: CLangStandardFFI::C17,
flags,
target_triple: std::ptr::null(),
output_file: std::ptr::null(),
include_paths: std::ptr::null(),
defines: std::ptr::null(),
opt_level_override: b'2',
_reserved: [0; 4],
};
let rust_opts = unsafe { compile_options_ffi_to_rust(&ffi_opts as *const _) };
assert!(rust_opts.optimize);
let ffi_opts_zero = CompileOptionsFFI {
opt_level_override: b'0',
..ffi_opts
};
let rust_opts_zero = unsafe { compile_options_ffi_to_rust(&ffi_opts_zero as *const _) };
assert!(!rust_opts_zero.optimize);
}
#[test]
fn test_validate_ffi_options_null_is_ok() {
assert!(validate_ffi_options(std::ptr::null()).is_ok());
}
#[test]
fn test_validate_ffi_options_valid() {
let opts = CompileOptionsFFI {
standard: CLangStandardFFI::C17,
flags: CompileFlags::none(),
target_triple: std::ptr::null(),
output_file: std::ptr::null(),
include_paths: std::ptr::null(),
defines: std::ptr::null(),
opt_level_override: 0xFF,
_reserved: [0; 4],
};
assert!(validate_ffi_options(&opts as *const _).is_ok());
}
#[test]
fn test_validate_ffi_options_rejects_nonzero_reserved() {
let opts = CompileOptionsFFI {
standard: CLangStandardFFI::C17,
flags: CompileFlags::none(),
target_triple: std::ptr::null(),
output_file: std::ptr::null(),
include_paths: std::ptr::null(),
defines: std::ptr::null(),
opt_level_override: 0xFF,
_reserved: [1, 0, 0, 0], };
assert!(validate_ffi_options(&opts as *const _).is_err());
}
#[test]
fn test_validate_ffi_options_valid_strings() {
let target = CString::new("x86_64-unknown-linux-gnu").unwrap();
let opts = CompileOptionsFFI {
standard: CLangStandardFFI::C17,
flags: CompileFlags::none(),
target_triple: target.as_ptr(),
output_file: std::ptr::null(),
include_paths: std::ptr::null(),
defines: std::ptr::null(),
opt_level_override: 0xFF,
_reserved: [0; 4],
};
assert!(validate_ffi_options(&opts as *const _).is_ok());
}
#[test]
fn test_llvm_native_version_not_null() {
let ptr = llvm_native_version();
assert!(!ptr.is_null());
let cstr = unsafe { CStr::from_ptr(ptr) };
let s = cstr.to_str().unwrap();
assert!(s.contains("llvm-native"));
assert!(s.contains("ABI"));
}
#[test]
fn test_llvm_native_get_error_initial_null() {
llvm_native_clear_error();
let ptr = llvm_native_get_error();
assert!(ptr.is_null());
}
#[test]
fn test_llvm_native_compile_file_null_path() {
llvm_native_clear_error();
let result = unsafe { llvm_native_compile_file(std::ptr::null(), std::ptr::null()) };
assert_eq!(result, -1);
let err_ptr = llvm_native_get_error();
assert!(!err_ptr.is_null());
let err_str = unsafe { CStr::from_ptr(err_ptr) }.to_str().unwrap();
assert!(err_str.contains("path is null"));
}
#[test]
fn test_llvm_native_compile_string_null_source() {
llvm_native_clear_error();
let result = unsafe { llvm_native_compile_string(std::ptr::null(), std::ptr::null()) };
assert_eq!(result, -1);
let err_ptr = llvm_native_get_error();
assert!(!err_ptr.is_null());
let err_str = unsafe { CStr::from_ptr(err_ptr) }.to_str().unwrap();
assert!(err_str.contains("source is null"));
}
#[test]
fn test_llvm_native_compile_string_basic() {
llvm_native_clear_error();
let source = CString::new("int main() { return 0; }").unwrap();
let result = unsafe { llvm_native_compile_string(source.as_ptr(), std::ptr::null()) };
if result != 0 {
let err_ptr = llvm_native_get_error();
assert!(!err_ptr.is_null());
let err_str = unsafe { CStr::from_ptr(err_ptr) }.to_str().unwrap();
assert!(
!err_str.contains("null"),
"Expected compile error, not ABI error: {}",
err_str
);
}
}
#[test]
fn test_llvm_native_compile_string_with_options() {
let source = CString::new("int main() { return 42; }").unwrap();
let mut flags = CompileFlags::none();
flags.set(compile_flag_bits::OPTIMIZE, true);
let opts = CompileOptionsFFI {
standard: CLangStandardFFI::C17,
flags,
target_triple: std::ptr::null(),
output_file: std::ptr::null(),
include_paths: std::ptr::null(),
defines: std::ptr::null(),
opt_level_override: 0xFF,
_reserved: [0; 4],
};
let result = unsafe { llvm_native_compile_string(source.as_ptr(), &opts as *const _) };
if result != 0 {
let err_ptr = llvm_native_get_error();
assert!(!err_ptr.is_null());
let err_str = unsafe { CStr::from_ptr(err_ptr) }.to_str().unwrap();
assert!(!err_str.contains("validate"),
"Expected compile error, not validation error: {}", err_str);
}
}
#[test]
fn test_llvm_native_validate_options_null() {
let result = unsafe { llvm_native_validate_options(std::ptr::null()) };
assert_eq!(result, 0);
}
#[test]
fn test_llvm_native_validate_options_valid() {
let opts = CompileOptionsFFI {
standard: CLangStandardFFI::C17,
flags: CompileFlags::none(),
target_triple: std::ptr::null(),
output_file: std::ptr::null(),
include_paths: std::ptr::null(),
defines: std::ptr::null(),
opt_level_override: 0xFF,
_reserved: [0; 4],
};
let result = unsafe { llvm_native_validate_options(&opts as *const _) };
assert_eq!(result, 0);
}
#[test]
fn test_llvm_native_validate_options_invalid_reserved() {
let opts = CompileOptionsFFI {
standard: CLangStandardFFI::C17,
flags: CompileFlags::none(),
target_triple: std::ptr::null(),
output_file: std::ptr::null(),
include_paths: std::ptr::null(),
defines: std::ptr::null(),
opt_level_override: 0xFF,
_reserved: [0xDEAD, 0, 0, 0],
};
let result = unsafe { llvm_native_validate_options(&opts as *const _) };
assert_eq!(result, -1);
let err_ptr = llvm_native_get_error();
assert!(!err_ptr.is_null());
let err_str = unsafe { CStr::from_ptr(err_ptr) }.to_str().unwrap();
assert!(err_str.contains("_reserved[0]"));
}
#[test]
fn test_llvm_native_clear_error() {
llvm_native_clear_error();
let result = unsafe { llvm_native_compile_string(std::ptr::null(), std::ptr::null()) };
assert_eq!(result, -1);
assert!(!llvm_native_get_error().is_null());
llvm_native_clear_error();
assert!(llvm_native_get_error().is_null());
}
#[test]
fn test_llvm_native_version_info() {
let mut vi = VersionInfoFFI {
major: 0,
minor: 0,
patch: 0,
abi_version: 0,
_reserved: [0; 4],
};
let result = llvm_native_version_info(&mut vi as *mut _);
assert_eq!(result, 0);
assert_eq!(vi.major, LLVM_NATIVE_ABI_VERSION_MAJOR);
assert_eq!(vi.minor, LLVM_NATIVE_ABI_VERSION_MINOR);
assert_eq!(vi.patch, LLVM_NATIVE_ABI_VERSION_PATCH);
assert_eq!(vi.abi_version, 1);
}
#[test]
fn test_llvm_native_version_info_null() {
let result = llvm_native_version_info(std::ptr::null_mut());
assert_eq!(result, -1);
}
#[test]
fn test_error_state_cleared_on_success() {
llvm_native_clear_error();
set_last_error("test error".into());
assert!(!llvm_native_get_error().is_null());
clear_last_error();
assert!(llvm_native_get_error().is_null());
}
#[test]
fn test_error_state_overwritable() {
llvm_native_clear_error();
set_last_error("first error".into());
let err1 = unsafe { CStr::from_ptr(llvm_native_get_error()) }
.to_str()
.unwrap()
.to_string();
assert_eq!(err1, "first error");
set_last_error("second error".into());
let err2 = unsafe { CStr::from_ptr(llvm_native_get_error()) }
.to_str()
.unwrap()
.to_string();
assert_eq!(err2, "second error");
}
#[test]
fn test_header_content_not_empty() {
assert!(!llvm_native_abi_header::HEADER_C.is_empty());
}
#[test]
fn test_header_has_include_guard() {
let header = llvm_native_abi_header::HEADER_C;
assert!(header.contains("LLVM_NATIVE_ABI_H"),
"Header should contain include guard");
}
#[test]
fn test_header_declares_all_functions() {
let header = llvm_native_abi_header::HEADER_C;
assert!(header.contains("llvm_native_compile_file"));
assert!(header.contains("llvm_native_compile_string"));
assert!(header.contains("llvm_native_get_error"));
assert!(header.contains("llvm_native_version"));
assert!(header.contains("llvm_native_clear_error"));
assert!(header.contains("llvm_native_version_info"));
assert!(header.contains("llvm_native_validate_options"));
}
#[test]
fn test_header_declares_data_structures() {
let header = llvm_native_abi_header::HEADER_C;
assert!(header.contains("llvm_native_compile_options_t"));
assert!(header.contains("llvm_native_compile_result_t"));
assert!(header.contains("llvm_native_version_info_t"));
assert!(header.contains("llvm_native_compile_flags_t"));
assert!(header.contains("llvm_native_standard_t"));
}
#[test]
fn test_header_has_cplusplus_guard() {
let header = llvm_native_abi_header::HEADER_C;
assert!(header.contains("extern \"C\""));
assert!(header.contains("__cplusplus"));
}
#[test]
fn test_header_has_stdint_include() {
let header = llvm_native_abi_header::HEADER_C;
assert!(header.contains("#include <stdint.h>"));
assert!(header.contains("#include <stddef.h>"));
}
#[test]
fn test_header_has_abi_version_constants() {
let header = llvm_native_abi_header::HEADER_C;
assert!(header.contains("LLVM_NATIVE_ABI_VERSION_MAJOR"));
assert!(header.contains("LLVM_NATIVE_ABI_VERSION_MINOR"));
assert!(header.contains("LLVM_NATIVE_ABI_VERSION_PATCH"));
}
#[test]
fn test_header_bytes_match_string() {
assert_eq!(
llvm_native_abi_header::header_bytes(),
llvm_native_abi_header::HEADER_C.as_bytes()
);
}
#[test]
fn test_header_len_positive() {
assert!(llvm_native_abi_header::header_len() > 0);
}
#[test]
fn test_write_header_tempfile() {
let dir = std::env::temp_dir();
let path = dir.join("llvm_native_test_header.h");
let result = llvm_native_abi_header::write_header(&path);
assert!(result.is_ok());
assert!(path.exists());
let _ = std::fs::remove_file(&path);
}
#[test]
fn test_header_contains_all_flag_defines() {
let header = llvm_native_abi_header::HEADER_C;
assert!(header.contains("LLVM_NATIVE_OPTIMIZE"));
assert!(header.contains("LLVM_NATIVE_DEBUG_INFO"));
assert!(header.contains("LLVM_NATIVE_WALL"));
assert!(header.contains("LLVM_NATIVE_WERROR"));
assert!(header.contains("LLVM_NATIVE_PEDANTIC"));
assert!(header.contains("LLVM_NATIVE_LTO"));
assert!(header.contains("LLVM_NATIVE_PIC"));
assert!(header.contains("LLVM_NATIVE_PIE"));
}
#[test]
fn test_abi_layout_constants_match() {
assert_eq!(
abi_layout::COMPILE_OPTIONS_FFI_SIZE,
std::mem::size_of::<CompileOptionsFFI>()
);
assert_eq!(
abi_layout::COMPILE_RESULT_SIZE,
std::mem::size_of::<CompileResult>()
);
assert_eq!(
abi_layout::VERSION_INFO_FFI_SIZE,
std::mem::size_of::<VersionInfoFFI>()
);
assert_eq!(
abi_layout::COMPILE_FLAGS_SIZE,
std::mem::size_of::<CompileFlags>()
);
}
#[test]
fn test_abi_alignments_reasonable() {
assert!(abi_layout::COMPILE_OPTIONS_FFI_ALIGN <= 16);
assert!(abi_layout::COMPILE_RESULT_ALIGN <= 16);
assert!(abi_layout::VERSION_INFO_FFI_ALIGN <= 16);
}
#[test]
fn test_options_roundtrip_all_fields() {
let target = CString::new("wasm32-unknown-unknown").unwrap();
let output = CString::new("test.wasm").unwrap();
let includes = CString::new("/sysroot/include").unwrap();
let defines = CString::new("__wasm__;WASI").unwrap();
let mut flags = CompileFlags::none();
flags.set(compile_flag_bits::OPTIMIZE_SIZE, true);
flags.set(compile_flag_bits::NO_WARNINGS, true);
flags.set(compile_flag_bits::PIC, true);
let ffi_opts = CompileOptionsFFI {
standard: CLangStandardFFI::C11,
flags,
target_triple: target.as_ptr(),
output_file: output.as_ptr(),
include_paths: includes.as_ptr(),
defines: defines.as_ptr(),
opt_level_override: b'z', _reserved: [0; 4],
};
let rust = unsafe { compile_options_ffi_to_rust(&ffi_opts as *const _) };
assert_eq!(rust.standard, CLangStandard::C11);
assert!(rust.optimize); assert!(!rust.warnings); assert_eq!(rust.target_triple, "wasm32-unknown-unknown");
assert_eq!(rust.output_file, Some("test.wasm".into()));
assert_eq!(rust.includes.len(), 1);
assert_eq!(rust.includes[0], "/sysroot/include");
assert_eq!(rust.defines.len(), 2);
}
#[test]
fn test_empty_source_string() {
llvm_native_clear_error();
let source = CString::new("").unwrap();
let result = unsafe { llvm_native_compile_string(source.as_ptr(), std::ptr::null()) };
if result == -1 {
let err_ptr = llvm_native_get_error();
assert!(!err_ptr.is_null());
}
}
#[test]
fn test_opt_level_override_values() {
for &level in &[b'0', b'1', b'2', b'3', b'z', b's', b'd', b'g', 0xFFu8] {
let ffi_opts = CompileOptionsFFI {
standard: CLangStandardFFI::C17,
flags: CompileFlags::none(),
target_triple: std::ptr::null(),
output_file: std::ptr::null(),
include_paths: std::ptr::null(),
defines: std::ptr::null(),
opt_level_override: level,
_reserved: [0; 4],
};
let rust = unsafe { compile_options_ffi_to_rust(&ffi_opts as *const _) };
match level {
b'0' | b'd' => assert!(!rust.optimize),
b'1' | b'2' | b'3' | b'g' | b's' | b'z' => assert!(rust.optimize),
0xFF => {}, _ => {},
}
}
}
}