llvm-native-core-ext 0.1.0

Extended modules for llvm-native-core: analysis passes, transforms, codegen extras, bitcode, linker, JIT, utilities. Part of the llvm-native workspace (https://crates.io/crates/llvm-native).
//! Self-compilation test: llvm-native compiles a C program that links
//! against llvm-native's own static library via the C ABI bridge.
//!
//! This proves: llvm-native can produce code that calls back into itself.

use std::ffi::CStr;
use std::path::Path;

/// The C test program. It calls llvm-native through the ABI bridge
/// to compile a tiny C program, proving self-interop.
pub const SELF_COMPILE_C: &str = r##"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Forward declarations of llvm-native ABI functions */
extern int llvm_native_compile_string(const char *source, const char *options);
extern const char *llvm_native_version(void);
extern const char *llvm_native_get_error(void);
extern void llvm_native_clear_error(void);

/* Simple test: compile a C string and verify it produced output */
int main(void) {
    const char *ver = llvm_native_version();
    if (ver == NULL || ver[0] == '\0') {
        fprintf(stderr, "FAIL: version string is empty\n");
        return 1;
    }
    printf("llvm-native version: %s\n", ver);

    /* Compile a tiny C program */
    const char *source = "int main(void) { return 42; }\n";
    int result = llvm_native_compile_string(source, "-O0");
    if (result != 0) {
        fprintf(stderr, "FAIL: compile_string returned %d\n", result);
        fprintf(stderr, "  error: %s\n", llvm_native_get_error());
        return 2;
    }

    printf("Self-compile test passed: version=%s, compile=OK\n", ver);
    return 42;
}
"##;

/// Build the self-compilation test:
/// 1. Write the ABI header to a temp dir
/// 2. Write the test C source
/// 3. Compile with llvm-native clang
/// 4. Link against llvm-native-core static library
/// 5. Run the resulting binary
pub fn build_and_run_self_compile() -> Result<(), String> {
    let tmp = std::env::temp_dir().join("llvm_native_self_compile");
    std::fs::create_dir_all(&tmp).map_err(|e| format!("mkdir: {}", e))?;

    // Write the C test source
    let c_path = tmp.join("self_compile_test.c");
    std::fs::write(&c_path, SELF_COMPILE_C).map_err(|e| format!("write c source: {}", e))?;

    // Write the ABI header
    let header_path = tmp.join("llvm_native.h");
    let header_content = llvm_native_core::clang::clang_abi_bridge::llvm_native_abi_header::HEADER_C;
    std::fs::write(&header_path, header_content).map_err(|e| format!("write header: {}", e))?;

    // Compile with llvm-native clang
    let obj_path = tmp.join("self_compile_test.o");
    let bin_path = tmp.join("self_compile_test");

    // Use the llvm-native CLI binary
    let cli_binary = if Path::new("target/release/llvm-native").exists() {
        "target/release/llvm-native"
    } else if Path::new("target/debug/llvm-native").exists() {
        "target/debug/llvm-native"
    } else {
        return Err("llvm-native CLI not found — build first".into());
    };

    // Step 1: Compile with llvm-native clang
    let status = std::process::Command::new(cli_binary)
        .args(&["clang", "-c", "-O0", "-o"])
        .arg(&obj_path)
        .arg(&c_path)
        .status()
        .map_err(|e| format!("clang execute: {}", e))?;

    if !status.success() {
        return Err(format!("clang compilation failed with status {:?}", status));
    }

    // Step 2: Link against llvm-native-core static library
    // Find the .a file
    let static_lib = if Path::new("target/release/libllvm_native_core.a").exists() {
        "target/release/libllvm_native_core.a"
    } else if Path::new("target/debug/libllvm_native_core.a").exists() {
        "target/debug/libllvm_native_core.a"
    } else {
        return Err("libllvm_native_core.a not found — rebuild with staticlib".into());
    };

    // Use GCC to link
    let link_status = std::process::Command::new("gcc")
        .args(&["-nostdlib", "-o"])
        .arg(&bin_path)
        .arg(&obj_path)
        .arg(static_lib)
        .args(&[
            "-lc",
            "-lpthread",
            "-ldl",
            "-lm",
            "-lrt",
            "-L/usr/lib",
            "-L/lib/x86_64-linux-gnu",
        ])
        .status()
        .map_err(|e| format!("link execute: {}", e))?;

    if !link_status.success() {
        return Err(format!("linking failed with status {:?}", link_status));
    }

    // Step 3: Run the resulting binary
    let output = std::process::Command::new(&bin_path)
        .output()
        .map_err(|e| format!("run execute: {}", e))?;

    if output.status.success() {
        let exit_code = output.status.code().unwrap_or(-1);
        let stdout = String::from_utf8_lossy(&output.stdout);
        println!("Self-compile test output: {}", stdout.trim());
        println!("Exit code: {}", exit_code);
        if exit_code == 42 {
            Ok(())
        } else {
            Err(format!("expected exit 42, got {}", exit_code))
        }
    } else {
        let stderr = String::from_utf8_lossy(&output.stderr);
        Err(format!("run failed: {}", stderr.trim()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_self_compile_abi_bridge_exists() {
        // Verify the ABI header content is non-empty
        let header = llvm_native_core::clang::clang_abi_bridge::llvm_native_abi_header::HEADER_C;
        assert!(
            header.contains("llvm_native_compile_string"),
            "HEADER_C should contain compile_string declaration"
        );
        assert!(
            header.contains("llvm_native_version"),
            "HEADER_C should contain version declaration"
        );
        println!("ABI header is {} bytes", header.len());
    }

    #[test]
    fn test_self_compile_source_valid() {
        // Verify the C test source compiles syntactically
        let source = SELF_COMPILE_C;
        assert!(source.contains("main(void)"));
        assert!(source.contains("llvm_native_version()"));
        assert!(source.contains("return 42"));
    }

    #[test]
    fn test_self_compile_cli_exists() {
        let cli = if Path::new("target/release/llvm-native").exists() {
            "target/release/llvm-native"
        } else if Path::new("target/debug/llvm-native").exists() {
            "target/debug/llvm-native"
        } else {
            return; // skip if not built
        };
        assert!(
            Path::new(cli).exists(),
            "CLI binary should exist at {}",
            cli
        );
    }

    #[test]
    fn test_self_compile_static_lib_exists() {
        let lib = if Path::new("target/release/libllvm_native_core.a").exists() {
            "target/release/libllvm_native_core.a"
        } else if Path::new("target/debug/libllvm_native_core.a").exists() {
            "target/debug/libllvm_native_core.a"
        } else {
            return; // skip if not built
        };
        assert!(
            Path::new(lib).exists(),
            "Static library should exist at {}",
            lib
        );
    }
}