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
57 changes: 57 additions & 0 deletions llvm/crt/linux/x86_64/crt1.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# This file is part of the Wave language project.
# Copyright (c) 2024-2026 Wave Foundation
# Copyright (c) 2024-2026 LunaStev and contributors
#
# This Source Code Form is subject to the terms of the
# Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file,
# You can obtain one at https://mozilla.org/MPL/2.0/.
#
# 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.

.text
.globl _start
.type _start,@function
_start:
.cfi_startproc
.cfi_undefined %rip
xorl %ebp, %ebp

# Linux x86_64 enters with:
# rdx = dynamic loader finalizer
# rsp = argc, argv..., NULL, envp..., NULL, auxv...
#
# Use glibc's hosted-program initializer rather than calling main
# directly. This keeps libc initialization, stdio flushing, TLS setup,
# argv/envp handling, and exit processing on the normal hosted path.
movq %rdx, %r9
popq %rsi
movq %rsp, %rdx
andq $-16, %rsp
pushq %rax
pushq %rsp
xorl %r8d, %r8d
xorl %ecx, %ecx
leaq __wave_main_trampoline(%rip), %rdi
call __libc_start_main@PLT
hlt
.cfi_endproc

.size _start, .-_start

.type __wave_main_trampoline,@function
__wave_main_trampoline:
.cfi_startproc
subq $8, %rsp
.cfi_adjust_cfa_offset 8
call main
addq $8, %rsp
.cfi_adjust_cfa_offset -8
xorl %eax, %eax
ret
.cfi_endproc

.size __wave_main_trampoline, .-__wave_main_trampoline

.section .note.GNU-stack,"",@progbits
35 changes: 27 additions & 8 deletions llvm/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ pub fn link_objects(
let mut cmd = Command::new(&linker_bin);
configure_bundled_llvm_tool_env(&mut cmd, &linker_bin);

if backend.linker.is_none() {
if backend.linker.is_none() && !(is_windows_gnu_target(Some(target)) && linker_bin == "gcc") {
append_lld_target_args(&mut cmd, target, backend);
}

Expand Down Expand Up @@ -161,6 +161,10 @@ pub fn link_objects(
fn default_lld_for_target(target: &str) -> String {
if is_darwin_target(target) {
resolve_bundled_tool("ld64.lld")
} else if is_windows_gnu_target(Some(target)) {
resolve_bundled_tool_path("ld.lld")
.map(|path| path.to_string_lossy().to_string())
.unwrap_or_else(|| "gcc".to_string())
} else {
resolve_bundled_tool("ld.lld")
}
Expand Down Expand Up @@ -231,26 +235,42 @@ fn elf_lld_emulation(target: &str) -> Option<&'static str> {
}

fn resolve_bundled_tool(tool: &str) -> String {
if let Some(path) = resolve_bundled_tool_path(tool) {
return path.to_string_lossy().to_string();
}
executable_tool_name(tool)
}

fn resolve_bundled_tool_path(tool: &str) -> Option<PathBuf> {
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();
return Some(candidate);
}
}
executable_tool_name(tool)
None
}

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

if cfg!(target_os = "linux") {
prepend_env_path(cmd, "LD_LIBRARY_PATH", lib_dir);
if let Some(lib_dir) = bin_dir.parent().map(|llvm_dir| llvm_dir.join("lib")) {
if lib_dir.is_dir() {
prepend_env_path(cmd, "LD_LIBRARY_PATH", lib_dir);
}
}
} else if cfg!(windows) {
if let Some(root_dir) = bin_dir.parent().and_then(|llvm_dir| llvm_dir.parent()) {
prepend_env_path(cmd, "PATH", root_dir.to_path_buf());
}
prepend_env_path(cmd, "PATH", bin_dir);
}
}

