Skip to content

Commit f88a757

Browse files
authored
feat: add freestanding RISC-V support and improve test runner (#310)
This commit introduces backend support for freestanding targets, including a complete initial implementation for RISC-V (rv64), migrates LLVM APIs to use opaque pointers, and drastically improves the Python test runner to support architecture-specific tests. [Details] 1. RISC-V and Freestanding Targets Support - Added new target configurations: `FreestandingX86_64`, `FreestandingArm64`, and `FreestandingRISCV64` (`riscv64-unknown-none-elf`). - Implemented C-ABI lowering rules (ByVal/SRet handling) for RISC-V 64-bit (`classify_param_riscv64`, `classify_ret_riscv64`). - Added inline assembly support for RISC-V, including physical register mapping (`x0`-`x31`, `a0`-`a7`, etc.), width calculations, default clobbers, and constraint validation (`r`, `m`, `i`, etc.). - Added RISC-V CPUs and target features to the CLI `print` commands. 2. LLVM Opaque Pointer Migration - Updated `abi_c.rs`, `legacy.rs`, and standard I/O generation (`printf`, `scanf`) to use modern LLVM opaque pointers (`context.ptr_type()`) instead of typed pointers (`i8_type().ptr_type()`), fixing API deprecation issues. 3. Test Runner & Metadata Improvements - Enhanced `tools/run_tests.py` to parse `// wave-test: host-os=<os>, host-arch=<arch>` metadata at the top of test files. - The test runner now automatically skips tests that do not match the host's OS or Architecture (e.g., skipping Linux syscall tests on macOS). - Applied metadata headers to existing hardware/OS-dependent tests (e.g., inline assembly and syscall tests). - Test runner now gracefully falls back to the `debug` build if the `release` binary is missing, and exits with code `1` if any tests fail. 4. Code Cleanup and Warning Fixes - Cleaned up unused variables, imports, and functions (e.g., removed `is_zero_decimal`, fixed `ASTNode::Enum` resolution). - Fixed an OS-specific unused import warning in `version.rs` (`std::fs`). - Removed an unnecessary `unsafe` block from the LLVM version fetcher. - Added `#[allow(dead_code)]` to currently unused runner functions. Signed-off-by: LunaStev <youngjae681@gmail.com>
1 parent 8063418 commit f88a757

44 files changed

Lines changed: 804 additions & 138 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

llvm/src/codegen/abi_c.rs

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,16 @@ fn is_float_ty<'ctx>(td: &TargetData, t: BasicTypeEnum<'ctx>) -> Option<u32> {
6060
}
6161
}
6262

63-
fn any_ptr_basic<'ctx>(ty: AnyTypeEnum<'ctx>) -> BasicTypeEnum<'ctx> {
63+
fn any_ptr_basic<'ctx>(context: &'ctx Context, ty: AnyTypeEnum<'ctx>) -> BasicTypeEnum<'ctx> {
6464
let aspace = AddressSpace::default();
6565
match ty {
66-
AnyTypeEnum::ArrayType(t) => t.ptr_type(aspace).as_basic_type_enum(),
67-
AnyTypeEnum::FloatType(t) => t.ptr_type(aspace).as_basic_type_enum(),
68-
AnyTypeEnum::FunctionType(t) => t.ptr_type(aspace).as_basic_type_enum(),
69-
AnyTypeEnum::IntType(t) => t.ptr_type(aspace).as_basic_type_enum(),
70-
AnyTypeEnum::PointerType(t) => t.ptr_type(aspace).as_basic_type_enum(),
71-
AnyTypeEnum::StructType(t) => t.ptr_type(aspace).as_basic_type_enum(),
72-
AnyTypeEnum::VectorType(t) => t.ptr_type(aspace).as_basic_type_enum(),
66+
AnyTypeEnum::ArrayType(_)
67+
| AnyTypeEnum::FloatType(_)
68+
| AnyTypeEnum::FunctionType(_)
69+
| AnyTypeEnum::IntType(_)
70+
| AnyTypeEnum::PointerType(_)
71+
| AnyTypeEnum::StructType(_)
72+
| AnyTypeEnum::VectorType(_) => context.ptr_type(aspace).as_basic_type_enum(),
7373
_ => panic!("unsupported AnyTypeEnum for ptr"),
7474
}
7575
}
@@ -372,6 +372,24 @@ fn classify_param_arm64_darwin<'ctx>(
372372
ParamLowering::Direct(t)
373373
}
374374

