diff --git a/Cargo.toml b/Cargo.toml
index 95af7d2d..531f8d0c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,7 +17,12 @@ utils = { path = "utils" }
lexer = { path = "front/lexer" }
parser = { path = "front/parser" }
error = { path = "front/error" }
-llvm = { path = "./llvm" }
+llvm = { path = "./llvm", default-features = false }
+
+[features]
+default = ["llvm-target-all"]
+llvm-target-all = ["llvm/llvm-target-all"]
+llvm-target-x86 = ["llvm/llvm-target-x86"]
[workspace]
members = [
diff --git a/README.md b/README.md
index 8b32eb43..497cbe95 100644
--- a/README.md
+++ b/README.md
@@ -101,22 +101,14 @@ Wave follows a tiered platform policy to set clear expectations for stability, C
- 🥉 Tier 3 · Experimental — OpenBSD
+ 🥉 Tier 3 · Experimental — OpenBSD, Windows (MinGW/GNU)
- - Compiler build/compile path prioritized
- - Minimal standard library coverage
+ - Cross-compilation from Linux supported
+ - Basic standard library coverage (via Wine/MinGW)
+ - Experimental support for native Windows binaries
-
- 🪦 Tier 4 · Unofficial — Windows
-
- - Build may work in some environments, but is not guaranteed
- - No official standard library target at this time
- - Community-maintained status
-
-
-
---
## CLI Usage
diff --git a/front/parser/src/import.rs b/front/parser/src/import.rs
index b8ca1445..6d9425ee 100644
--- a/front/parser/src/import.rs
+++ b/front/parser/src/import.rs
@@ -31,7 +31,7 @@ fn parse_target_os_attr(line: &str) -> Option> {
let start = "#[target(os=\"".len();
let end = trimmed.len() - 3; // ")]"
let os = &trimmed[start..end];
- if os == "linux" || os == "macos" {
+ if os == "linux" || os == "macos" || os == "windows" {
Some(TargetAttr::Supported(os))
} else {
Some(TargetAttr::Unsupported)
@@ -182,8 +182,8 @@ fn consume_target_item(lines: &[&str], mut idx: usize, keep: bool, out: &mut Vec
idx
}
-fn preprocess_target_attrs(source: &str) -> String {
- let host = std::env::consts::OS;
+fn preprocess_target_attrs(source: &str, target_os: Option<&str>) -> String {
+ let host = target_os.unwrap_or(std::env::consts::OS);
let lines: Vec<&str> = source.lines().collect();
let mut out: Vec = Vec::with_capacity(lines.len());
let mut idx: usize = 0;
@@ -257,6 +257,7 @@ pub struct ImportedUnit {
pub struct ImportConfig {
pub dep_roots: Vec,
pub dep_packages: HashMap,
+ pub target_os: Option,
}
pub fn local_import_unit(
@@ -284,7 +285,7 @@ pub fn local_import_unit_with_config(
}
if path.starts_with("std::") {
- return std_import_unit(path, already_imported);
+ return std_import_unit(path, already_imported, config);
}
if path.contains("::") {
@@ -308,7 +309,7 @@ pub fn local_import_unit_with_config(
));
}
- parse_wave_file(&found_path, &target_file_name, already_imported)
+ parse_wave_file(&found_path, &target_file_name, already_imported, config)
}
pub fn local_import(
@@ -462,7 +463,7 @@ fn external_import_unit(
for candidate in &candidates {
if candidate.exists() && candidate.is_file() {
- return parse_wave_file(candidate, path, already_imported);
+ return parse_wave_file(candidate, path, already_imported, config);
}
}
@@ -489,6 +490,7 @@ fn external_import_unit(
fn std_import_unit(
path: &str,
already_imported: &mut HashSet,
+ config: &ImportConfig,
) -> Result {
let rel = path.strip_prefix("std::").unwrap();
if rel.trim().is_empty() {
@@ -520,7 +522,7 @@ fn std_import_unit(
));
}
- parse_wave_file(&found_path, path, already_imported)
+ parse_wave_file(&found_path, path, already_imported, config)
}
fn std_root_dir(import_path: &str) -> Result {
@@ -541,6 +543,7 @@ fn parse_wave_file(
found_path: &Path,
display_name: &str,
already_imported: &mut HashSet,
+ config: &ImportConfig,
) -> Result {
let abs_path = found_path.canonicalize().map_err(|e| {
WaveError::new(
@@ -582,7 +585,7 @@ fn parse_wave_file(
0,
)
})?;
- let content = preprocess_target_attrs(&raw_content);
+ let content = preprocess_target_attrs(&raw_content, config.target_os.as_deref());
let mut lexer = Lexer::new_with_file(&content, abs_path.display().to_string());
let tokens = lexer.tokenize()?;
diff --git a/llvm/Cargo.toml b/llvm/Cargo.toml
index 9b1339ac..db4da564 100644
--- a/llvm/Cargo.toml
+++ b/llvm/Cargo.toml
@@ -7,5 +7,10 @@ edition = "2021"
parser = { path = "../front/parser" }
lexer = { path = "../front/lexer" }
error = { path = "../front/error" }
-inkwell = { version = "0.8.0", features = ["llvm21-1"] }
+inkwell = { version = "0.8.0", default-features = false, features = ["llvm21-1"] }
llvm-sys = { version = "211.0.0", features = ["prefer-dynamic"] }
+
+[features]
+default = ["llvm-target-all"]
+llvm-target-all = ["inkwell/target-all"]
+llvm-target-x86 = ["inkwell/target-x86"]
diff --git a/llvm/src/backend.rs b/llvm/src/backend.rs
index b14f9f72..9de75bb3 100644
--- a/llvm/src/backend.rs
+++ b/llvm/src/backend.rs
@@ -26,6 +26,14 @@ pub struct BackendOptions {
pub no_default_libs: bool,
}
+fn is_windows_gnu_target(target: Option<&str>) -> bool {
+ let Some(target) = target else {
+ return false;
+ };
+ let t = target.to_ascii_lowercase();
+ t.starts_with("x86_64-") && t.contains("windows") && !t.contains("msvc")
+}
+
fn normalize_clang_opt_flag(opt_flag: &str) -> &str {
match opt_flag {
// LLVM pass pipeline currently has no dedicated Ofast preset, so keep
@@ -131,7 +139,7 @@ pub fn link_objects(
cmd.arg("-o").arg(output);
- if !backend.no_default_libs {
+ if !backend.no_default_libs && !is_windows_gnu_target(backend.target.as_deref()) {
cmd.arg("-lc").arg("-lm");
}
diff --git a/llvm/src/codegen/abi_c.rs b/llvm/src/codegen/abi_c.rs
index a9cd7ef4..0e15250e 100644
--- a/llvm/src/codegen/abi_c.rs
+++ b/llvm/src/codegen/abi_c.rs
@@ -351,6 +351,55 @@ fn classify_ret_x86_64_sysv<'ctx>(
RetLowering::Direct(t)
}
+fn classify_param_x86_64_windows<'ctx>(
+ context: &'ctx Context,
+ td: &TargetData,
+ t: BasicTypeEnum<'ctx>,
+) -> ParamLowering<'ctx> {
+ let size = td.get_store_size(&t) as u64;
+
+ match t {
+ BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_) => match size {
+ 1 | 2 | 4 | 8 => ParamLowering::Direct(
+ context
+ .custom_width_int_type((size * 8) as u32)
+ .as_basic_type_enum(),
+ ),
+ _ => ParamLowering::ByVal {
+ ty: t.as_any_type_enum(),
+ align: td.get_abi_alignment(&t) as u32,
+ },
+ },
+ _ => ParamLowering::Direct(t),
+ }
+}
+
+fn classify_ret_x86_64_windows<'ctx>(
+ context: &'ctx Context,
+ td: &TargetData,
+ t: Option>,
+) -> RetLowering<'ctx> {
+ let Some(t) = t else {
+ return RetLowering::Void;
+ };
+ let size = td.get_store_size(&t) as u64;
+
+ match t {
+ BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_) => match size {
+ 1 | 2 | 4 | 8 => RetLowering::Direct(
+ context
+ .custom_width_int_type((size * 8) as u32)
+ .as_basic_type_enum(),
+ ),
+ _ => RetLowering::SRet {
+ ty: t.as_any_type_enum(),
+ align: td.get_abi_alignment(&t) as u32,
+ },
+ },
+ _ => RetLowering::Direct(t),
+ }
+}
+
fn classify_param_arm64_darwin<'ctx>(
td: &TargetData,
t: BasicTypeEnum<'ctx>,
@@ -448,6 +497,7 @@ fn classify_param<'ctx>(
CodegenTarget::LinuxX86_64
| CodegenTarget::DarwinX86_64
| CodegenTarget::FreestandingX86_64 => classify_param_x86_64_sysv(context, td, t),
+ CodegenTarget::WindowsX86_64Gnu => classify_param_x86_64_windows(context, td, t),
CodegenTarget::LinuxArm64
| CodegenTarget::DarwinArm64
| CodegenTarget::FreestandingArm64 => classify_param_arm64_darwin(td, t),
@@ -465,6 +515,7 @@ fn classify_ret<'ctx>(
CodegenTarget::LinuxX86_64
| CodegenTarget::DarwinX86_64
| CodegenTarget::FreestandingX86_64 => classify_ret_x86_64_sysv(context, td, t),
+ CodegenTarget::WindowsX86_64Gnu => classify_ret_x86_64_windows(context, td, t),
CodegenTarget::LinuxArm64
| CodegenTarget::DarwinArm64
| CodegenTarget::FreestandingArm64 => classify_ret_arm64_darwin(td, t),
diff --git a/llvm/src/codegen/ir.rs b/llvm/src/codegen/ir.rs
index 0d7ac1f6..256ee9d6 100644
--- a/llvm/src/codegen/ir.rs
+++ b/llvm/src/codegen/ir.rs
@@ -60,6 +60,20 @@ fn target_opt_level_from_flag(opt_flag: &str) -> OptimizationLevel {
}
}
+fn initialize_llvm_targets() {
+ let config = InitializationConfig::default();
+
+ #[cfg(feature = "llvm-target-all")]
+ {
+ Target::initialize_all(&config);
+ }
+
+ #[cfg(all(not(feature = "llvm-target-all"), feature = "llvm-target-x86"))]
+ {
+ Target::initialize_x86(&config);
+ }
+}
+
pub unsafe fn generate_ir(
ast_nodes: &[ASTNode],
opt_flag: &str,
@@ -75,7 +89,7 @@ pub unsafe fn generate_ir(
.map(|n| resolve_ast_node(n, &named_types))
.collect();
- Target::initialize_all(&InitializationConfig::default());
+ initialize_llvm_targets();
let triple = if let Some(raw) = &backend.target {
TargetTriple::create(raw)
} else {
diff --git a/llvm/src/codegen/plan.rs b/llvm/src/codegen/plan.rs
index 4e8c8e68..600011d7 100644
--- a/llvm/src/codegen/plan.rs
+++ b/llvm/src/codegen/plan.rs
@@ -182,6 +182,7 @@ fn parse_token(target: CodegenTarget, raw: &str) -> RegToken {
let phys_group = match target {
CodegenTarget::LinuxX86_64
| CodegenTarget::DarwinX86_64
+ | CodegenTarget::WindowsX86_64Gnu
| CodegenTarget::FreestandingX86_64 => {
reg_phys_group_x86_64(&raw_norm).map(|s| s.to_string())
}
@@ -216,6 +217,7 @@ fn build_default_clobbers(
let mut clobbers = match target {
CodegenTarget::LinuxX86_64
| CodegenTarget::DarwinX86_64
+ | CodegenTarget::WindowsX86_64Gnu
| CodegenTarget::FreestandingX86_64 => vec![
"~{memory}".to_string(),
"~{dirflag}".to_string(),
@@ -265,6 +267,7 @@ fn build_default_clobbers(
match target {
CodegenTarget::LinuxX86_64
| CodegenTarget::DarwinX86_64
+ | CodegenTarget::WindowsX86_64Gnu
| CodegenTarget::FreestandingX86_64 => {
const GPRS: [&str; 16] = [
"rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp", "r8", "r9", "r10",
@@ -346,6 +349,7 @@ fn normalize_special_clobber(target: CodegenTarget, token: &str) -> Option match token {
"memory" => Some("~{memory}".to_string()),
"cc" | "flags" | "eflags" | "rflags" => Some("~{flags}".to_string()),
diff --git a/llvm/src/codegen/target.rs b/llvm/src/codegen/target.rs
index c2249b79..3d7bd9bc 100644
--- a/llvm/src/codegen/target.rs
+++ b/llvm/src/codegen/target.rs
@@ -19,6 +19,7 @@ pub enum CodegenTarget {
LinuxArm64,
DarwinX86_64,
DarwinArm64,
+ WindowsX86_64Gnu,
FreestandingX86_64,
FreestandingArm64,
FreestandingRISCV64,
@@ -33,6 +34,7 @@ impl CodegenTarget {
let is_riscv64 = t.starts_with("riscv64");
let is_linux = t.contains("linux");
let is_darwin = t.contains("darwin");
+ let is_windows_gnu = t.contains("windows") && !t.contains("msvc");
let is_freestanding = t.contains("-none-") || t.ends_with("-none") || t.contains("elf");
if is_x86_64 && is_linux {
@@ -47,6 +49,9 @@ impl CodegenTarget {
if is_arm64 && is_darwin {
return Some(Self::DarwinArm64);
}
+ if is_x86_64 && is_windows_gnu {
+ return Some(Self::WindowsX86_64Gnu);
+ }
if is_x86_64 && is_freestanding {
return Some(Self::FreestandingX86_64);
}
@@ -76,6 +81,7 @@ impl CodegenTarget {
Self::LinuxArm64 => "linux arm64",
Self::DarwinX86_64 => "darwin x86_64",
Self::DarwinArm64 => "darwin arm64",
+ Self::WindowsX86_64Gnu => "windows x86_64 gnu",
Self::FreestandingX86_64 => "freestanding x86_64",
Self::FreestandingArm64 => "freestanding arm64",
Self::FreestandingRISCV64 => "freestanding riscv64",
@@ -90,7 +96,7 @@ pub fn require_supported_target_from_triple(triple: &TargetTriple) -> CodegenTar
let raw = triple.as_str().to_string_lossy();
panic!(
- "unsupported target triple '{}': Wave currently supports linux x86_64/arm64, darwin x86_64/arm64, and freestanding x86_64/arm64/riscv64",
+ "unsupported target triple '{}': Wave currently supports linux x86_64/arm64, darwin x86_64/arm64, windows x86_64 gnu, and freestanding x86_64/arm64/riscv64",
raw
);
}
@@ -103,7 +109,7 @@ pub fn require_supported_target_from_module(module: &Module<'_>) -> CodegenTarge
let triple = module.get_triple();
let raw = triple.as_str().to_string_lossy();
panic!(
- "unsupported target triple '{}': Wave currently supports linux x86_64/arm64, darwin x86_64/arm64, and freestanding x86_64/arm64/riscv64",
+ "unsupported target triple '{}': Wave currently supports linux x86_64/arm64, darwin x86_64/arm64, windows x86_64 gnu, and freestanding x86_64/arm64/riscv64",
raw
);
}
diff --git a/llvm/src/expression/rvalue/asm.rs b/llvm/src/expression/rvalue/asm.rs
index bb0369d6..0153ab8d 100644
--- a/llvm/src/expression/rvalue/asm.rs
+++ b/llvm/src/expression/rvalue/asm.rs
@@ -25,6 +25,7 @@ fn inline_asm_dialect_for_target(target: CodegenTarget) -> InlineAsmDialect {
match target {
CodegenTarget::LinuxX86_64
| CodegenTarget::DarwinX86_64
+ | CodegenTarget::WindowsX86_64Gnu
| CodegenTarget::FreestandingX86_64 => InlineAsmDialect::Intel,
CodegenTarget::LinuxArm64
| CodegenTarget::DarwinArm64
diff --git a/llvm/src/statement/asm.rs b/llvm/src/statement/asm.rs
index 1c227208..e880e1ef 100644
--- a/llvm/src/statement/asm.rs
+++ b/llvm/src/statement/asm.rs
@@ -67,6 +67,7 @@ fn reg_width_bits_for_target(target: CodegenTarget, reg: &str) -> Option {
match target {
CodegenTarget::LinuxX86_64
| CodegenTarget::DarwinX86_64
+ | CodegenTarget::WindowsX86_64Gnu
| CodegenTarget::FreestandingX86_64 => reg_width_bits(reg),
CodegenTarget::LinuxArm64
| CodegenTarget::DarwinArm64
@@ -158,6 +159,7 @@ fn inline_asm_dialect_for_target(target: CodegenTarget) -> InlineAsmDialect {
match target {
CodegenTarget::LinuxX86_64
| CodegenTarget::DarwinX86_64
+ | CodegenTarget::WindowsX86_64Gnu
| CodegenTarget::FreestandingX86_64 => InlineAsmDialect::Intel,
CodegenTarget::LinuxArm64
| CodegenTarget::DarwinArm64
diff --git a/src/cli.rs b/src/cli.rs
index 43156d2c..3e449641 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -224,7 +224,7 @@ fn dispatch_build(global: &Global, build: &BuildRequest) -> Result<(), CliError>
let classified = classify_inputs(build)?;
validate_build_request(&effective_global, build, &classified)?;
- let plan = create_build_plan(build, &classified)?;
+ let plan = create_build_plan(&effective_global, build, &classified)?;
if build.dry_run {
print_dry_run(&effective_global, build, &classified, &plan);
@@ -238,6 +238,7 @@ fn dispatch_build(global: &Global, build: &BuildRequest) -> Result<(), CliError>
&input.path,
&effective_global.debug,
&effective_global.dep,
+ &effective_global.llvm,
);
}
}
@@ -1460,6 +1461,7 @@ fn set_link_sysroot_arg(link_args: &mut Vec, value: &str) {
}
fn create_build_plan(
+ global: &Global,
build: &BuildRequest,
classified: &[ClassifiedInput],
) -> Result {
@@ -1517,7 +1519,7 @@ fn create_build_plan(
let primary = classified
.first()
.ok_or_else(|| CliError::usage("build requires at least one input"))?;
- plan.link_output = Some(resolve_binary_output_path(build, primary));
+ plan.link_output = Some(resolve_binary_output_path(global, build, primary));
}
Ok(plan)
@@ -1556,7 +1558,11 @@ fn resolve_object_output_path(
PathBuf::from("target").join(file_name)
}
-fn resolve_binary_output_path(build: &BuildRequest, primary: &ClassifiedInput) -> PathBuf {
+fn resolve_binary_output_path(
+ global: &Global,
+ build: &BuildRequest,
+ primary: &ClassifiedInput,
+) -> PathBuf {
if let Some(path) = &build.output {
return path.clone();
}
@@ -1566,8 +1572,12 @@ fn resolve_binary_output_path(build: &BuildRequest, primary: &ClassifiedInput) -
.file_stem()
.and_then(|s| s.to_str())
.filter(|s| !s.is_empty())
- .unwrap_or("a.out")
- .to_string();
+ .unwrap_or("a.out");
+ let stem = if is_windows_gnu_target_global(global) {
+ format!("{}.exe", stem)
+ } else {
+ stem.to_string()
+ };
if let Some(out_dir) = &build.out_dir {
return out_dir.join(&stem);
@@ -1710,7 +1720,12 @@ fn execute_explicit_emit_artifacts(
match kind {
EmitKind::Ast => {
let text = unsafe {
- runner::emit_wave_ast_text(&input.path, &global.debug, &global.dep)
+ runner::emit_wave_ast_text(
+ &input.path,
+ &global.debug,
+ &global.dep,
+ &global.llvm,
+ )
};
fs::write(output, text)?;
}
@@ -2022,7 +2037,7 @@ fn build_linker_args(
args.push("-o".to_string());
args.push(output.to_string_lossy().to_string());
- if !global.llvm.no_default_libs {
+ if !global.llvm.no_default_libs && !is_windows_gnu_target_global(global) {
args.push("-lc".to_string());
args.push("-lm".to_string());
}
@@ -2453,17 +2468,44 @@ fn host_target_triple() -> String {
}
fn supported_targets() -> &'static [&'static str] {
+ #[cfg(all(feature = "llvm-target-x86", not(feature = "llvm-target-all")))]
+ {
+ return &[
+ "x86_64-unknown-linux-gnu",
+ "x86_64-apple-darwin",
+ "x86_64-w64-windows-gnu",
+ "x86_64-pc-windows-gnu",
+ "x86_64-unknown-none-elf",
+ ];
+ }
+
+ #[cfg(not(all(feature = "llvm-target-x86", not(feature = "llvm-target-all"))))]
&[
"x86_64-unknown-linux-gnu",
"aarch64-unknown-linux-gnu",
"x86_64-apple-darwin",
"aarch64-apple-darwin",
+ "x86_64-w64-windows-gnu",
+ "x86_64-pc-windows-gnu",
"x86_64-unknown-none-elf",
"aarch64-unknown-none-elf",
"riscv64-unknown-none-elf",
]
}
+fn is_windows_gnu_target(target: &str) -> bool {
+ let t = target.to_ascii_lowercase();
+ t.starts_with("x86_64-") && t.contains("windows") && !t.contains("msvc")
+}
+
+fn is_windows_gnu_target_global(global: &Global) -> bool {
+ global
+ .llvm
+ .target
+ .as_deref()
+ .is_some_and(is_windows_gnu_target)
+}
+
fn ensure_supported_target(target: &str) -> Result<(), CliError> {
if target == host_target_triple() || supported_targets().iter().any(|t| *t == target) {
return Ok(());
diff --git a/src/runner.rs b/src/runner.rs
index 0e1d41f0..233f88c2 100644
--- a/src/runner.rs
+++ b/src/runner.rs
@@ -40,7 +40,7 @@ fn parse_target_os_attr(line: &str) -> Option> {
let start = "#[target(os=\"".len();
let end = trimmed.len() - 3; // ")]"
let os = &trimmed[start..end];
- if os == "linux" || os == "macos" {
+ if os == "linux" || os == "macos" || os == "windows" {
Some(TargetAttr::Supported(os))
} else {
Some(TargetAttr::Unsupported)
@@ -191,8 +191,27 @@ fn consume_target_item(lines: &[&str], mut idx: usize, keep: bool, out: &mut Vec
idx
}
-fn preprocess_target_attrs(source: &str) -> String {
- let host = std::env::consts::OS;
+fn target_os_from_triple(triple: &str) -> Option<&'static str> {
+ let lower = triple.to_ascii_lowercase();
+ if lower.contains("windows") {
+ Some("windows")
+ } else if lower.contains("darwin") || lower.contains("apple") {
+ Some("macos")
+ } else if lower.contains("linux") {
+ Some("linux")
+ } else {
+ None
+ }
+}
+
+fn target_os_for_llvm(llvm: Option<&LlvmFlags>) -> Option {
+ llvm.and_then(|opts| opts.target.as_deref())
+ .and_then(target_os_from_triple)
+ .map(str::to_string)
+}
+
+fn preprocess_target_attrs(source: &str, target_os: Option<&str>) -> String {
+ let host = target_os.unwrap_or(std::env::consts::OS);
let lines: Vec<&str> = source.lines().collect();
let mut out: Vec = Vec::with_capacity(lines.len());
let mut idx: usize = 0;
@@ -739,8 +758,9 @@ fn emit_codegen_panic_and_exit(
process::exit(1);
}
-fn build_import_config(dep: &DepFlags) -> ImportConfig {
+fn build_import_config(dep: &DepFlags, target_os: Option) -> ImportConfig {
let mut config = ImportConfig::default();
+ config.target_os = target_os;
for root in &dep.roots {
config.dep_roots.push(PathBuf::from(root));
@@ -956,6 +976,7 @@ fn frontend_prepare_wave_ast(
file_path: &Path,
debug: &DebugFlags,
dep: &DepFlags,
+ llvm: Option<&LlvmFlags>,
) -> (String, Vec) {
let raw_code = match fs::read_to_string(file_path) {
Ok(c) => c,
@@ -972,7 +993,8 @@ fn frontend_prepare_wave_ast(
process::exit(1);
}
};
- let code = preprocess_target_attrs(&raw_code);
+ let target_os = target_os_for_llvm(llvm);
+ let code = preprocess_target_attrs(&raw_code, target_os.as_deref());
let mut lexer = Lexer::new_with_file(&code, file_path.display().to_string());
let tokens = lexer.tokenize().unwrap_or_else(|e| {
@@ -993,7 +1015,7 @@ fn frontend_prepare_wave_ast(
println!("\n===== AST =====\n{:#?}", parsed_ast);
}
- let import_config = build_import_config(dep);
+ let import_config = build_import_config(dep, target_os);
let ast = match expand_imports_for_codegen(file_path, parsed_ast, &import_config) {
Ok(a) => a,
Err(e) => {
@@ -1026,16 +1048,22 @@ fn frontend_prepare_wave_ast(
(code, ast)
}
-pub(crate) unsafe fn check_wave_file(file_path: &Path, debug: &DebugFlags, dep: &DepFlags) {
- let _ = frontend_prepare_wave_ast(file_path, debug, dep);
+pub(crate) unsafe fn check_wave_file(
+ file_path: &Path,
+ debug: &DebugFlags,
+ dep: &DepFlags,
+ llvm: &LlvmFlags,
+) {
+ let _ = frontend_prepare_wave_ast(file_path, debug, dep, Some(llvm));
}
pub(crate) unsafe fn emit_wave_ast_text(
file_path: &Path,
debug: &DebugFlags,
dep: &DepFlags,
+ llvm: &LlvmFlags,
) -> String {
- let (_, ast) = frontend_prepare_wave_ast(file_path, debug, dep);
+ let (_, ast) = frontend_prepare_wave_ast(file_path, debug, dep, Some(llvm));
format!("{:#?}\n", ast)
}
@@ -1046,7 +1074,7 @@ pub(crate) unsafe fn emit_wave_ir_text(
dep: &DepFlags,
llvm: &LlvmFlags,
) -> String {
- let (code, ast) = frontend_prepare_wave_ast(file_path, debug, dep);
+ let (code, ast) = frontend_prepare_wave_ast(file_path, debug, dep, Some(llvm));
let backend_opts = build_backend_options(llvm);
let ir = match run_panic_guarded(|| unsafe { generate_ir(&ast, opt_flag, &backend_opts) }) {
@@ -1087,7 +1115,8 @@ pub(crate) unsafe fn run_wave_file(
process::exit(1);
}
};
- let code = preprocess_target_attrs(&raw_code);
+ let target_os = target_os_for_llvm(Some(llvm));
+ let code = preprocess_target_attrs(&raw_code, target_os.as_deref());
let mut lexer = Lexer::new_with_file(&code, file_path.display().to_string());
let tokens = lexer.tokenize().unwrap_or_else(|e| {
@@ -1108,7 +1137,7 @@ pub(crate) unsafe fn run_wave_file(
println!("\n===== AST =====\n{:#?}", ast);
}
- let import_config = build_import_config(dep);
+ let import_config = build_import_config(dep, target_os);
let ast = match expand_imports_for_codegen(file_path, ast, &import_config) {
Ok(a) => a,
@@ -1227,7 +1256,8 @@ pub(crate) unsafe fn object_build_wave_file(
.display();
process::exit(1);
});
- let code = preprocess_target_attrs(&raw_code);
+ let target_os = target_os_for_llvm(Some(llvm));
+ let code = preprocess_target_attrs(&raw_code, target_os.as_deref());
let mut lexer = Lexer::new_with_file(&code, file_path.display().to_string());
let tokens = lexer.tokenize().unwrap_or_else(|e| {
@@ -1248,7 +1278,7 @@ pub(crate) unsafe fn object_build_wave_file(
println!("\n===== AST =====\n{:#?}", ast);
}
- let import_config = build_import_config(dep);
+ let import_config = build_import_config(dep, target_os);
let ast = expand_imports_for_codegen(file_path, ast, &import_config).unwrap_or_else(|e| {
e.display();
diff --git a/x.py b/x.py
index 7df07d76..dcc70d0b 100644
--- a/x.py
+++ b/x.py
@@ -31,6 +31,14 @@
TARGET_DIR = ROOT / "target"
BINARY_NAME = "wavec"
NAME = "wave"
+WINDOWS_GNU_TARGET = "x86_64-pc-windows-gnu"
+WINDOWS_LLVM_PREFIX = ROOT / "tools" / "llvm-win-prefix"
+WINDOWS_LLVM_CONFIG_EXE = Path(os.environ.get(
+ "LLVM_CONFIG_EXE",
+ "/opt/llvm-win/bin/llvm-config.exe",
+))
+MINGW_CC = "x86_64-w64-mingw32-gcc"
+MINGW_CXX = "x86_64-w64-mingw32-g++"
TARGET_MATRIX = {
"x86_64-unknown-linux-gnu": ["Linux"],
@@ -42,7 +50,7 @@
"x86_64-unknown-redox": ["Redox"],
"x86_64-unknown-fuchsia": ["Fuchsia"],
"x86_64-unknown-haiku": ["Haiku"],
- "x86_64-pc-windows-gnu": ["Windows"],
+ WINDOWS_GNU_TARGET: ["Linux", "Windows"],
"aarch64-apple-darwin": ["Darwin"],
"x86_64-apple-darwin": ["Darwin"],
}
@@ -73,6 +81,71 @@ def get_version():
VERSION = get_version()
+def is_windows_gnu_target(target):
+ return target == WINDOWS_GNU_TARGET
+
+def require_tool(tool):
+ if shutil.which(tool) is None:
+ print(f"[!] Missing required tool: {tool}")
+ sys.exit(1)
+
+def configure_windows_gnu_env(env):
+ if not WINDOWS_LLVM_PREFIX.exists():
+ print(f"[!] Missing Windows LLVM prefix wrapper: {WINDOWS_LLVM_PREFIX}")
+ print(" Expected tools/llvm-win-prefix/bin/llvm-config to exist.")
+ sys.exit(1)
+
+ if not WINDOWS_LLVM_CONFIG_EXE.exists():
+ print(f"[!] Missing Windows llvm-config.exe: {WINDOWS_LLVM_CONFIG_EXE}")
+ print(" Set LLVM_CONFIG_EXE=/path/to/llvm-config.exe if it is installed elsewhere.")
+ sys.exit(1)
+
+ require_tool(MINGW_CC)
+ require_tool(MINGW_CXX)
+ require_tool("wine")
+
+ env["LLVM_SYS_211_PREFIX"] = str(WINDOWS_LLVM_PREFIX)
+ env["LLVM_CONFIG_EXE"] = str(WINDOWS_LLVM_CONFIG_EXE)
+ env["CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER"] = MINGW_CC
+ env["CC_x86_64_pc_windows_gnu"] = MINGW_CC
+ env["CXX_x86_64_pc_windows_gnu"] = MINGW_CXX
+
+def cargo_build_args(target):
+ args = ["cargo", "build", "--target", target, "--release"]
+ if is_windows_gnu_target(target):
+ args.extend(["--no-default-features", "--features", "llvm-target-x86"])
+ return args
+
+def mingw_print_file_name(name):
+ fallback = Path("/usr/x86_64-w64-mingw32/sys-root/mingw/bin") / name
+ if shutil.which(MINGW_CC) is None:
+ return fallback if fallback.exists() else None
+
+ result = subprocess.run(
+ [MINGW_CC, f"-print-file-name={name}"],
+ text=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.DEVNULL,
+ check=False,
+ )
+ if result.returncode != 0:
+ return None
+
+ path = Path(result.stdout.strip())
+ if path.exists() and path.name.lower() == name.lower():
+ return path
+ if fallback.exists():
+ return fallback
+ return None
+
+def windows_package_inputs(exe_path):
+ files = [exe_path]
+ for dll in ["libgcc_s_seh-1.dll", "libstdc++-6.dll", "libwinpthread-1.dll"]:
+ path = mingw_print_file_name(dll)
+ if path is not None:
+ files.append(path)
+ return files
+
# ------------------------------------------------------
# rustup target add
# ------------------------------------------------------
@@ -93,14 +166,12 @@ def cmd_build():
env = os.environ.copy()
- if platform.system() == "Linux" and t == "x86_64-pc-windows-gnu":
- print(" [*] Applying MinGW LLVM environment")
-
- env["LLVM_SYS_211_PREFIX"] = "/opt/llvm-win"
- env["LLVM_CONFIG_PATH"] = "/opt/llvm-win/bin/llvm-config.exe"
+ if is_windows_gnu_target(t):
+ print(" [*] Applying MinGW + Windows LLVM environment")
+ configure_windows_gnu_env(env)
subprocess.run(
- ["cargo", "build", "--target", t, "--release"],
+ cargo_build_args(t),
check=True,
env=env
)
@@ -127,10 +198,16 @@ def cmd_package():
print(f"[!] Missing binary: {bin_path}")
continue
- shutil.copy(bin_path, ROOT / f"{BINARY_NAME}.exe")
+ package_files = []
+ for src in windows_package_inputs(bin_path):
+ dst = ROOT / src.name
+ shutil.copy(src, dst)
+ package_files.append(dst.name)
+
zip_path = ROOT / f"{out_name}.zip"
- subprocess.run(["zip", "-q", zip_path, f"{BINARY_NAME}.exe"], check=True)
- os.remove(ROOT / f"{BINARY_NAME}.exe")
+ subprocess.run(["zip", "-q", zip_path, *package_files], check=True)
+ for name in package_files:
+ os.remove(ROOT / name)
print(f"[+] Windows packaged → {zip_path}")
@@ -349,11 +426,23 @@ def cmd_clean():
# ------------------------------------------------------
def main():
if len(sys.argv) < 2:
- print("Usage: x.py [install | build | package | release | clean | gui]")
+ print("Usage: x.py [install | build | package | release | clean | gui] [target...]")
return
cmd = sys.argv[1]
+ selected_targets = sys.argv[2:]
+
+ global TARGETS
+ if selected_targets:
+ unknown = [t for t in selected_targets if t not in ALL_TARGETS]
+ if unknown:
+ print("Unknown target(s):", ", ".join(unknown))
+ print("Known targets:")
+ for target in ALL_TARGETS:
+ print(" ", target)
+ sys.exit(1)
+ TARGETS = selected_targets
if cmd == "install":
cmd_install()
@@ -369,7 +458,7 @@ def main():
cmd_gui()
else:
print("Unknown command:", cmd)
- print("Usage: x.py [install | build | package | release | clean | gui]")
+ print("Usage: x.py [install | build | package | release | clean | gui] [target...]")
if __name__ == "__main__":
main()