1use std::collections::hash_map::DefaultHasher;
7use std::fs;
8use std::hash::{Hash, Hasher};
9use std::path::PathBuf;
10use std::process::Command;
11use std::time::Instant;
12
13use crate::graph::CellInfo;
14
15use super::errors::ErrorMapper;
16use super::toolchain::ToolchainManager;
17use super::types::{
18 CompilationResult, CompiledCell, CompilerConfig, dylib_extension, dylib_prefix,
19};
20
21pub struct CellCompiler {
23 config: CompilerConfig,
25
26 toolchain: ToolchainManager,
28
29 universe_path: Option<PathBuf>,
31}
32
33impl CellCompiler {
34 pub fn new(config: CompilerConfig, toolchain: ToolchainManager) -> Self {
36 Self {
37 config,
38 toolchain,
39 universe_path: None,
40 }
41 }
42
43 pub fn with_universe(mut self, path: PathBuf) -> Self {
45 self.universe_path = Some(path);
46 self
47 }
48
49 pub fn compile(&self, cell: &CellInfo, deps_hash: u64) -> CompilationResult {
51 let source_hash = self.hash_source(&cell.source_code);
52
53 if let Some(cached) = self.check_cache(cell, source_hash, deps_hash) {
55 return CompilationResult::Cached(cached);
56 }
57
58 let start = Instant::now();
59
60 let wrapper_code = self.generate_wrapper(cell);
62
63 match self.compile_to_dylib(cell, &wrapper_code, source_hash) {
65 Ok(dylib_path) => {
66 let compile_time = start.elapsed().as_millis() as u64;
67
68 let compiled = CompiledCell {
69 cell_id: cell.id,
70 name: cell.name.clone(),
71 dylib_path,
72 entry_symbol: format!("venus_cell_{}", cell.name),
73 source_hash,
74 deps_hash,
75 compile_time_ms: compile_time,
76 };
77
78 self.save_to_cache(&compiled);
80
81 CompilationResult::Success(compiled)
82 }
83 Err(errors) => CompilationResult::Failed {
84 cell_id: cell.id,
85 errors,
86 },
87 }
88 }
89
90 fn generate_wrapper(&self, cell: &CellInfo) -> String {
92 let mut code = String::new();
93
94 code.push_str("// Auto-generated cell wrapper\n");
96 code.push_str("#![allow(unused_imports)]\n");
97 code.push_str("#![allow(dead_code)]\n\n");
98
99 code.push_str("extern crate venus_universe;\n");
106 code.push_str("use venus_universe::*;\n\n");
107
108 code.push_str(&format!(
110 "// Original source: {}:{}\n",
111 cell.source_file.display(),
112 cell.span.start_line
113 ));
114
115 code.push_str(&cell.source_code);
117 code.push_str("\n\n");
118
119 code.push_str(&self.generate_ffi_entry(cell));
121
122 code
123 }
124
125 fn generate_ffi_entry(&self, cell: &CellInfo) -> String {
127 let mut code = String::new();
128
129 let fn_name = &cell.name;
130 let entry_name = format!("venus_cell_{}", fn_name);
131
132 let returns_result = cell.return_type.starts_with("Result<");
134
135 code.push_str("/// FFI entry point for the cell.\n");
136 code.push_str("/// \n");
137 code.push_str("/// # Safety\n");
138 code.push_str("/// This function is called from the Venus runtime.\n");
139 code.push_str("#[no_mangle]\n");
140 code.push_str(&format!("pub unsafe extern \"C\" fn {}(\n", entry_name));
141
142 for (i, dep) in cell.dependencies.iter().enumerate() {
144 code.push_str(&format!(" {}_ptr: *const u8,\n", dep.param_name));
145 code.push_str(&format!(" {}_len: usize,\n", dep.param_name));
146 if i < cell.dependencies.len() - 1 {
147 code.push('\n');
148 }
149 }
150
151 code.push_str(" widget_values_ptr: *const u8,\n");
153 code.push_str(" widget_values_len: usize,\n");
154
155 code.push_str(" out_ptr: *mut *mut u8,\n");
157 code.push_str(" out_len: *mut usize,\n");
158 code.push_str(") -> i32 {\n");
159
160 code.push_str(" // Set up widget context\n");
162 code.push_str(" use std::collections::HashMap;\n");
163 code.push_str(" let widget_values: HashMap<String, WidgetValue> = if widget_values_len > 0 {\n");
164 code.push_str(" let json_slice = std::slice::from_raw_parts(widget_values_ptr, widget_values_len);\n");
165 code.push_str(" venus_universe::serde_json::from_slice(json_slice).unwrap_or_default()\n");
166 code.push_str(" } else {\n");
167 code.push_str(" HashMap::new()\n");
168 code.push_str(" };\n");
169 code.push_str(" set_widget_context(WidgetContext::with_values(widget_values));\n\n");
170
171 for dep in &cell.dependencies {
173 let base_type = dep.param_type.trim_start_matches('&').trim();
175
176 code.push_str(&format!(
177 " let {}_bytes = std::slice::from_raw_parts({}_ptr, {}_len);\n",
178 dep.param_name, dep.param_name, dep.param_name
179 ));
180 code.push_str(&format!(
182 " let {}_archived = match rkyv::access::<rkyv::Archived<{}>, RkyvError>({}_bytes) {{\n",
183 dep.param_name, base_type, dep.param_name
184 ));
185 code.push_str(" Ok(v) => v,\n");
186 code.push_str(" Err(_) => return -1, // Access error\n");
187 code.push_str(" };\n");
188 code.push_str(&format!(
190 " let {}: {} = match rkyv::deserialize::<_, RkyvError>({}_archived) {{\n",
191 dep.param_name, base_type, dep.param_name
192 ));
193 code.push_str(" Ok(v) => v,\n");
194 code.push_str(" Err(_) => return -1, // Deserialization error\n");
195 code.push_str(" };\n\n");
196 }
197
198 let args: Vec<String> = cell
200 .dependencies
201 .iter()
202 .map(|d| {
203 if d.is_ref {
204 if d.is_mut {
205 format!("&mut {}", d.param_name)
206 } else {
207 format!("&{}", d.param_name)
208 }
209 } else {
210 d.param_name.clone()
211 }
212 })
213 .collect();
214
215 code.push_str(" // Wrap execution in catch_unwind for panic safety\n");
218 code.push_str(" let execution_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n");
219
220 if returns_result {
222 code.push_str(&format!(
223 " let result = match {}({}) {{\n",
224 fn_name,
225 args.join(", ")
226 ));
227 code.push_str(" Ok(v) => v,\n");
228 code.push_str(" Err(_) => return Err(-2i32), // Cell returned error\n");
229 code.push_str(" };\n\n");
230 } else {
231 code.push_str(&format!(
232 " let result = {}({});\n\n",
233 fn_name,
234 args.join(", ")
235 ));
236 }
237
238 code.push_str(" let display_str = format!(\"{:?}\", result);\n");
240 code.push_str(" let display_bytes = display_str.as_bytes();\n\n");
241
242 code.push_str(" let rkyv_data = match rkyv::to_bytes::<RkyvError>(&result) {\n");
244 code.push_str(" Ok(v) => v,\n");
245 code.push_str(" Err(_) => return Err(-3i32), // Serialization error\n");
246 code.push_str(" };\n\n");
247
248 code.push_str(" // Capture registered widgets\n");
250 code.push_str(" let widgets_json = if let Some(mut ctx) = take_widget_context() {\n");
251 code.push_str(" let widgets = ctx.take_widgets();\n");
252 code.push_str(" if widgets.is_empty() { Vec::new() } else { venus_universe::serde_json::to_vec(&widgets).unwrap_or_default() }\n");
253 code.push_str(" } else { Vec::new() };\n\n");
254
255 code.push_str(" let display_len = display_bytes.len() as u64;\n");
257 code.push_str(" let widgets_len = widgets_json.len() as u64;\n");
258 code.push_str(" let total_len = 8 + display_bytes.len() + 8 + widgets_json.len() + rkyv_data.len();\n");
259 code.push_str(" let mut output = Vec::with_capacity(total_len);\n");
260 code.push_str(" output.extend_from_slice(&display_len.to_le_bytes());\n");
261 code.push_str(" output.extend_from_slice(display_bytes);\n");
262 code.push_str(" output.extend_from_slice(&widgets_len.to_le_bytes());\n");
263 code.push_str(" output.extend_from_slice(&widgets_json);\n");
264 code.push_str(" output.extend_from_slice(&rkyv_data);\n\n");
265 code.push_str(" Ok(output)\n");
266 code.push_str(" }));\n\n");
267
268 code.push_str(" // Handle panic or success\n");
270 code.push_str(" match execution_result {\n");
271 code.push_str(" Ok(Ok(output)) => {\n");
272 code.push_str(" let len = output.len();\n");
273 code.push_str(" let ptr = output.as_ptr();\n");
274 code.push_str(" std::mem::forget(output);\n");
275 code.push_str(" *out_ptr = ptr as *mut u8;\n");
276 code.push_str(" *out_len = len;\n");
277 code.push_str(" 0 // Success\n");
278 code.push_str(" }\n");
279 code.push_str(" Ok(Err(code)) => code, // Cell error or serialization error\n");
280 code.push_str(" Err(_) => -4, // Panic occurred\n");
281 code.push_str(" }\n");
282 code.push_str("}\n");
283
284 code
285 }
286
287 fn compile_to_dylib(
289 &self,
290 cell: &CellInfo,
291 wrapper_code: &str,
292 source_hash: u64,
293 ) -> std::result::Result<PathBuf, Vec<super::CompileError>> {
294 let build_dir = self.config.cell_build_dir();
295 fs::create_dir_all(&build_dir).map_err(|e| {
296 super::CompileError::simple(format!("Failed to create build directory: {}", e))
297 })?;
298
299 let src_file = build_dir.join(format!("{}.rs", cell.name));
301 fs::write(&src_file, wrapper_code)
302 .map_err(|e| super::CompileError::simple(format!("Failed to write source: {}", e)))?;
303
304 let dylib_name = format!("{}cell_{}_{:x}.{}", dylib_prefix(), cell.name, source_hash, dylib_extension());
307 let dylib_path = build_dir.join(&dylib_name);
308
309 let cell_prefix = format!("{}cell_{}_", dylib_prefix(), cell.name);
311 if let Ok(entries) = fs::read_dir(&build_dir) {
312 for entry in entries.flatten() {
313 let name = entry.file_name();
314 let name_str = name.to_string_lossy();
315 if name_str.starts_with(&cell_prefix) && name_str != dylib_name {
316 let _ = fs::remove_file(entry.path());
317 }
318 }
319 }
320
321 let mut cmd = Command::new(self.toolchain.rustc_path());
323
324 cmd.arg(&src_file)
325 .arg("--crate-type=cdylib")
326 .arg("--edition=2021")
327 .arg("-o")
328 .arg(&dylib_path)
329 .arg("--error-format=json");
330
331 if self.config.use_cranelift && self.toolchain.has_cranelift() {
333 for flag in self.toolchain.cranelift_flags() {
334 cmd.arg(&flag);
335 }
336 }
337
338 cmd.arg(format!("-Copt-level={}", self.config.opt_level));
340
341 if self.config.debug_info {
343 cmd.arg("-g");
344 }
345
346 if let Some(universe_dylib) = &self.universe_path {
348 let universe_build_dir = universe_dylib.parent().unwrap_or(universe_dylib);
351 let target_release_dir = universe_build_dir.join("target").join("release");
352 let deps_dir = target_release_dir.join("deps");
353
354 cmd.arg("-L").arg(&target_release_dir);
356 cmd.arg("-L").arg(&deps_dir);
357
358 let rlib_path = target_release_dir.join("libvenus_universe.rlib");
360 if rlib_path.exists() {
361 cmd.arg("--extern").arg(format!("venus_universe={}", rlib_path.display()));
362 } else {
363 if let Ok(entries) = std::fs::read_dir(&deps_dir) {
365 for entry in entries.flatten() {
366 let name = entry.file_name();
367 let name_str = name.to_string_lossy();
368 if name_str.starts_with("libvenus_universe-") && name_str.ends_with(".rlib") {
369 cmd.arg("--extern").arg(format!("venus_universe={}", entry.path().display()));
370 break;
371 }
372 }
373 }
374 }
375
376 #[cfg(any(target_os = "linux", target_os = "macos"))]
378 {
379 cmd.arg(format!("-Clink-arg=-Wl,-rpath,{}", universe_build_dir.display()));
381 }
382 }
383
384 for flag in &self.config.extra_rustc_flags {
386 cmd.arg(flag);
387 }
388
389 let output = cmd
391 .output()
392 .map_err(|e| super::CompileError::simple(format!("Failed to run rustc: {}", e)))?;
393
394 if output.status.success() {
395 Ok(dylib_path)
396 } else {
397 let stderr = String::from_utf8_lossy(&output.stderr);
399 let mapper = ErrorMapper::new(cell.source_file.clone());
400 let errors = mapper.parse_rustc_output(&stderr);
401
402 if errors.is_empty() {
403 Err(super::CompileError::simple_rendered(stderr.to_string()))
405 } else {
406 Err(errors)
407 }
408 }
409 }
410
411 fn hash_source(&self, source: &str) -> u64 {
413 let mut hasher = DefaultHasher::new();
414 source.hash(&mut hasher);
415 hasher.finish()
416 }
417
418 fn check_cache(
420 &self,
421 cell: &CellInfo,
422 source_hash: u64,
423 deps_hash: u64,
424 ) -> Option<CompiledCell> {
425 let cache_file = self.cache_path(cell);
426 if !cache_file.exists() {
427 return None;
428 }
429
430 let meta_file = self.cache_meta_path(&cell.name);
432 if let Ok(meta) = fs::read_to_string(&meta_file) {
433 let lines: Vec<&str> = meta.lines().collect();
434 if lines.len() >= 2
435 && let (Ok(cached_src), Ok(cached_deps)) =
436 (lines[0].parse::<u64>(), lines[1].parse::<u64>())
437 && cached_src == source_hash
438 && cached_deps == deps_hash
439 {
440 return Some(CompiledCell {
441 cell_id: cell.id,
442 name: cell.name.clone(),
443 dylib_path: cache_file,
444 entry_symbol: format!("venus_cell_{}", cell.name),
445 source_hash,
446 deps_hash,
447 compile_time_ms: 0,
448 });
449 }
450 }
451
452 None
453 }
454
455 fn save_to_cache(&self, compiled: &CompiledCell) {
457 let meta_file = self.cache_meta_path(&compiled.name);
458
459 if let Some(parent) = meta_file.parent()
461 && let Err(e) = fs::create_dir_all(parent) {
462 tracing::warn!("Failed to create cache directory: {}", e);
463 return;
464 }
465
466 let meta = format!("{}\n{}", compiled.source_hash, compiled.deps_hash);
467 if let Err(e) = fs::write(&meta_file, meta) {
469 tracing::warn!("Failed to save cell cache: {}", e);
470 }
471 }
472
473 fn cache_path(&self, cell: &CellInfo) -> PathBuf {
475 let filename = format!("{}cell_{}.{}", dylib_prefix(), cell.name, dylib_extension());
476 self.config.cache_dir.join("cells").join(filename)
477 }
478
479 fn cache_meta_path(&self, name: &str) -> PathBuf {
481 self.config
482 .cache_dir
483 .join("cells")
484 .join(format!("{}.meta", name))
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491 use crate::graph::{CellId, Dependency, SourceSpan};
492
493 fn make_test_cell() -> CellInfo {
494 CellInfo {
495 id: CellId::new(0),
496 name: "test_cell".to_string(),
497 display_name: "test_cell".to_string(),
498 dependencies: vec![],
499 return_type: "i32".to_string(),
500 doc_comment: None,
501 source_code: "pub fn test_cell() -> i32 { 42 }".to_string(),
502 source_file: PathBuf::from("test.rs"),
503 span: SourceSpan {
504 start_line: 1,
505 start_col: 0,
506 end_line: 1,
507 end_col: 30,
508 },
509 }
510 }
511
512 #[test]
513 fn test_generate_wrapper_simple() {
514 let config = CompilerConfig::default();
515 let toolchain = ToolchainManager::new().unwrap();
516 let compiler = CellCompiler::new(config, toolchain);
517
518 let cell = make_test_cell();
519 let wrapper = compiler.generate_wrapper(&cell);
520
521 assert!(wrapper.contains("venus_cell_test_cell"));
522 assert!(wrapper.contains("pub fn test_cell() -> i32"));
523 assert!(wrapper.contains("#[no_mangle]"));
524 }
525
526 #[test]
527 fn test_generate_wrapper_with_deps() {
528 let config = CompilerConfig::default();
529 let toolchain = ToolchainManager::new().unwrap();
530 let compiler = CellCompiler::new(config, toolchain);
531
532 let cell = CellInfo {
533 id: CellId::new(1),
534 name: "process".to_string(),
535 display_name: "process".to_string(),
536 dependencies: vec![Dependency {
537 param_name: "config".to_string(),
538 param_type: "Config".to_string(),
539 is_ref: true,
540 is_mut: false,
541 }],
542 return_type: "Output".to_string(),
543 doc_comment: None,
544 source_code: "pub fn process(config: &Config) -> Output { todo!() }".to_string(),
545 source_file: PathBuf::from("test.rs"),
546 span: SourceSpan {
547 start_line: 5,
548 start_col: 0,
549 end_line: 5,
550 end_col: 50,
551 },
552 };
553
554 let wrapper = compiler.generate_wrapper(&cell);
555
556 assert!(wrapper.contains("config_ptr: *const u8"));
557 assert!(wrapper.contains("config_len: usize"));
558 assert!(wrapper.contains("rkyv::access"));
559 }
560
561 #[test]
562 fn test_hash_source() {
563 let config = CompilerConfig::default();
564 let toolchain = ToolchainManager::new().unwrap();
565 let compiler = CellCompiler::new(config, toolchain);
566
567 let hash1 = compiler.hash_source("fn foo() {}");
568 let hash2 = compiler.hash_source("fn foo() {}");
569 let hash3 = compiler.hash_source("fn bar() {}");
570
571 assert_eq!(hash1, hash2);
572 assert_ne!(hash1, hash3);
573 }
574}