375+
fn classify_param_riscv64<'ctx>(td: &TargetData, t: BasicTypeEnum<'ctx>) -> ParamLowering<'ctx> {
376+
let size = td.get_store_size(&t) as u64;
377+
let is_agg = matches!(
378+
t,
379+
BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_)
380+
);
381+
382+
if is_agg && size > 16 {
383+
let align = td.get_abi_alignment(&t) as u32;
384+
return ParamLowering::ByVal {
385+
ty: t.as_any_type_enum(),
386+
align,
387+
};
388+
}
389+
390+
ParamLowering::Direct(t)
391+
}
392+
375393
fn classify_ret_arm64_darwin<'ctx>(
376394
td: &TargetData,
377395
t: Option<BasicTypeEnum<'ctx>>,
@@ -396,19 +414,44 @@ fn classify_ret_arm64_darwin<'ctx>(
396414
RetLowering::Direct(t)
397415
}
398416

417+
fn classify_ret_riscv64<'ctx>(
418+
td: &TargetData,
419+
t: Option<BasicTypeEnum<'ctx>>,
420+
) -> RetLowering<'ctx> {
421+
let Some(t) = t else {
422+
return RetLowering::Void;
423+
};
424+
let size = td.get_store_size(&t) as u64;
425+
let is_agg = matches!(
426+
t,
427+
BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_)
428+
);
429+
430+
if is_agg && size > 16 {
431+
let align = td.get_abi_alignment(&t) as u32;
432+
return RetLowering::SRet {
433+
ty: t.as_any_type_enum(),
434+
align,
435+
};
436+
}
437+
438+
RetLowering::Direct(t)
439+
}
440+
399441
fn classify_param<'ctx>(
400442
context: &'ctx Context,
401443
td: &TargetData,
402444
target: CodegenTarget,
403445
t: BasicTypeEnum<'ctx>,
404446
) -> ParamLowering<'ctx> {
405447
match target {
406-
CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => {
407-
classify_param_x86_64_sysv(context, td, t)
408-
}
409-
CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => {
410-
classify_param_arm64_darwin(td, t)
411-
}
448+
CodegenTarget::LinuxX86_64
449+
| CodegenTarget::DarwinX86_64
450+
| CodegenTarget::FreestandingX86_64 => classify_param_x86_64_sysv(context, td, t),
451+
CodegenTarget::LinuxArm64
452+
| CodegenTarget::DarwinArm64
453+
| CodegenTarget::FreestandingArm64 => classify_param_arm64_darwin(td, t),
454+
CodegenTarget::FreestandingRISCV64 => classify_param_riscv64(td, t),
412455
}
413456
}
414457

@@ -419,10 +462,13 @@ fn classify_ret<'ctx>(
419462
t: Option<BasicTypeEnum<'ctx>>,
420463
) -> RetLowering<'ctx> {
421464
match target {
422-
CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => {
423-
classify_ret_x86_64_sysv(context, td, t)
424-
}
425-
CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => classify_ret_arm64_darwin(td, t),
465+
CodegenTarget::LinuxX86_64
466+
| CodegenTarget::DarwinX86_64
467+
| CodegenTarget::FreestandingX86_64 => classify_ret_x86_64_sysv(context, td, t),
468+
CodegenTarget::LinuxArm64
469+
| CodegenTarget::DarwinArm64
470+
| CodegenTarget::FreestandingArm64 => classify_ret_arm64_darwin(td, t),
471+
CodegenTarget::FreestandingRISCV64 => classify_ret_riscv64(td, t),
426472
}
427473
}
428474

@@ -468,7 +514,7 @@ pub fn lower_extern_c<'ctx>(
468514