fn bundled_llvm_lib_dir(bin: &str) -> Option<PathBuf> {
fn bundled_llvm_bin_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") {
Expand All @@ -262,8 +282,7 @@ fn bundled_llvm_lib_dir(bin: &str) -> Option<PathBuf> {
return None;
}

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

fn prepend_env_path(cmd: &mut Command, name: &str, first: PathBuf) {
Expand Down
81 changes: 58 additions & 23 deletions llvm/src/codegen/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use parser::ast::{
StructNode, TypeAliasNode, VariableNode, WaveType,
};
use std::collections::{HashMap, HashSet};
use std::sync::Once;

use crate::backend::BackendOptions;
use crate::codegen::target::require_supported_target_from_triple;
Expand Down Expand Up @@ -74,28 +75,46 @@ fn target_opt_level_from_flag(opt_flag: &str) -> OptimizationLevel {
}
}

fn initialize_llvm_targets() {
let config = InitializationConfig::default();
static INIT_LLVM_TARGETS: Once = Once::new();

#[cfg(feature = "llvm-target-all")]
{
Target::initialize_all(&config);
fn codegen_trace(step: &str) {
if std::env::var_os("WAVE_CODEGEN_TRACE").is_some() {
eprintln!("[wavec-codegen] {}", step);
}
}

#[cfg(all(not(feature = "llvm-target-all"), feature = "llvm-target-x86"))]
{
Target::initialize_x86(&config);
}
fn initialize_llvm_targets() {
INIT_LLVM_TARGETS.call_once(|| {
let config = InitializationConfig::default();

#[cfg(all(not(feature = "llvm-target-all"), feature = "llvm-target-aarch64"))]
{
Target::initialize_aarch64(&config);
}
#[cfg(feature = "llvm-target-all")]
{
Target::initialize_all(&config);
}

#[cfg(all(not(feature = "llvm-target-all"), feature = "llvm-target-riscv"))]
{
Target::initialize_riscv(&config);
}
#[cfg(all(not(feature = "llvm-target-all"), feature = "llvm-target-x86"))]
{
Target::initialize_x86(&config);
}

#[cfg(all(not(feature = "llvm-target-all"), feature = "llvm-target-aarch64"))]
{
Target::initialize_aarch64(&config);
}

#[cfg(all(not(feature = "llvm-target-all"), feature = "llvm-target-riscv"))]
{
Target::initialize_riscv(&config);
}
});
}

fn should_run_llvm_pass_pipeline() -> bool {
// LLVM 21's C pass pipeline can jump through a null callback in the
// MinGW-built Windows package. Code generation still uses the target
// machine's optimization level, so keep Windows codegen usable by skipping
// the in-process IR pass pipeline there.
!cfg!(target_os = "windows")
}

pub unsafe fn generate_ir(
Expand Down Expand Up @@ -150,27 +169,36 @@ fn build_module(
opt_flag: &str,
backend: &BackendOptions,
) -> GeneratedModule {
codegen_trace("initialize targets");
initialize_llvm_targets();

codegen_trace("create context");
let context: &'static Context = Box::leak(Box::new(Context::create()));
codegen_trace("create module");
let module: &'static _ = Box::leak(Box::new(context.create_module("main")));
codegen_trace("create builder");
let builder: &'static _ = Box::leak(Box::new(context.create_builder()));

codegen_trace("resolve named types");
let named_types = collect_named_types(ast_nodes);
let ast_nodes: Vec<ASTNode> = ast_nodes
.iter()
.map(|n| resolve_ast_node(n, &named_types))
.collect();

initialize_llvm_targets();
codegen_trace("resolve target triple");
let triple = if let Some(raw) = &backend.target {
TargetTriple::create(raw)
} else {
TargetMachine::get_default_triple()
};
let abi_target = require_supported_target_from_triple(&triple);
codegen_trace("lookup target");
let target = Target::from_triple(&triple).unwrap();
let cpu = backend.cpu.as_deref().unwrap_or("generic");
let features = backend.features.as_deref().unwrap_or("");

codegen_trace("create target machine");
let tm = target
.create_target_machine(
&triple,
Expand All @@ -182,6 +210,7 @@ fn build_module(
)
.unwrap();

codegen_trace("set target metadata");
module.set_triple(&triple);

let td_val: TargetData = tm.get_target_data();
Expand Down Expand Up @@ -495,13 +524,19 @@ fn build_module(
}
}

let pbo = PassBuilderOptions::create();
let pipeline = pipeline_from_opt_flag(opt_flag);
if should_run_llvm_pass_pipeline() {
let pbo = PassBuilderOptions::create();
let pipeline = pipeline_from_opt_flag(opt_flag);

module
.run_passes(pipeline, &tm, pbo)
.expect("failed to run optimization passes");
codegen_trace("run optimization passes");
module
.run_passes(pipeline, &tm, pbo)
.expect("failed to run optimization passes");
} else {
codegen_trace("skip optimization passes");
}

codegen_trace("finish module");
GeneratedModule {
module,
target_machine: tm,
Expand Down
6 changes: 5 additions & 1 deletion llvm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@ pub mod codegen;
pub mod expression;
pub mod importgen;
pub mod statement;
pub mod toolchain;

pub fn backend() -> Option<String> {
let (major, minor, patch) = (0_u32, 0_u32, 0_u32);
let (mut major, mut minor, mut patch) = (0_u32, 0_u32, 0_u32);
unsafe {
llvm_sys::core::LLVMGetVersion(&mut major, &mut minor, &mut patch);
}
Some(format!("LLVM {}.{}.{}", major, minor, patch))
}
54 changes: 54 additions & 0 deletions llvm/src/toolchain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// This file is part of the Wave language project.
// Copyright (c) 2024–2026 Wave Foundation
// Copyright (c) 2024–2026 LunaStev and contributors
//
// This Source Code Form is subject to the terms of the
// Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
//
// 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;

pub fn find_bundled_linux_crt1(target: &str) -> Option<PathBuf> {
bundled_linux_crt1_candidates(target)
.into_iter()
.find(|path| path.is_file())
}

pub fn expected_bundled_linux_crt1(target: &str) -> PathBuf {
bundled_linux_crt1_candidates(target)
.into_iter()
.next()
.unwrap_or_else(|| PathBuf::from(format!("crt/{}/crt1.o", target)))
}

fn bundled_linux_crt1_candidates(target: &str) -> Vec<PathBuf> {
let mut paths = Vec::new();

if let Ok(path) = env::var("WAVE_LINUX_CRT1_OBJECT") {
if !path.trim().is_empty() {
paths.push(PathBuf::from(path));
}
}

if let Ok(exe) = env::current_exe() {
if let Some(dir) = exe.parent() {
paths.push(dir.join("crt").join(target).join("crt1.o"));
if let Some(root) = dir.parent() {
paths.push(
root.join("lib")
.join("wave")
.join("crt")
.join(target)
.join("crt1.o"),
);
}
}
}

paths
}
Loading
Loading