autozig_engine/
zig_compiler.rs

1//! Zig compiler wrapper with target support
2
3use std::{
4    path::Path,
5    process::Command,
6};
7
8use anyhow::{
9    Context,
10    Result,
11};
12
13/// Wrapper for invoking the Zig compiler
14pub struct ZigCompiler {
15    zig_path: String,
16}
17
18impl ZigCompiler {
19    /// Create a new Zig compiler wrapper
20    pub fn new() -> Self {
21        // Check for ZIG_PATH environment variable, otherwise use "zig"
22        let zig_path = std::env::var("ZIG_PATH").unwrap_or_else(|_| "zig".to_string());
23        Self { zig_path }
24    }
25
26    /// Check Zig compiler version
27    pub fn check_version(&self) -> Result<String> {
28        let output = Command::new(&self.zig_path)
29            .arg("version")
30            .output()
31            .context("Failed to execute zig version command")?;
32
33        if !output.status.success() {
34            anyhow::bail!("Zig compiler not found or failed to run");
35        }
36
37        let version = String::from_utf8_lossy(&output.stdout).trim().to_string();
38        Ok(version)
39    }
40
41    /// Compile Zig source to static library with target support
42    ///
43    /// # Arguments
44    /// * `source` - Path to .zig source file
45    /// * `output_lib` - Path for output static library (.a)
46    /// * `target` - Target triple (e.g., "x86_64-linux-gnu", "native",
47    ///   "wasm32-freestanding")
48    pub fn compile_with_target(
49        &self,
50        source: &Path,
51        output_lib: &Path,
52        target: &str,
53    ) -> Result<()> {
54        println!("cargo:warning=Compiling Zig code: {} for target: {}", source.display(), target);
55
56        // 查找同目录下的所有 C 源文件
57        let c_sources = self.find_c_sources(source)?;
58
59        if !c_sources.is_empty() {
60            println!(
61                "cargo:warning=Found {} C source file(s) to compile with Zig",
62                c_sources.len()
63            );
64            for c_file in &c_sources {
65                println!("cargo:warning=  - {}", c_file.display());
66            }
67        }
68
69        // 检测是否为 WASM 目标
70        let is_wasm = target.contains("wasm32");
71
72        // zig build-lib source.zig -static -femit-bin=output.a -target <target>
73        let mut cmd = Command::new(&self.zig_path);
74        cmd.arg("build-lib")
75            .arg(source)
76            .arg("-static")
77            .arg(format!("-femit-bin={}", output_lib.display()))
78            .arg("-target")
79            .arg(target);
80
81        if is_wasm {
82            // WASM 特殊配置
83            println!("cargo:warning=Detected WASM target, applying WASM-specific flags");
84
85            // WASM 不需要栈保护(没有 OS 支持)
86            cmd.arg("-fno-stack-protector");
87
88            // 🚀 启用 WASM SIMD128 支持(关键性能优化!)
89            // 这将允许使用 v128.load, v128.sub, v128.store 等 SIMD 指令
90            cmd.arg("-mcpu=mvp+simd128");
91
92            // WASM 优化:改用 ReleaseFast 以获得最佳性能
93            // (ReleaseSmall 会禁用某些 SIMD 优化)
94            cmd.arg("-O").arg("ReleaseFast");
95
96            // 不链接 libc(freestanding 环境)
97            // WASM 环境下没有标准的 libc
98        } else {
99            // 非 WASM 目标的标准配置
100            // Generate Position Independent Code (required for PIE executables)
101            cmd.arg("-fPIC");
102
103            // Link with libc (required for c_allocator and other libc functions)
104            cmd.arg("-lc");
105
106            // Optimize for release builds
107            cmd.arg("-O").arg("ReleaseFast");
108        }
109
110        // 添加所有 C 源文件到编译命令(WASM 也支持 C 文件)
111        for c_file in &c_sources {
112            cmd.arg(c_file);
113        }
114
115        let status = cmd.status().context("Failed to execute zig build-lib")?;
116
117        if !status.success() {
118            anyhow::bail!("Zig compilation failed");
119        }
120
121        println!("cargo:warning=Zig compilation successful");
122        println!("cargo:warning=Library: {}", output_lib.display());
123
124        Ok(())
125    }
126
127    /// Compile with target and search for C sources in the provided src
128    /// directory
129    ///
130    /// # Arguments
131    /// * `source` - Path to .zig source file (usually in OUT_DIR)
132    /// * `output_lib` - Path for output static library (.a)
133    /// * `target` - Target triple (e.g., "x86_64-linux-gnu", "native",
134    ///   "wasm32-freestanding")
135    /// * `src_dir` - Original source directory to search for C files
136    pub fn compile_with_target_and_src(
137        &self,
138        source: &Path,
139        output_lib: &Path,
140        target: &str,
141        src_dir: &Path,
142    ) -> Result<()> {
143        println!("cargo:warning=Compiling Zig code: {} for target: {}", source.display(), target);
144
145        // 在原始源码目录查找 C 源文件
146        let c_sources = self.find_c_sources_in_dir(src_dir)?;
147
148        if !c_sources.is_empty() {
149            println!(
150                "cargo:warning=Found {} C source file(s) to compile with Zig",
151                c_sources.len()
152            );
153            for c_file in &c_sources {
154                println!("cargo:warning=  - {}", c_file.display());
155            }
156        }
157
158        // 检测是否为 WASM 目标
159        let is_wasm = target.contains("wasm32");
160
161        let mut cmd = Command::new(&self.zig_path);
162        cmd.arg("build-lib")
163            .arg(source)
164            .arg("-static")
165            .arg(format!("-femit-bin={}", output_lib.display()))
166            .arg("-target")
167            .arg(target);
168
169        if is_wasm {
170            // WASM 特殊配置
171            cmd.arg("-fno-stack-protector")
172                // 🚀 启用 WASM SIMD128 支持
173                .arg("-mcpu=mvp+simd128")
174                .arg("-O")
175                .arg("ReleaseFast");
176        } else {
177            // 非 WASM 目标的标准配置
178            cmd.arg("-fPIC").arg("-lc").arg("-O").arg("ReleaseFast");
179        }
180
181        // 添加所有 C 源文件到编译命令
182        for c_file in &c_sources {
183            cmd.arg(c_file);
184        }
185
186        let status = cmd.status().context("Failed to execute zig build-lib")?;
187
188        if !status.success() {
189            anyhow::bail!("Zig compilation failed");
190        }
191
192        println!("cargo:warning=Zig compilation successful");
193        println!("cargo:warning=Library: {}", output_lib.display());
194
195        Ok(())
196    }
197
198    /// Find all C source files in a specific directory
199    fn find_c_sources_in_dir(&self, dir: &Path) -> Result<Vec<std::path::PathBuf>> {
200        let mut c_sources = Vec::new();
201
202        if dir.exists() {
203            for entry in std::fs::read_dir(dir)? {
204                let entry = entry?;
205                let path = entry.path();
206
207                if path.is_file() {
208                    if let Some(ext) = path.extension() {
209                        if ext == "c" {
210                            c_sources.push(path);
211                        }
212                    }
213                }
214            }
215        }
216
217        Ok(c_sources)
218    }
219
220    /// Find all C source files in the same directory as the Zig source
221    fn find_c_sources(&self, zig_source: &Path) -> Result<Vec<std::path::PathBuf>> {
222        let mut c_sources = Vec::new();
223
224        if let Some(parent) = zig_source.parent() {
225            if parent.exists() {
226                for entry in std::fs::read_dir(parent)? {
227                    let entry = entry?;
228                    let path = entry.path();
229
230                    if path.is_file() {
231                        if let Some(ext) = path.extension() {
232                            if ext == "c" {
233                                c_sources.push(path);
234                            }
235                        }
236                    }
237                }
238            }
239        }
240
241        Ok(c_sources)
242    }
243
244    /// Compile with native target (convenience method)
245    pub fn compile(&self, source: &Path, output_lib: &Path) -> Result<()> {
246        self.compile_with_target(source, output_lib, "native")
247    }
248
249    /// Compile Zig tests to an executable
250    ///
251    /// # Arguments
252    /// * `source` - Path to .zig source file containing tests
253    /// * `output_exe` - Path for output test executable
254    /// * `target` - Target triple (e.g., "x86_64-linux-gnu", "native")
255    pub fn compile_tests(&self, source: &Path, output_exe: &Path, target: &str) -> Result<()> {
256        println!("cargo:warning=Compiling Zig tests: {} for target: {}", source.display(), target);
257
258        // zig test source.zig -femit-bin=output_exe -target <target>
259        let status = Command::new(&self.zig_path)
260            .arg("test")
261            .arg(source)
262            .arg(format!("-femit-bin={}", output_exe.display()))
263            .arg("-target")
264            .arg(target)
265            // Optimize for release builds
266            .arg("-O")
267            .arg("ReleaseFast")
268            .status()
269            .context("Failed to execute zig test")?;
270
271        if !status.success() {
272            anyhow::bail!("Zig test compilation failed");
273        }
274
275        println!("cargo:warning=Zig test compilation successful");
276        println!("cargo:warning=Test executable: {}", output_exe.display());
277
278        Ok(())
279    }
280
281    /// Run compiled Zig test executable
282    ///
283    /// # Arguments
284    /// * `test_exe` - Path to compiled test executable
285    pub fn run_test_executable(&self, test_exe: &Path) -> Result<String> {
286        let output = Command::new(test_exe)
287            .output()
288            .context(format!("Failed to execute test: {}", test_exe.display()))?;
289
290        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
291        let stderr = String::from_utf8_lossy(&output.stderr).to_string();
292
293        if !output.status.success() {
294            anyhow::bail!("Zig tests failed:\nStdout: {}\nStderr: {}", stdout, stderr);
295        }
296
297        Ok(format!("Stdout: {}\nStderr: {}", stdout, stderr))
298    }
299}
300
301impl Default for ZigCompiler {
302    fn default() -> Self {
303        Self::new()
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310
311    #[test]
312    fn test_compiler_creation() {
313        let compiler = ZigCompiler::new();
314        assert!(!compiler.zig_path.is_empty());
315    }
316
317    #[test]
318    #[ignore] // Only run if Zig is installed
319    fn test_check_version() {
320        let compiler = ZigCompiler::new();
321        let version = compiler.check_version();
322        if version.is_ok() {
323            println!("Zig version: {}", version.unwrap());
324        }
325    }
326}