469515
if let RetLowering::SRet { ty, .. } = &ret {
470516
// sret param is ptr to the return aggregate
471-
let ptr = any_ptr_basic(ty.clone());
517+
let ptr = any_ptr_basic(context, ty.clone());
472518
llvm_param_types.push(ptr.into());
473519
}
474520

@@ -481,7 +527,7 @@ pub fn lower_extern_c<'ctx>(
481527
}
482528
}
483529
ParamLowering::ByVal { ty, .. } => {
484-
let ptr = any_ptr_basic(ty.clone());
530+
let ptr = any_ptr_basic(context, ty.clone());
485531
llvm_param_types.push(ptr.into());
486532
}
487533
}

llvm/src/codegen/address.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use inkwell::builder::Builder;
1414
use inkwell::context::Context;
1515
use inkwell::module::Module;
1616
use inkwell::types::{AsTypeRef, BasicType, BasicTypeEnum, StructType};
17-
use inkwell::values::{BasicValue, IntValue, PointerValue};
17+
use inkwell::values::{IntValue, PointerValue};
1818
use parser::ast::{Expression, Literal, WaveType};
1919

2020
use std::collections::HashMap;

llvm/src/codegen/ir.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,7 @@ fn resolve_ast_node(n: &ASTNode, named: &HashMap<String, WaveType>) -> ASTNode {
631631
ASTNode::ProtoImpl(p) => ASTNode::ProtoImpl(resolve_proto(p, named)),
632632
ASTNode::Variable(v) => ASTNode::Variable(resolve_variable(v, named)),
633633

634-
ASTNode::TypeAlias(_) | ASTNode::Enum(_) => n.clone(),
634+
ASTNode::TypeAlias(_) => n.clone(),
635635

636636
_ => n.clone(),
637637
}

llvm/src/codegen/legacy.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ pub fn get_llvm_type<'a>(context: &'a Context, ty: &TokenType) -> BasicTypeEnum<
3535
TokenType::TypeChar => context.i8_type().as_basic_type_enum(),
3636
TokenType::TypeByte => context.i8_type().as_basic_type_enum(),
3737
TokenType::TypePointer(inner_type) => {
38-
let inner_llvm_type = get_llvm_type(context, inner_type);
39-
inner_llvm_type
38+
let _inner_llvm_type = get_llvm_type(context, inner_type);
39+
context
4040
.ptr_type(AddressSpace::default())
4141
.as_basic_type_enum()
4242
}
@@ -47,17 +47,17 @@ pub fn get_llvm_type<'a>(context: &'a Context, ty: &TokenType) -> BasicTypeEnum<
4747
.as_basic_type_enum()
4848
}
4949
TokenType::TypeString => context
50-
.i8_type()
5150
.ptr_type(AddressSpace::default())
5251
.as_basic_type_enum(),
5352
_ => panic!("Unsupported type: {:?}", ty),
5453
}
5554
}
5655

56+
#[allow(dead_code)]
5757
pub unsafe fn create_alloc<'a>(
5858
context: &'a Context,
5959
builder: &'a inkwell::builder::Builder<'a>,
60-
function: FunctionValue<'a>,
60+
_function: FunctionValue<'a>,
6161
name: &'a str,
6262
) -> PointerValue<'a> {
6363
let alloca = builder.build_alloca(context.i32_type(), name).unwrap();

llvm/src/codegen/plan.rs

Lines changed: 109 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,21 +126,80 @@ fn reg_phys_group_arm64(token: &str) -> Option<String> {
126126
None
127127
}
128128

