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).
//! LLVM Split — splits an LLVM module into multiple smaller modules.
//! Clean-room behavioral reconstruction from the `llvm-split`
//! documentation.
//!
//! Supports three splitting strategies:
//! - Split by partition count: distribute functions evenly across N modules
//! - Split by function count: put at most N functions per module
//! - Split by size: keep module under a byte-size budget
//!
//! @llvm_behavior: `llvm-split` is an LLVM utility that splits a
//! bitcode module into multiple modules. It preserves all global
//! variables, type definitions, and metadata needed by each part.

use llvm_native_core::module::Module;
use llvm_native_core::value::ValueRef;
use std::collections::HashSet;

// ============================================================================
// LLVMSplit
// ============================================================================

/// LLVMSplit — partitions an LLVM module into multiple self-contained modules.
pub struct LLVMSplit;

impl LLVMSplit {
    // ========================================================================
    // Split by Partition Count
    // ========================================================================

    /// Split a module into `num_parts` roughly-equal partitions.
    ///
    /// Each output module contains a subset of the functions from the
    /// original, along with all referenced globals, types, and metadata
    /// needed to keep the module valid.
    pub fn split_module(module: &Module, num_parts: usize) -> Vec<Module> {
        if num_parts <= 1 || module.functions.is_empty() {
            return vec![module.clone()];
        }

        let num_funcs = module.functions.len();
        let part_size = (num_funcs + num_parts - 1) / num_parts; // ceil division

        let mut result = Vec::with_capacity(num_parts);
        for i in 0..num_parts {
            let start = i * part_size;
            let end = (start + part_size).min(num_funcs);
            if start >= num_funcs {
                break;
            }

            let subset: Vec<String> = module.functions[start..end]
                .iter()
                .map(|f| f.borrow().name.clone())
                .collect();

            let mut part = Module::new(&format!("{}_part{}", module.name, i));
            Self::copy_metadata(module, &mut part);
            Self::copy_types(module, &mut part);
            Self::copy_functions(module, &mut part, &subset);
            Self::copy_referenced_globals(module, &mut part);

            result.push(part);
        }

        result
    }

    // ========================================================================
    // Split by Function Count
    // ========================================================================

    /// Split a module so each output module has at most `parts` functions.
    pub fn split_functions(module: &Module, parts: usize) -> Vec<Module> {
        if parts == 0 || module.functions.is_empty() {
            return vec![module.clone()];
        }

        let num_funcs = module.functions.len();
        let num_parts = (num_funcs + parts - 1) / parts;

        Self::split_module(module, num_parts)
    }

    // ========================================================================
    // Split by Size Budget
    // ========================================================================

    /// Split a module so each output module's byte-size estimate is under
    /// `max_size`.
    ///
    /// Size is estimated by counting functions (1 unit per function).
    pub fn split_by_size(module: &Module, max_size: usize) -> Vec<Module> {
        if max_size == 0 || module.functions.is_empty() {
            return vec![module.clone()];
        }

        // Estimate function sizes: count 1 per function for simplicity.
        let func_names: Vec<String> = module
            .functions
            .iter()
            .map(|f| f.borrow().name.clone())
            .collect();

        let mut result = Vec::new();
        let mut part_index = 0;
        let mut current_size = 0usize;
        let mut current_funcs: Vec<String> = Vec::new();

        for name in &func_names {
            if current_size + 1 > max_size && !current_funcs.is_empty() {
                // Emit current partition.
                let mut part = Module::new(&format!("{}_size{}", module.name, part_index));
                Self::copy_metadata(module, &mut part);
                Self::copy_types(module, &mut part);
                Self::copy_functions(module, &mut part, &current_funcs);
                Self::copy_referenced_globals(module, &mut part);
                result.push(part);

                part_index += 1;
                current_size = 0;
                current_funcs.clear();
            }

            current_size += 1;
            current_funcs.push(name.clone());
        }

        // Emit the last partition.
        if !current_funcs.is_empty() {
            let mut part = Module::new(&format!("{}_size{}", module.name, part_index));
            Self::copy_metadata(module, &mut part);
            Self::copy_types(module, &mut part);
            Self::copy_functions(module, &mut part, &current_funcs);
            Self::copy_referenced_globals(module, &mut part);
            result.push(part);
        }

        result
    }

    // ========================================================================
    // Copy Helpers
    // ========================================================================

