1use crate::Result;
7use crate::dispatch::KernelCif;
8use tracing::debug;
9
10pub struct LlvmKernel {
12 _mmap: memmap2::MmapMut,
13 fn_ptr: *const (),
14 entry_point: String,
15 name: String,
16 var_names: Vec<String>,
17 cif: KernelCif,
18}
19
20unsafe impl Send for LlvmKernel {}
23unsafe impl Sync for LlvmKernel {}
24
25impl LlvmKernel {
26 pub fn compile_ir(
28 ir: &str,
29 entry_point: impl Into<String>,
30 name: impl Into<String>,
31 var_names: Vec<String>,
32 buf_count: usize,
33 ) -> Result<Self> {
34 let entry_point = entry_point.into();
35 let name = name.into();
36
37 debug!(kernel.name = %name, ir.length = ir.len(), "Compiling LLVM IR via external clang");
38
39 if let Ok(dir) = std::env::var("SVOD_DUMP_LLVM_IR") {
40 let path = std::path::Path::new(&dir).join(format!("{name}.ll"));
41 let _ = std::fs::create_dir_all(&dir);
42 let _ = std::fs::write(&path, ir);
43 }
44
45 if let Ok(dir) = std::env::var("SVOD_DUMP_POST_O2_IR") {
46 let _ = std::fs::create_dir_all(&dir);
50 if let Some(post_ir) = compile_ir_to_post_o2_text(ir) {
51 let path = std::path::Path::new(&dir).join(format!("{name}.post.ll"));
52 let _ = std::fs::write(&path, post_ir);
53 }
54 }
55
56 let obj = compile_ir_to_object(ir)?;
57 let (fn_ptr, mmap) = crate::jit_loader::jit_load(&obj, &entry_point)?;
58 let cif = KernelCif::new(buf_count + var_names.len());
59
60 debug!(kernel.name = %name, "LLVM kernel compiled and loaded");
61
62 Ok(Self { _mmap: mmap, fn_ptr, entry_point, name, var_names, cif })
63 }
64
65 pub fn compile(kernel: &svod_codegen::RenderedKernel) -> Result<Self> {
67 Self::compile_ir(&kernel.code, &kernel.name, &kernel.name, kernel.var_names.clone(), kernel.buffer_args.len())
68 }
69
70 pub fn var_names(&self) -> &[String] {
71 &self.var_names
72 }
73
74 pub fn fn_ptr(&self) -> *const () {
75 self.fn_ptr
76 }
77
78 pub fn name(&self) -> &str {
79 &self.name
80 }
81
82 pub unsafe fn execute_with_vals(&self, buffers: &[*mut u8], vals: &[i64]) -> Result<()> {
89 debug!(
90 kernel.entry_point = %self.entry_point,
91 kernel.num_buffers = buffers.len(),
92 kernel.num_vals = vals.len(),
93 "Executing LLVM kernel"
94 );
95
96 unsafe { self.cif.dispatch(self.fn_ptr, buffers, vals, None) };
97
98 Ok(())
99 }
100
101 pub(crate) fn cif(&self) -> &KernelCif {
102 &self.cif
103 }
104}
105
106fn compile_ir_to_object(ir: &str) -> Result<Vec<u8>> {
112 use std::io::Write;
113 use std::process::{Command, Stdio};
114
115 let target = crate::jit_loader::elf_target_triple();
116
117 let mut args = vec![
118 "-x",
119 "ir",
120 "-c",
121 "-O2",
122 "-march=native",
123 "-fPIC",
124 "-fno-math-errno",
125 "-fno-stack-protector",
126 "-funroll-loops",
127 "-fvectorize",
128 "-fslp-vectorize",
129 ];
130 args.push(&target);
131 args.extend_from_slice(crate::jit_loader::platform_clang_flags());
132 args.extend_from_slice(&["-", "-o", "-"]);
133
134 let mut child = Command::new("clang")
135 .args(&args)
136 .stdin(Stdio::piped())
137 .stdout(Stdio::piped())
138 .stderr(Stdio::piped())
139 .spawn()
140 .map_err(|e| crate::Error::JitCompilation {
141 reason: format!("Failed to spawn clang: {e}. Is clang installed?"),
142 })?;
143
144 child
145 .stdin
146 .take()
147 .expect("stdin was piped")
148 .write_all(ir.as_bytes())
149 .map_err(|e| crate::Error::JitCompilation { reason: format!("Failed to write IR to clang stdin: {e}") })?;
150
151 let output = child
152 .wait_with_output()
153 .map_err(|e| crate::Error::JitCompilation { reason: format!("Failed to wait for clang: {e}") })?;
154
155 if !output.status.success() {
156 let stderr = String::from_utf8_lossy(&output.stderr);
157 return Err(crate::Error::JitCompilation { reason: format!("clang IR compilation failed:\n{stderr}") });
158 }
159
160 if output.stdout.is_empty() {
161 return Err(crate::Error::JitCompilation { reason: "clang produced empty output from IR".to_string() });
162 }
163
164 Ok(output.stdout)
165}
166
167fn compile_ir_to_post_o2_text(ir: &str) -> Option<String> {
171 use std::io::Write;
172 use std::process::{Command, Stdio};
173
174 let mut args = vec![
175 "-x",
176 "ir",
177 "-S",
178 "-emit-llvm",
179 "-O2",
180 "-march=native",
181 "-fno-math-errno",
182 "-funroll-loops",
183 "-fvectorize",
184 "-fslp-vectorize",
185 ];
186 args.extend_from_slice(crate::jit_loader::platform_clang_flags());
187 args.extend_from_slice(&["-", "-o", "-"]);
188
189 let mut child = Command::new("clang")
190 .args(&args)
191 .stdin(Stdio::piped())
192 .stdout(Stdio::piped())
193 .stderr(Stdio::piped())
194 .spawn()
195 .ok()?;
196 child.stdin.take()?.write_all(ir.as_bytes()).ok()?;
197 let output = child.wait_with_output().ok()?;
198 if !output.status.success() {
199 return None;
200 }
201 String::from_utf8(output.stdout).ok()
202}
203
204#[cfg(test)]
205#[path = "test/unit/llvm.rs"]
206mod tests;