129+
fn reg_phys_group_riscv64(token: &str) -> Option<String> {
130+
match token {
131+
"zero" => return Some("x0".to_string()),
132+
"ra" => return Some("x1".to_string()),
133+
"sp" => return Some("x2".to_string()),
134+
"gp" => return Some("x3".to_string()),
135+
"tp" => return Some("x4".to_string()),
136+
"t0" => return Some("x5".to_string()),
137+
"t1" => return Some("x6".to_string()),
138+
"t2" => return Some("x7".to_string()),
139+
"s0" | "fp" => return Some("x8".to_string()),
140+
"s1" => return Some("x9".to_string()),
141+
"a0" => return Some("x10".to_string()),
142+
"a1" => return Some("x11".to_string()),
143+
"a2" => return Some("x12".to_string()),
144+
"a3" => return Some("x13".to_string()),
145+
"a4" => return Some("x14".to_string()),
146+
"a5" => return Some("x15".to_string()),
147+
"a6" => return Some("x16".to_string()),
148+
"a7" => return Some("x17".to_string()),
149+
"s2" => return Some("x18".to_string()),
150+
"s3" => return Some("x19".to_string()),
151+
"s4" => return Some("x20".to_string()),
152+
"s5" => return Some("x21".to_string()),
153+
"s6" => return Some("x22".to_string()),
154+
"s7" => return Some("x23".to_string()),
155+
"s8" => return Some("x24".to_string()),
156+
"s9" => return Some("x25".to_string()),
157+
"s10" => return Some("x26".to_string()),
158+
"s11" => return Some("x27".to_string()),
159+
"t3" => return Some("x28".to_string()),
160+
"t4" => return Some("x29".to_string()),
161+
"t5" => return Some("x30".to_string()),
162+
"t6" => return Some("x31".to_string()),
163+
_ => {}
164+
}
165+
166+
if let Some(num) = token.strip_prefix('x') {
167+
if num.chars().all(|c| c.is_ascii_digit()) && !num.is_empty() {
168+
if let Ok(n) = num.parse::<u32>() {
169+
if n <= 31 {
170+
return Some(format!("x{}", n));
171+
}
172+
}
173+
}
174+
}
175+
176+
None
177+
}
178+
129179
/// Decide whether user token is a real register or a constraint class.
130180
fn parse_token(target: CodegenTarget, raw: &str) -> RegToken {
131181
let raw_norm = normalize_token(raw);
132182
let phys_group = match target {
133-
CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => {
183+
CodegenTarget::LinuxX86_64
184+
| CodegenTarget::DarwinX86_64
185+
| CodegenTarget::FreestandingX86_64 => {
134186
reg_phys_group_x86_64(&raw_norm).map(|s| s.to_string())
135187
}
136-
CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => reg_phys_group_arm64(&raw_norm),
188+
CodegenTarget::LinuxArm64
189+
| CodegenTarget::DarwinArm64
190+
| CodegenTarget::FreestandingArm64 => reg_phys_group_arm64(&raw_norm),
191+
CodegenTarget::FreestandingRISCV64 => reg_phys_group_riscv64(&raw_norm),
137192
};
138193
RegToken {
139194
raw_norm,
140195
phys_group,
141196
}
142197
}
143198

199+
fn is_valid_constraint_class(token: &str) -> bool {
200+
matches!(token, "r" | "m" | "rm" | "i" | "ri" | "im" | "irm")
201+
}
202+
144203
/// For conservative kernel mode:
145204
/// - ALWAYS clobber memory + flags-ish
146205
/// - If ANY operand uses a *class constraint* (no concrete phys reg),
@@ -155,15 +214,20 @@ fn build_default_clobbers(
155214
match mode {
156215
AsmSafetyMode::ConservativeKernel => {
157216
let mut clobbers = match target {
158-
CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => vec![
217+
CodegenTarget::LinuxX86_64
218+
| CodegenTarget::DarwinX86_64
219+
| CodegenTarget::FreestandingX86_64 => vec![
159220
"~{memory}".to_string(),
160221
"~{dirflag}".to_string(),
161222
"~{fpsr}".to_string(),
162223
"~{flags}".to_string(),
163224
],
164-
CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => {
225+
CodegenTarget::LinuxArm64
226+
| CodegenTarget::DarwinArm64
227+
| CodegenTarget::FreestandingArm64 => {
165228
vec!["~{memory}".to_string(), "~{cc}".to_string()]
166229
}
230+
CodegenTarget::FreestandingRISCV64 => vec!["~{memory}".to_string()],
167231
};
168232

169233
// Empty barrier-like asm blocks must not implicitly clobber every GPR.
@@ -199,7 +263,9 @@ fn build_default_clobbers(
199263
}
200264

201265
match target {
202-
CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => {
266+
CodegenTarget::LinuxX86_64
267+
| CodegenTarget::DarwinX86_64
268+
| CodegenTarget::FreestandingX86_64 => {
203269
const GPRS: [&str; 16] = [
204270
"rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp", "r8", "r9", "r10",
205271
"r11", "r12", "r13", "r14", "r15",
@@ -211,7 +277,9 @@ fn build_default_clobbers(
211277
}
212278
}
213279
}
214-
CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => {
280+
CodegenTarget::LinuxArm64
281+
| CodegenTarget::DarwinArm64
282+
| CodegenTarget::FreestandingArm64 => {
215283
for n in 0..=30u32 {
216284
if n == 18 {
217285
continue;
@@ -222,6 +290,17 @@ fn build_default_clobbers(
222290
}
223291
}
224292
}
293+
CodegenTarget::FreestandingRISCV64 => {
294+
for n in [
295+
1u32, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
296+
23, 24, 25, 26, 27, 28, 29, 30, 31,
297+
] {
298+
let r = format!("x{}", n);
299+
if !used_phys.contains(&r) {
300+
clobbers.push(format!("~{{{}}}", r));
301+
}
302+
}
303+
}
225304
}
226305

227306
clobbers
@@ -265,18 +344,26 @@ fn gcc_percent_to_llvm_dollar(s: &str) -> String {
265344

266345
fn normalize_special_clobber(target: CodegenTarget, token: &str) -> Option<String> {
267346
match target {
268-
CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => match token {
347+
CodegenTarget::LinuxX86_64
348+
| CodegenTarget::DarwinX86_64
349+
| CodegenTarget::FreestandingX86_64 => match token {
269350
"memory" => Some("~{memory}".to_string()),
270351
"cc" | "flags" | "eflags" | "rflags" => Some("~{flags}".to_string()),
271352
"dirflag" => Some("~{dirflag}".to_string()),
272353
"fpsr" => Some("~{fpsr}".to_string()),
273354
_ => None,
274355
},
275-
CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => match token {
356+
CodegenTarget::LinuxArm64
357+
| CodegenTarget::DarwinArm64
358+
| CodegenTarget::FreestandingArm64 => match token {
276359
"memory" => Some("~{memory}".to_string()),
277360
"cc" | "flags" | "eflags" | "rflags" => Some("~{cc}".to_string()),
278361
_ => None,
279362
},
363+
CodegenTarget::FreestandingRISCV64 => match token {
364+
"memory" => Some("~{memory}".to_string()),
365+
_ => None,
366+
},
280367
}
281368
}
282369

@@ -376,6 +463,13 @@ impl<'a> AsmPlan<'a> {
376463
for (reg, out_target) in outputs_raw {
377464
let t = parse_token(target, reg);
378465

466+
if t.phys_group.is_none() && !is_valid_constraint_class(&t.raw_norm) {
467+
panic!(
468+
"asm output register/constraint '{}' is not valid for target {:?}",
469+
reg, target
470+
);
471+
}
472+
379473
// real reg outputs: disallow duplicates by physical group
380474
if let Some(pg) = &t.phys_group {
381475
if !used_out_phys.insert(pg.clone()) {
@@ -404,6 +498,13 @@ impl<'a> AsmPlan<'a> {
404498
for (reg, expr) in inputs_raw {
405499
let t = parse_token(target, reg);
406500

501+
if t.phys_group.is_none() && !is_valid_constraint_class(&t.raw_norm) {
502+
panic!(
503+
"asm input register/constraint '{}' is not valid for target {:?}",
504+
reg, target
505+
);
506+
}
507+
407508
// real reg inputs: disallow duplicates by physical group
408509
if let Some(pg) = &t.phys_group {
409510
if !used_in_phys.insert(pg.clone()) {

0 commit comments

Comments
 (0)