Skip to main content

llvm_bitcode/
lib.rs

1//! LLVM-in-Rust IR binary format (LRIR) reader and writer.
2//!
3//! This crate implements a compact binary serialization format for
4//! `(Context, Module)` pairs, enabling round-trip fidelity without
5//! depending on the full LLVM bitcode bitstream format.
6
7pub mod error;
8/// Public API for `reader`.
9pub mod reader;
10/// Public API for `writer`.
11pub mod writer;
12
13/// Public API for `re-export`.
14pub use error::BitcodeError;
15/// Public API for `re-export`.
16pub use reader::read_bitcode;
17/// Public API for `re-export`.
18pub use writer::write_bitcode;
19
20// ── tests ──────────────────────────────────────────────────────────────────
21
22#[cfg(test)]
23mod tests {
24    use super::*;
25    use llvm_ir::{Builder, Context, Linkage, Module};
26
27    fn make_empty_module() -> (Context, Module) {
28        let ctx = Context::new();
29        let module = Module::new("empty");
30        (ctx, module)
31    }
32
33    fn make_add_fn() -> (Context, Module) {
34        let mut ctx = Context::new();
35        let mut module = Module::new("test");
36        let mut b = Builder::new(&mut ctx, &mut module);
37        b.add_function(
38            "add",
39            b.ctx.i64_ty,
40            vec![b.ctx.i64_ty, b.ctx.i64_ty],
41            vec!["a".into(), "b".into()],
42            false,
43            Linkage::External,
44        );
45        let entry = b.add_block("entry");
46        b.position_at_end(entry);
47        let a = b.get_arg(0);
48        let bv = b.get_arg(1);
49        let sum = b.build_add("sum", a, bv);
50        b.build_ret(sum);
51        (ctx, module)
52    }
53
54    #[test]
55    fn write_then_read_empty_module() {
56        let (ctx, module) = make_empty_module();
57        let bytes = write_bitcode(&ctx, &module);
58        let (ctx2, module2) = read_bitcode(&bytes).expect("round-trip must succeed");
59        assert_eq!(module2.name, "empty");
60        assert_eq!(module2.functions.len(), 0);
61        // Context must have at minimum the built-in types.
62        assert!(ctx2.num_types() > 0);
63    }
64
65    #[test]
66    fn write_then_read_simple_function() {
67        let (ctx, module) = make_add_fn();
68        let bytes = write_bitcode(&ctx, &module);
69        let (_, module2) = read_bitcode(&bytes).expect("round-trip must succeed");
70        assert_eq!(module2.functions.len(), 1);
71        let func = &module2.functions[0];
72        // The function must have at least one block containing at least one instruction.
73        assert!(!func.blocks.is_empty());
74        assert!(!func.instructions.is_empty());
75    }
76
77    #[test]
78    fn write_then_read_preserves_freeze_instruction() {
79        let mut ctx = Context::new();
80        let mut module = Module::new("freeze");
81        let mut b = Builder::new(&mut ctx, &mut module);
82        b.add_function(
83            "freeze_id",
84            b.ctx.i32_ty,
85            vec![b.ctx.i32_ty],
86            vec!["x".into()],
87            false,
88            Linkage::External,
89        );
90        let entry = b.add_block("entry");
91        b.position_at_end(entry);
92        let x = b.get_arg(0);
93        let y = b.build_freeze("y", x);
94        b.build_ret(y);
95
96        let bytes = write_bitcode(&ctx, &module);
97        let (_, module2) = read_bitcode(&bytes).expect("round-trip must succeed");
98        let func = &module2.functions[0];
99        let iid = func.blocks[0].body[0];
100        assert_eq!(func.instr(iid).kind.opcode(), "freeze");
101    }
102
103    #[test]
104    fn write_then_read_preserves_function_names() {
105        let (ctx, module) = make_add_fn();
106        let bytes = write_bitcode(&ctx, &module);
107        let (_, module2) = read_bitcode(&bytes).expect("round-trip must succeed");
108        assert_eq!(module2.functions[0].name, "add");
109    }
110
111    #[test]
112    fn write_then_read_multiple_functions() {
113        let mut ctx = Context::new();
114        let mut module = Module::new("multi");
115
116        // Function 1: add.
117        let mut b = Builder::new(&mut ctx, &mut module);
118        b.add_function(
119            "add",
120            b.ctx.i64_ty,
121            vec![b.ctx.i64_ty, b.ctx.i64_ty],
122            vec!["x".into(), "y".into()],
123            false,
124            Linkage::External,
125        );
126        let entry1 = b.add_block("entry");
127        b.position_at_end(entry1);
128        let x = b.get_arg(0);
129        let y = b.get_arg(1);
130        let sum = b.build_add("sum", x, y);
131        b.build_ret(sum);
132
133        // Function 2: sub.
134        b.add_function(
135            "sub",
136            b.ctx.i64_ty,
137            vec![b.ctx.i64_ty, b.ctx.i64_ty],
138            vec!["a".into(), "b".into()],
139            false,
140            Linkage::External,
141        );
142        let entry2 = b.add_block("entry");
143        b.position_at_end(entry2);
144        let a = b.get_arg(0);
145        let bv = b.get_arg(1);
146        let diff = b.build_sub("diff", a, bv);
147        b.build_ret(diff);
148
149        let bytes = write_bitcode(&ctx, &module);
150        let (_, module2) = read_bitcode(&bytes).expect("round-trip must succeed");
151
152        assert_eq!(module2.functions.len(), 2);
153        assert_eq!(module2.functions[0].name, "add");
154        assert_eq!(module2.functions[1].name, "sub");
155    }
156
157    #[test]
158    fn metadata_type_round_trips_as_metadata_not_label() {
159        // A Context that contains a Metadata type must deserialise back as
160        // Metadata, not as Label (which was the previous incorrect fallback).
161        use llvm_ir::TypeData;
162        let mut ctx = Context::new();
163        let meta_ty = ctx.mk_metadata();
164        let module = Module::new("meta_test");
165        let bytes = write_bitcode(&ctx, &module);
166        let (ctx2, _) = read_bitcode(&bytes).expect("round-trip must succeed");
167        // The serialised type at the same index must decode as Metadata.
168        let td = ctx2.get_type(meta_ty);
169        assert_eq!(
170            td,
171            &TypeData::Metadata,
172            "Metadata type must round-trip as TypeData::Metadata, not Label"
173        );
174    }
175
176    #[test]
177    fn invalid_magic_returns_error() {
178        let bad = b"BAAD\x01\x00\x00\x00\x00\x00\x00\x00";
179        let result = read_bitcode(bad);
180        assert!(result.is_err(), "invalid magic must return an error");
181        if let Err(BitcodeError::InvalidMagic) = result { /* ok */
182        } else {
183            panic!("expected InvalidMagic error");
184        }
185    }
186}