Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
16 changes: 4 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,22 +101,14 @@ Wave follows a tiered platform policy to set clear expectations for stability, C
</details>

<details>
<summary><strong>🥉 Tier 3 · Experimental</strong> — <code>OpenBSD</code></summary>
<summary><strong>🥉 Tier 3 · Experimental</strong> — <code>OpenBSD</code>, <code>Windows (MinGW/GNU)</code></summary>
<ul>
<li>Compiler build/compile path prioritized</li>
<li>Minimal standard library coverage</li>
<li>Cross-compilation from Linux supported</li>
<li>Basic standard library coverage (via Wine/MinGW)</li>
<li>Experimental support for native Windows binaries</li>
</ul>
</details>

<details>
<summary><strong>🪦 Tier 4 · Unofficial</strong> — <code>Windows</code></summary>
<ul>
<li>Build may work in some environments, but is not guaranteed</li>
<li>No official standard library target at this time</li>
<li>Community-maintained status</li>
</ul>
</details>

---

## CLI Usage
Expand Down
19 changes: 11 additions & 8 deletions front/parser/src/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn parse_target_os_attr(line: &str) -> Option<TargetAttr<'_>> {
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)
Expand Down Expand Up @@ -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<String> = Vec::with_capacity(lines.len());
let mut idx: usize = 0;
Expand Down Expand Up @@ -257,6 +257,7 @@ pub struct ImportedUnit {
pub struct ImportConfig {
pub dep_roots: Vec<PathBuf>,
pub dep_packages: HashMap<String, PathBuf>,
pub target_os: Option<String>,
}

pub fn local_import_unit(
Expand Down Expand Up @@ -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("::") {
Expand All @@ -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(
Expand Down Expand Up @@ -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);
}
}

Expand All @@ -489,6 +490,7 @@ fn external_import_unit(
fn std_import_unit(
path: &str,
already_imported: &mut HashSet<String>,
config: &ImportConfig,
) -> Result<ImportedUnit, WaveError> {
let rel = path.strip_prefix("std::").unwrap();
if rel.trim().is_empty() {
Expand Down Expand Up @@ -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<PathBuf, WaveError> {
Expand All @@ -541,6 +543,7 @@ fn parse_wave_file(
found_path: &Path,
display_name: &str,
already_imported: &mut HashSet<String>,
config: &ImportConfig,
) -> Result<ImportedUnit, WaveError> {
let abs_path = found_path.canonicalize().map_err(|e| {
WaveError::new(
Expand Down Expand Up @@ -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()?;
Expand Down
7 changes: 6 additions & 1 deletion llvm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
10 changes: 9 additions & 1 deletion llvm/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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");
}

Expand Down
51 changes: 51 additions & 0 deletions llvm/src/codegen/abi_c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<BasicTypeEnum<'ctx>>,
) -> 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>,
Expand Down Expand Up @@ -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),
Expand All @@ -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),
Expand Down
16 changes: 15 additions & 1 deletion llvm/src/codegen/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions llvm/src/codegen/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -346,6 +349,7 @@ fn normalize_special_clobber(target: CodegenTarget, token: &str) -> Option<Strin
match target {
CodegenTarget::LinuxX86_64
| CodegenTarget::DarwinX86_64
| CodegenTarget::WindowsX86_64Gnu
| CodegenTarget::FreestandingX86_64 => match token {
"memory" => Some("~{memory}".to_string()),
"cc" | "flags" | "eflags" | "rflags" => Some("~{flags}".to_string()),
Expand Down
10 changes: 8 additions & 2 deletions llvm/src/codegen/target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub enum CodegenTarget {
LinuxArm64,
DarwinX86_64,
DarwinArm64,
WindowsX86_64Gnu,
FreestandingX86_64,
FreestandingArm64,
FreestandingRISCV64,
Expand All @@ -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 {
Expand All @@ -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);
}
Expand Down Expand Up @@ -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",
Expand All @@ -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
);
}
Expand All @@ -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
);
}
1 change: 1 addition & 0 deletions llvm/src/expression/rvalue/asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions llvm/src/statement/asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ fn reg_width_bits_for_target(target: CodegenTarget, reg: &str) -> Option<u32> {
match target {
CodegenTarget::LinuxX86_64
| CodegenTarget::DarwinX86_64
| CodegenTarget::WindowsX86_64Gnu
| CodegenTarget::FreestandingX86_64 => reg_width_bits(reg),
CodegenTarget::LinuxArm64
| CodegenTarget::DarwinArm64
Expand Down Expand Up @@ -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
Expand Down
Loading
Loading