    /// Copy module-level metadata from src to dst.
    fn copy_metadata(src: &Module, dst: &mut Module) {
        dst.target_triple = src.target_triple.clone();
        dst.data_layout = src.data_layout.clone();
        dst.flags = src.flags.clone();
        dst.named_metadata = src.named_metadata.clone();
        dst.comdats = src.comdats.clone();
    }

    /// Copy all type definitions.
    fn copy_types(src: &Module, dst: &mut Module) {
        dst.types = src.types.clone();
        dst.named_types = src.named_types.clone();
    }

    /// Copy only the named functions into the destination module.
    fn copy_functions(src: &Module, dst: &mut Module, names: &[String]) {
        let name_set: HashSet<&str> = names.iter().map(|s| s.as_str()).collect();

        for func in &src.functions {
            let f_ref = func.borrow();
            if name_set.contains(f_ref.name.as_str()) {
                dst.functions.push(func.clone());
            }
        }
    }

    /// Copy global variables referenced by the copied functions.
    fn copy_referenced_globals(src: &Module, dst: &mut Module) {
        // Collect all global variable names referenced in the copied functions.
        let mut referenced: HashSet<String> = HashSet::new();

        for func in &dst.functions {
            Self::collect_global_refs(func, &mut referenced);
        }

        for g in &src.globals {
            let g_ref = g.borrow();
            if referenced.contains(&g_ref.name) {
                dst.globals.push(g.clone());
            }
        }
    }

    /// Recursively collect global variable references from a function.
    fn collect_global_refs(func: &ValueRef, out: &mut HashSet<String>) {
        let f_ref = func.borrow();
        // Walk operands of the function value.
        for operand in &f_ref.operands {
            let op_ref = operand.borrow();
            if op_ref.subclass == llvm_native_core::value::SubclassKind::GlobalVariable {
                out.insert(op_ref.name.clone());
            }
            // Recurse into instruction operands.
            Self::collect_global_refs(operand, out);
        }
    }
}

// ============================================================================
// Tests
// ============================================================================

#[cfg(test)]
mod tests {
    use super::*;
    use llvm_native_core::function::new_function;
    use llvm_native_core::types::Type;

    fn make_module(name: &str, num_funcs: usize) -> Module {
        let mut module = Module::new(name);
        for i in 0..num_funcs {
            let func = new_function(&format!("func_{}", i), Type::i32(), &[]);
            module.functions.push(func);
        }
        module
    }

    #[test]
    fn test_split_empty() {
        let module = Module::new("empty");
        let parts = LLVMSplit::split_module(&module, 3);
        assert_eq!(parts.len(), 1);
        assert_eq!(parts[0].name, "empty");
    }

    #[test]
    fn test_split_single_part() {
        let module = make_module("test", 5);
        let parts = LLVMSplit::split_module(&module, 1);
        assert_eq!(parts.len(), 1);
        assert_eq!(parts[0].functions.len(), 5);
    }

    #[test]
    fn test_split_even() {
        let module = make_module("test", 4);
        let parts = LLVMSplit::split_module(&module, 2);
        assert_eq!(parts.len(), 2);
        assert_eq!(parts[0].functions.len(), 2);
        assert_eq!(parts[1].functions.len(), 2);
    }

    #[test]
    fn test_split_uneven() {
        let module = make_module("test", 5);
        let parts = LLVMSplit::split_module(&module, 2);
        assert_eq!(parts.len(), 2);
        let total: usize = parts.iter().map(|p| p.functions.len()).sum();
        assert_eq!(total, 5);
    }

    #[test]
    fn test_split_by_functions() {
        let module = make_module("test", 10);
        let parts = LLVMSplit::split_functions(&module, 3);
        // 10 functions, 3 per part → 4 parts.
        assert_eq!(parts.len(), 4);
        let total: usize = parts.iter().map(|p| p.functions.len()).sum();
        assert_eq!(total, 10);
    }

    #[test]
    fn test_split_by_size_small_budget() {
        let module = make_module("test", 5);
        // Each function counts as 1 unit, budget 2 → 3 parts.
        let parts = LLVMSplit::split_by_size(&module, 2);
        assert_eq!(parts.len(), 3);
        let total: usize = parts.iter().map(|p| p.functions.len()).sum();
        assert_eq!(total, 5);
    }

    #[test]
    fn test_metadata_preserved() {
        let mut module = make_module("test", 2);
        module.target_triple = Some("x86_64-unknown-linux-gnu".to_string());
        let parts = LLVMSplit::split_module(&module, 2);
        for part in &parts {
            assert_eq!(
                part.target_triple,
                Some("x86_64-unknown-linux-gnu".to_string())
            );
        }
    }
}