Skip to content

Commit 91f79a8

Browse files
authored
feat: implement advanced cross-compilation support and backend customization (#303)
This commit introduces a robust framework for cross-compilation and fine-grained backend control. It expands architecture support for ARM64 and x86_64 across Linux and Darwin, while providing a comprehensive CLI interface for LLVM backend options. Changes: - **LLVM Backend & Cross-Compilation**: - Introduced `BackendOptions` to parameterize target triple, CPU, features, ABI, sysroot, and linker settings. - Updated `generate_ir` to initialize all LLVM targets and use provided backend options during target machine creation. - Refactored `abi_c.rs` to support System V ABI for both x86_64 and Arm64 on Linux and macOS. - **Enhanced Inline Assembly**: - Implemented Arm64 register group mapping (`x0`-`x30`, `sp`, etc.) in `plan.rs`. - Added support for target-specific inline assembly dialects (Intel for x86, AT&T for ARM). - Refined automatic clobber logic to avoid trashing GPRs in empty barrier-like assembly blocks. - **Advanced CLI Interface**: - Added `--llvm` subcommand flags to control the backend: `--target`, `--cpu`, `--features`, `--abi`, and `--sysroot`. - Added `-C` flags for linker customization: `linker=<path>`, `link-arg=<arg>`, and `no-default-libs`. - Reserved the `--whale` flag for future alternative backend support. - **Parser & Preprocessor**: - Significantly improved `#[target(os="...")]` preprocessing logic. The new scanner is aware of block comments, strings, char literals, and nested braces, preventing premature truncation of target blocks. - **Infrastructure & Documentation**: - Formalized a **Tiered Platform Policy** (Tier 1-4) in `README.md` to set support expectations. - Updated `x.py` build script to include `aarch64-unknown-linux-gnu`. - Cleaned up build artifacts by moving object files to the root temporary directory before linking. These updates transform Wave into a portable toolchain capable of targeting multiple architectures and operating systems from a single host. Signed-off-by: LunaStev <luna@lunastev.org>
1 parent 43b6844 commit 91f79a8

File tree

16 files changed

+697
-81
lines changed

16 files changed

+697
-81
lines changed

.github/doom-demo.gif

4.85 MB
Loading

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,20 @@ Useful global options:
139139

140140
---
141141

142+
## Contributing
143+
144+
Contributions are welcome! Please read the [contributing guidelines](CONTRIBUTING.md) before submitting a pull request.
145+
146+
---
147+
148+
## What can do?
149+
150+
### [Doom Domo](https://github.com/wavefnd/Wave/tree/master/examples/doom.wave)
151+
152+
[doom-demo](.github/doom-demo.gif)
153+
154+
---
155+
142156
<p align="center">
143157
<a href="https://star-history.com/#wavefnd/Wave&Date">
144158
<img src="https://api.star-history.com/svg?repos=wavefnd/Wave&type=Date" alt="Star History Chart" width="80%">

front/parser/src/import.rs

Lines changed: 103 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,98 @@ fn is_supported_target_item_start(line: &str) -> bool {
5757
false
5858
}
5959

60-
fn consume_target_item(
61-
lines: &[&str],
62-
mut idx: usize,
63-
keep: bool,
64-
out: &mut Vec<String>,
65-
) -> usize {
60+
fn scan_target_item_line(
61+
line: &str,
62+
in_block_comment: &mut bool,
63+
depth: &mut i32,
64+
seen_open: &mut bool,
65+
saw_semicolon: &mut bool,
66+
) {
67+
let mut chars = line.chars().peekable();
68+
let mut in_string = false;
69+
let mut in_char = false;
70+
let mut escape = false;
71+
72+
while let Some(ch) = chars.next() {
73+
if *in_block_comment {
74+
if ch == '*' && chars.peek() == Some(&'/') {
75+
chars.next();
76+
*in_block_comment = false;
77+
}
78+
continue;
79+
}
80+
81+
if in_string {
82+
if escape {
83+
escape = false;
84+
continue;
85+
}
86+
if ch == '\\' {
87+
escape = true;
88+
continue;
89+
}
90+
if ch == '"' {
91+
in_string = false;
92+
}
93+
continue;
94+
}
95+
96+
if in_char {
97+
if escape {
98+
escape = false;
99+
continue;
100+
}
101+
if ch == '\\' {
102+
escape = true;
103+
continue;
104+
}
105+
if ch == '\'' {
106+
in_char = false;
107+
}
108+
continue;
109+
}
110+
111+
if ch == '/' {
112+
if chars.peek() == Some(&'/') {
113+
break;
114+
}
115+
if chars.peek() == Some(&'*') {
116+
chars.next();
117+
*in_block_comment = true;
118+
continue;
119+
}
120+
}
121+
122+
if ch == '"' {
123+
in_string = true;
124+
continue;
125+
}
126+
if ch == '\'' {
127+
in_char = true;
128+
continue;
129+
}
130+
131+
if ch == '{' {
132+
*depth += 1;
133+
*seen_open = true;
134+
continue;
135+
}
136+
if ch == '}' {
137+
if *depth > 0 {
138+
*depth -= 1;
139+
}
140+
continue;
141+
}
142+
if ch == ';' {
143+
*saw_semicolon = true;
144+
}
145+
}
146+
}
147+
148+
fn consume_target_item(lines: &[&str], mut idx: usize, keep: bool, out: &mut Vec<String>) -> usize {
66149
let mut depth: i32 = 0;
67150
let mut seen_open = false;
151+
let mut in_block_comment = false;
68152

69153
while idx < lines.len() {
70154
let line = lines[idx];
@@ -74,19 +158,22 @@ fn consume_target_item(
74158
out.push(String::new());
75159
}
76160

77-
for ch in line.chars() {
78-
if ch == '{' {
79-
depth += 1;
80-
seen_open = true;
81-
} else if ch == '}' && depth > 0 {
82-
depth -= 1;
83-
}
84-
}
161+
let mut saw_semicolon = false;
162+
scan_target_item_line(
163+
line,
164+
&mut in_block_comment,
165+
&mut depth,
166+
&mut seen_open,
167+
&mut saw_semicolon,
168+
);
85169

86170
idx += 1;
87171

88-
let trimmed = line.trim_end();
89-
if seen_open && depth == 0 {
172+
if seen_open {
173+
if depth == 0 {
174+
break;
175+
}
176+
} else if saw_semicolon {
90177
break;
91178
}
92179
}

llvm/src/backend.rs

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,20 @@
99
//
1010
// SPDX-License-Identifier: MPL-2.0
1111

12-
use std::fs;
13-
use std::path::Path;
1412
use std::process::Command;
1513

14+
#[derive(Debug, Default, Clone)]
15+
pub struct BackendOptions {
16+
pub target: Option<String>,
17+
pub cpu: Option<String>,
18+
pub features: Option<String>,
19+
pub abi: Option<String>,
20+
pub sysroot: Option<String>,
21+
pub linker: Option<String>,
22+
pub link_args: Vec<String>,
23+
pub no_default_libs: bool,
24+
}
25+
1626
fn normalize_clang_opt_flag(opt_flag: &str) -> &str {
1727
match opt_flag {
1828
// LLVM pass pipeline currently has no dedicated Ofast preset, so keep
@@ -22,12 +32,29 @@ fn normalize_clang_opt_flag(opt_flag: &str) -> &str {
2232
}
2333
}
2434

25-
pub fn compile_ir_to_object(ir: &str, file_stem: &str, opt_flag: &str) -> String {
35+
pub fn compile_ir_to_object(
36+
ir: &str,
37+
file_stem: &str,
38+
opt_flag: &str,
39+
backend: &BackendOptions,
40+
) -> String {
2641
let object_path = format!("{}.o", file_stem);
2742

2843
let normalized_opt = normalize_clang_opt_flag(opt_flag);
2944
let mut cmd = Command::new("clang");
3045

46+
if let Some(target) = &backend.target {
47+
cmd.arg(format!("--target={}", target));
48+
}
49+
50+
if let Some(sysroot) = &backend.sysroot {
51+
cmd.arg(format!("--sysroot={}", sysroot));
52+
}
53+
54+
if let Some(abi) = &backend.abi {
55+
cmd.arg("-target-abi").arg(abi);
56+
}
57+
3158
if !normalized_opt.is_empty() {
3259
cmd.arg(normalized_opt);
3360
}
@@ -61,8 +88,27 @@ pub fn compile_ir_to_object(ir: &str, file_stem: &str, opt_flag: &str) -> String
6188
object_path
6289
}
6390

64-
pub fn link_objects(objects: &[String], output: &str, libs: &[String], lib_paths: &[String]) {
65-
let mut cmd = Command::new("clang");
91+
pub fn link_objects(
92+
objects: &[String],
93+
output: &str,
94+
libs: &[String],
95+
lib_paths: &[String],
96+
backend: &BackendOptions,
97+
) {
98+
let linker_bin = backend.linker.as_deref().unwrap_or("clang");
99+
let mut cmd = Command::new(linker_bin);
100+
101+
if backend.linker.is_none() {
102+
if let Some(target) = &backend.target {
103+
cmd.arg(format!("--target={}", target));
104+
}
105+
if let Some(sysroot) = &backend.sysroot {
106+
cmd.arg(format!("--sysroot={}", sysroot));
107+
}
108+
if let Some(abi) = &backend.abi {
109+
cmd.arg("-target-abi").arg(abi);
110+
}
111+
}
66112

67113
for obj in objects {
68114
cmd.arg(obj);
@@ -76,7 +122,15 @@ pub fn link_objects(objects: &[String], output: &str, libs: &[String], lib_paths
76122
cmd.arg(format!("-l{}", lib));
77123
}
78124

79-
cmd.arg("-o").arg(output).arg("-lc").arg("-lm");
125+
for arg in &backend.link_args {
126+
cmd.arg(arg);
127+
}
128+
129+
cmd.arg("-o").arg(output);
130+
131+
if !backend.no_default_libs {
132+
cmd.arg("-lc").arg("-lm");
133+
}
80134

81135
let output = cmd.output().expect("Failed to link");
82136
if !output.status.success() {

llvm/src/codegen/abi_c.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -402,8 +402,12 @@ fn classify_param<'ctx>(
402402
t: BasicTypeEnum<'ctx>,
403403
) -> ParamLowering<'ctx> {
404404
match target {
405-
CodegenTarget::LinuxX86_64 => classify_param_x86_64_sysv(context, td, t),
406-
CodegenTarget::DarwinArm64 => classify_param_arm64_darwin(td, t),
405+
CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => {
406+
classify_param_x86_64_sysv(context, td, t)
407+
}
408+
CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => {
409+
classify_param_arm64_darwin(td, t)
410+
}
407411
}
408412
}
409413

@@ -414,8 +418,10 @@ fn classify_ret<'ctx>(
414418
t: Option<BasicTypeEnum<'ctx>>,
415419
) -> RetLowering<'ctx> {
416420
match target {
417-
CodegenTarget::LinuxX86_64 => classify_ret_x86_64_sysv(context, td, t),
418-
CodegenTarget::DarwinArm64 => classify_ret_arm64_darwin(td, t),
421+
CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => {
422+
classify_ret_x86_64_sysv(context, td, t)
423+
}
424+
CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => classify_ret_arm64_darwin(td, t),
419425
}
420426
}
421427

llvm/src/codegen/ir.rs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ use inkwell::values::{BasicValue, BasicValueEnum, FunctionValue};
1616
use inkwell::OptimizationLevel;
1717

1818
use inkwell::targets::{
19-
CodeModel, InitializationConfig, RelocMode, Target, TargetData, TargetMachine,
19+
CodeModel, InitializationConfig, RelocMode, Target, TargetData, TargetMachine, TargetTriple,
2020
};
2121
use parser::ast::{
2222
ASTNode, EnumNode, ExternFunctionNode, FunctionNode, Mutability, ParameterNode, ProtoImplNode,
2323
StructNode, TypeAliasNode, VariableNode, WaveType,
2424
};
2525
use std::collections::{HashMap, HashSet};
2626

27+
use crate::backend::BackendOptions;
2728
use crate::codegen::target::require_supported_target_from_triple;
2829
use crate::statement::generate_statement_ir;
2930

@@ -48,7 +49,21 @@ fn normalize_opt_flag_for_passes(opt_flag: &str) -> &str {
4849
}
4950
}
5051

51-
pub unsafe fn generate_ir(ast_nodes: &[ASTNode], opt_flag: &str) -> String {
52+
fn target_opt_level_from_flag(opt_flag: &str) -> OptimizationLevel {
53+
match normalize_opt_flag_for_passes(opt_flag) {
54+
"" | "-O0" => OptimizationLevel::None,
55+
"-O1" => OptimizationLevel::Less,
56+
"-O2" | "-Os" | "-Oz" => OptimizationLevel::Default,
57+
"-O3" => OptimizationLevel::Aggressive,
58+
other => panic!("unknown opt flag for target machine: {}", other),
59+
}
60+
}
61+
62+
pub unsafe fn generate_ir(
63+
ast_nodes: &[ASTNode],
64+
opt_flag: &str,
65+
backend: &BackendOptions,
66+
) -> String {
5267
let context: &'static Context = Box::leak(Box::new(Context::create()));
5368
let module: &'static _ = Box::leak(Box::new(context.create_module("main")));
5469
let builder: &'static _ = Box::leak(Box::new(context.create_builder()));
@@ -59,17 +74,23 @@ pub unsafe fn generate_ir(ast_nodes: &[ASTNode], opt_flag: &str) -> String {
5974
.map(|n| resolve_ast_node(n, &named_types))
6075
.collect();
6176

62-
Target::initialize_native(&InitializationConfig::default()).unwrap();
63-
let triple = TargetMachine::get_default_triple();
77+
Target::initialize_all(&InitializationConfig::default());
78+
let triple = if let Some(raw) = &backend.target {
79+
TargetTriple::create(raw)
80+
} else {
81+
TargetMachine::get_default_triple()
82+
};
6483
let abi_target = require_supported_target_from_triple(&triple);
6584
let target = Target::from_triple(&triple).unwrap();
85+
let cpu = backend.cpu.as_deref().unwrap_or("generic");
86+
let features = backend.features.as_deref().unwrap_or("");
6687

6788
let tm = target
6889
.create_target_machine(
6990
&triple,
70-
"generic",
71-
"",
72-
OptimizationLevel::Default,
91+
cpu,
92+
features,
93+
target_opt_level_from_flag(opt_flag),
7394
RelocMode::Default,
7495
CodeModel::Default,
7596
)

0 commit comments

Comments
 (0)