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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
*.crate # Compiled crate files
Cargo.lock

dist/

# Build artifacts
*.d # Dependency files generated by build tools
*.o # Object files
Expand Down
12 changes: 5 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
[package]
name = "wavec"
version = "0.1.8-pre-beta"
version = "0.1.9-pre-beta"
edition = "2021"

[lib]
name = "wavec"
path = "src/lib.rs"

# [target.x86_64-apple-darwin]
# linker = "clang"
# [target.aarch64-apple-darwin]
# linker = "clang"

[dependencies]
utils = { path = "utils" }
lexer = { path = "front/lexer" }
Expand All @@ -20,8 +15,11 @@ error = { path = "front/error" }
llvm = { path = "./llvm", default-features = false }

[features]
default = ["llvm-target-all"]
default = ["llvm-target-core64"]
llvm-target-core64 = ["llvm-target-x86", "llvm-target-aarch64", "llvm-target-riscv"]
llvm-target-all = ["llvm/llvm-target-all"]
llvm-target-aarch64 = ["llvm/llvm-target-aarch64"]
llvm-target-riscv = ["llvm/llvm-target-riscv"]
llvm-target-x86 = ["llvm/llvm-target-x86"]

[workspace]
Expand Down
8 changes: 7 additions & 1 deletion llvm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ error = { path = "../front/error" }
inkwell = { version = "0.8.0", default-features = false, features = ["llvm21-1"] }
llvm-sys = { version = "211.0.0", features = ["prefer-dynamic"] }

[target.'cfg(target_os = "windows")'.dependencies]
inkwell = { version = "0.8.0", default-features = false, features = ["no-libffi-linking"] }

[features]
default = ["llvm-target-all"]
default = ["llvm-target-core64"]
llvm-target-core64 = ["llvm-target-x86", "llvm-target-aarch64", "llvm-target-riscv"]
llvm-target-all = ["inkwell/target-all"]
llvm-target-aarch64 = ["inkwell/target-aarch64"]
llvm-target-riscv = ["inkwell/target-riscv"]
llvm-target-x86 = ["inkwell/target-x86"]
222 changes: 193 additions & 29 deletions llvm/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
// SPDX-License-Identifier: MPL-2.0
// AI TRAINING NOTICE: Prohibited without prior written permission. No use for machine learning or generative AI training, fine-tuning, distillation, embedding, or dataset creation.

use std::env;
use std::path::PathBuf;
use std::process::Command;

#[derive(Debug, Default, Clone)]
Expand All @@ -34,7 +36,7 @@ fn is_windows_gnu_target(target: Option<&str>) -> bool {
t.starts_with("x86_64-") && t.contains("windows") && !t.contains("msvc")
}

fn normalize_clang_opt_flag(opt_flag: &str) -> &str {
fn normalize_llvm_opt_flag(opt_flag: &str) -> &str {
match opt_flag {
// LLVM pass pipeline currently has no dedicated Ofast preset, so keep
// frontend/back-end optimization behavior aligned at O3.
Expand All @@ -51,36 +53,42 @@ pub fn compile_ir_to_object(
) -> String {
let object_path = format!("{}.o", file_stem);

let normalized_opt = normalize_clang_opt_flag(opt_flag);
let mut cmd = Command::new("clang");
let normalized_opt = normalize_llvm_opt_flag(opt_flag);
let llc = resolve_bundled_tool("llc");
let mut cmd = Command::new(&llc);
configure_bundled_llvm_tool_env(&mut cmd, &llc);

if let Some(target) = &backend.target {
cmd.arg(format!("--target={}", target));
cmd.arg(format!("--mtriple={}", target));
}

if let Some(sysroot) = &backend.sysroot {
cmd.arg(format!("--sysroot={}", sysroot));
if let Some(cpu) = &backend.cpu {
cmd.arg(format!("--mcpu={}", cpu));
}
if let Some(features) = &backend.features {
cmd.arg(format!("--mattr={}", features));
}
if let Some(model) = &backend.code_model {
cmd.arg(format!("--code-model={}", model));
}
if let Some(model) = &backend.relocation_model {
cmd.arg(format!("--relocation-model={}", model));
}

if let Some(abi) = &backend.abi {
cmd.arg("-target-abi").arg(abi);
cmd.arg(format!("--target-abi={}", abi));
}

if !normalized_opt.is_empty() {
cmd.arg(normalized_opt);
}

let mut child = cmd
.arg("-c")
.arg("-x")
.arg("ir")
.arg("--filetype=obj")
.arg("-")
.arg("-o")
.arg(&object_path)
.arg("-Wno-override-module")
.stdin(std::process::Stdio::piped())
.spawn()
.expect("Failed to execute clang");
.expect("Failed to execute llc");

use std::io::Write;
child
Expand All @@ -92,7 +100,7 @@ pub fn compile_ir_to_object(

let output = child.wait_with_output().unwrap();
if !output.status.success() {
eprintln!("clang failed: {}", String::from_utf8_lossy(&output.stderr));
eprintln!("llc failed: {}", String::from_utf8_lossy(&output.stderr));
return String::new();
}

Expand All @@ -106,19 +114,16 @@ pub fn link_objects(
lib_paths: &[String],
backend: &BackendOptions,
) {
let linker_bin = backend.linker.as_deref().unwrap_or("clang");
let mut cmd = Command::new(linker_bin);
let target = backend.target.as_deref().unwrap_or("");
let linker_bin = backend
.linker
.clone()
.unwrap_or_else(|| default_lld_for_target(target));
let mut cmd = Command::new(&linker_bin);
configure_bundled_llvm_tool_env(&mut cmd, &linker_bin);

if backend.linker.is_none() {
if let Some(target) = &backend.target {
cmd.arg(format!("--target={}", target));
}
if let Some(sysroot) = &backend.sysroot {
cmd.arg(format!("--sysroot={}", sysroot));
}
if let Some(abi) = &backend.abi {
cmd.arg("-target-abi").arg(abi);
}
append_lld_target_args(&mut cmd, target, backend);
}

for obj in objects {
Expand All @@ -133,18 +138,177 @@ pub fn link_objects(
cmd.arg(format!("-l{}", lib));
}

for arg in &backend.link_args {
for arg in expand_lld_link_args(&backend.link_args) {
cmd.arg(arg);
}

cmd.arg("-o").arg(output);

if !backend.no_default_libs && !is_windows_gnu_target(backend.target.as_deref()) {
cmd.arg("-lc").arg("-lm");
if !backend.no_default_libs {
if is_darwin_target(target) {
cmd.arg("-lSystem");
} else if !is_windows_gnu_target(backend.target.as_deref()) {
cmd.arg("-lc").arg("-lm");
}
}

let output = cmd.output().expect("Failed to link");
if !output.status.success() {
eprintln!("link failed: {}", String::from_utf8_lossy(&output.stderr));
}
}

fn default_lld_for_target(target: &str) -> String {
if is_darwin_target(target) {
resolve_bundled_tool("ld64.lld")
} else {
resolve_bundled_tool("ld.lld")
}
}

fn append_lld_target_args(cmd: &mut Command, target: &str, backend: &BackendOptions) {
if is_darwin_target(target) {
cmd.arg("-arch")
.arg(if target.starts_with("x86_64-") {
"x86_64"
} else {
"arm64"
})
.arg("-platform_version")
.arg("macos")
.arg("11.0")
.arg("11.0");

if let Some(sysroot) = &backend.sysroot {
cmd.arg("-syslibroot").arg(sysroot);
}
return;
}

if is_windows_gnu_target(Some(target)) {
cmd.arg("-m").arg("i386pep");
return;
}

if let Some(emulation) = elf_lld_emulation(target) {
cmd.arg("-m").arg(emulation);
}
if let Some(sysroot) = &backend.sysroot {
cmd.arg(format!("--sysroot={}", sysroot));
}
}

fn expand_lld_link_args(link_args: &[String]) -> Vec<String> {
let mut out = Vec::new();
for arg in link_args {
if arg == "-nostartfiles" {
continue;
}
if let Some(rest) = arg.strip_prefix("-Wl,") {
out.extend(
rest.split(',')
.filter(|part| !part.is_empty())
.map(|part| part.to_string()),
);
} else {
out.push(arg.clone());
}
}
out
}

fn is_darwin_target(target: &str) -> bool {
target.contains("apple-darwin")
}

fn elf_lld_emulation(target: &str) -> Option<&'static str> {
match target.split('-').next().unwrap_or(target) {
"x86_64" => Some("elf_x86_64"),
"aarch64" => Some("aarch64elf"),
"riscv64" => Some("elf64lriscv"),
_ => None,
}
}

fn resolve_bundled_tool(tool: &str) -> String {
for dir in llvm_tool_search_dirs() {
let candidate = dir.join(executable_tool_name(tool));
if candidate.is_file() {
return candidate.to_string_lossy().to_string();
}
}
executable_tool_name(tool)
}

fn configure_bundled_llvm_tool_env(cmd: &mut Command, bin: &str) {
let Some(lib_dir) = bundled_llvm_lib_dir(bin) else {
return;
};

if cfg!(target_os = "linux") {
prepend_env_path(cmd, "LD_LIBRARY_PATH", lib_dir);
}
}

fn bundled_llvm_lib_dir(bin: &str) -> Option<PathBuf> {
let bin_path = std::path::Path::new(bin);
let bin_dir = bin_path.parent()?;
if bin_dir.file_name().and_then(|name| name.to_str()) != Some("bin") {
return None;
}

let llvm_dir = bin_dir.parent()?;
if llvm_dir.file_name().and_then(|name| name.to_str()) != Some("llvm") {
return None;
}

let lib_dir = llvm_dir.join("lib");
lib_dir.is_dir().then_some(lib_dir)
}

fn prepend_env_path(cmd: &mut Command, name: &str, first: PathBuf) {
let mut paths = vec![first];
if let Some(current) = env::var_os(name) {
paths.extend(env::split_paths(&current));
}
if let Ok(joined) = env::join_paths(paths) {
cmd.env(name, joined);
}
}

fn executable_tool_name(tool: &str) -> String {
if cfg!(windows) && !tool.to_ascii_lowercase().ends_with(".exe") {
format!("{}.exe", tool)
} else {
tool.to_string()
}
}

fn llvm_tool_search_dirs() -> Vec<PathBuf> {
let mut dirs = Vec::new();

if let Ok(path) = env::var("WAVE_LLVM_BIN") {
if !path.trim().is_empty() {
dirs.push(PathBuf::from(path));
}
}
for env_name in ["WAVE_LLVM_HOME", "LLVM_SYS_211_PREFIX"] {
if let Ok(path) = env::var(env_name) {
if !path.trim().is_empty() {
dirs.push(PathBuf::from(path).join("bin"));
}
}
}
if let Ok(exe) = env::current_exe() {
if let Some(dir) = exe.parent() {
dirs.push(dir.to_path_buf());
dirs.push(dir.join("llvm").join("bin"));
if let Some(root) = dir.parent() {
dirs.push(root.join("llvm").join("bin"));
dirs.push(root.join("lib").join("wave").join("llvm").join("bin"));
}
}
}

dirs
}
Loading
Loading