diff --git a/llvm/src/codegen/abi_c.rs b/llvm/src/codegen/abi_c.rs index 95eed736..a9cd7ef4 100644 --- a/llvm/src/codegen/abi_c.rs +++ b/llvm/src/codegen/abi_c.rs @@ -60,16 +60,16 @@ fn is_float_ty<'ctx>(td: &TargetData, t: BasicTypeEnum<'ctx>) -> Option { } } -fn any_ptr_basic<'ctx>(ty: AnyTypeEnum<'ctx>) -> BasicTypeEnum<'ctx> { +fn any_ptr_basic<'ctx>(context: &'ctx Context, ty: AnyTypeEnum<'ctx>) -> BasicTypeEnum<'ctx> { let aspace = AddressSpace::default(); match ty { - AnyTypeEnum::ArrayType(t) => t.ptr_type(aspace).as_basic_type_enum(), - AnyTypeEnum::FloatType(t) => t.ptr_type(aspace).as_basic_type_enum(), - AnyTypeEnum::FunctionType(t) => t.ptr_type(aspace).as_basic_type_enum(), - AnyTypeEnum::IntType(t) => t.ptr_type(aspace).as_basic_type_enum(), - AnyTypeEnum::PointerType(t) => t.ptr_type(aspace).as_basic_type_enum(), - AnyTypeEnum::StructType(t) => t.ptr_type(aspace).as_basic_type_enum(), - AnyTypeEnum::VectorType(t) => t.ptr_type(aspace).as_basic_type_enum(), + AnyTypeEnum::ArrayType(_) + | AnyTypeEnum::FloatType(_) + | AnyTypeEnum::FunctionType(_) + | AnyTypeEnum::IntType(_) + | AnyTypeEnum::PointerType(_) + | AnyTypeEnum::StructType(_) + | AnyTypeEnum::VectorType(_) => context.ptr_type(aspace).as_basic_type_enum(), _ => panic!("unsupported AnyTypeEnum for ptr"), } } @@ -372,6 +372,24 @@ fn classify_param_arm64_darwin<'ctx>( ParamLowering::Direct(t) } +fn classify_param_riscv64<'ctx>(td: &TargetData, t: BasicTypeEnum<'ctx>) -> ParamLowering<'ctx> { + let size = td.get_store_size(&t) as u64; + let is_agg = matches!( + t, + BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_) + ); + + if is_agg && size > 16 { + let align = td.get_abi_alignment(&t) as u32; + return ParamLowering::ByVal { + ty: t.as_any_type_enum(), + align, + }; + } + + ParamLowering::Direct(t) +} + fn classify_ret_arm64_darwin<'ctx>( td: &TargetData, t: Option>, @@ -396,6 +414,30 @@ fn classify_ret_arm64_darwin<'ctx>( RetLowering::Direct(t) } +fn classify_ret_riscv64<'ctx>( + td: &TargetData, + t: Option>, +) -> RetLowering<'ctx> { + let Some(t) = t else { + return RetLowering::Void; + }; + let size = td.get_store_size(&t) as u64; + let is_agg = matches!( + t, + BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_) + ); + + if is_agg && size > 16 { + let align = td.get_abi_alignment(&t) as u32; + return RetLowering::SRet { + ty: t.as_any_type_enum(), + align, + }; + } + + RetLowering::Direct(t) +} + fn classify_param<'ctx>( context: &'ctx Context, td: &TargetData, @@ -403,12 +445,13 @@ fn classify_param<'ctx>( t: BasicTypeEnum<'ctx>, ) -> ParamLowering<'ctx> { match target { - CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => { - classify_param_x86_64_sysv(context, td, t) - } - CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => { - classify_param_arm64_darwin(td, t) - } + CodegenTarget::LinuxX86_64 + | CodegenTarget::DarwinX86_64 + | CodegenTarget::FreestandingX86_64 => classify_param_x86_64_sysv(context, td, t), + CodegenTarget::LinuxArm64 + | CodegenTarget::DarwinArm64 + | CodegenTarget::FreestandingArm64 => classify_param_arm64_darwin(td, t), + CodegenTarget::FreestandingRISCV64 => classify_param_riscv64(td, t), } } @@ -419,10 +462,13 @@ fn classify_ret<'ctx>( t: Option>, ) -> RetLowering<'ctx> { match target { - CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => { - classify_ret_x86_64_sysv(context, td, t) - } - CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => classify_ret_arm64_darwin(td, t), + CodegenTarget::LinuxX86_64 + | CodegenTarget::DarwinX86_64 + | CodegenTarget::FreestandingX86_64 => classify_ret_x86_64_sysv(context, td, t), + CodegenTarget::LinuxArm64 + | CodegenTarget::DarwinArm64 + | CodegenTarget::FreestandingArm64 => classify_ret_arm64_darwin(td, t), + CodegenTarget::FreestandingRISCV64 => classify_ret_riscv64(td, t), } } @@ -468,7 +514,7 @@ pub fn lower_extern_c<'ctx>( if let RetLowering::SRet { ty, .. } = &ret { // sret param is ptr to the return aggregate - let ptr = any_ptr_basic(ty.clone()); + let ptr = any_ptr_basic(context, ty.clone()); llvm_param_types.push(ptr.into()); } @@ -481,7 +527,7 @@ pub fn lower_extern_c<'ctx>( } } ParamLowering::ByVal { ty, .. } => { - let ptr = any_ptr_basic(ty.clone()); + let ptr = any_ptr_basic(context, ty.clone()); llvm_param_types.push(ptr.into()); } } diff --git a/llvm/src/codegen/address.rs b/llvm/src/codegen/address.rs index 132d8295..839aa471 100644 --- a/llvm/src/codegen/address.rs +++ b/llvm/src/codegen/address.rs @@ -14,7 +14,7 @@ use inkwell::builder::Builder; use inkwell::context::Context; use inkwell::module::Module; use inkwell::types::{AsTypeRef, BasicType, BasicTypeEnum, StructType}; -use inkwell::values::{BasicValue, IntValue, PointerValue}; +use inkwell::values::{IntValue, PointerValue}; use parser::ast::{Expression, Literal, WaveType}; use std::collections::HashMap; diff --git a/llvm/src/codegen/ir.rs b/llvm/src/codegen/ir.rs index a74af6b2..0d7ac1f6 100644 --- a/llvm/src/codegen/ir.rs +++ b/llvm/src/codegen/ir.rs @@ -631,7 +631,7 @@ fn resolve_ast_node(n: &ASTNode, named: &HashMap) -> ASTNode { ASTNode::ProtoImpl(p) => ASTNode::ProtoImpl(resolve_proto(p, named)), ASTNode::Variable(v) => ASTNode::Variable(resolve_variable(v, named)), - ASTNode::TypeAlias(_) | ASTNode::Enum(_) => n.clone(), + ASTNode::TypeAlias(_) => n.clone(), _ => n.clone(), } diff --git a/llvm/src/codegen/legacy.rs b/llvm/src/codegen/legacy.rs index 06107b11..8e021c5b 100644 --- a/llvm/src/codegen/legacy.rs +++ b/llvm/src/codegen/legacy.rs @@ -35,8 +35,8 @@ pub fn get_llvm_type<'a>(context: &'a Context, ty: &TokenType) -> BasicTypeEnum< TokenType::TypeChar => context.i8_type().as_basic_type_enum(), TokenType::TypeByte => context.i8_type().as_basic_type_enum(), TokenType::TypePointer(inner_type) => { - let inner_llvm_type = get_llvm_type(context, inner_type); - inner_llvm_type + let _inner_llvm_type = get_llvm_type(context, inner_type); + context .ptr_type(AddressSpace::default()) .as_basic_type_enum() } @@ -47,17 +47,17 @@ pub fn get_llvm_type<'a>(context: &'a Context, ty: &TokenType) -> BasicTypeEnum< .as_basic_type_enum() } TokenType::TypeString => context - .i8_type() .ptr_type(AddressSpace::default()) .as_basic_type_enum(), _ => panic!("Unsupported type: {:?}", ty), } } +#[allow(dead_code)] pub unsafe fn create_alloc<'a>( context: &'a Context, builder: &'a inkwell::builder::Builder<'a>, - function: FunctionValue<'a>, + _function: FunctionValue<'a>, name: &'a str, ) -> PointerValue<'a> { let alloca = builder.build_alloca(context.i32_type(), name).unwrap(); diff --git a/llvm/src/codegen/plan.rs b/llvm/src/codegen/plan.rs index 764b2b43..4e8c8e68 100644 --- a/llvm/src/codegen/plan.rs +++ b/llvm/src/codegen/plan.rs @@ -126,14 +126,69 @@ fn reg_phys_group_arm64(token: &str) -> Option { None } +fn reg_phys_group_riscv64(token: &str) -> Option { + match token { + "zero" => return Some("x0".to_string()), + "ra" => return Some("x1".to_string()), + "sp" => return Some("x2".to_string()), + "gp" => return Some("x3".to_string()), + "tp" => return Some("x4".to_string()), + "t0" => return Some("x5".to_string()), + "t1" => return Some("x6".to_string()), + "t2" => return Some("x7".to_string()), + "s0" | "fp" => return Some("x8".to_string()), + "s1" => return Some("x9".to_string()), + "a0" => return Some("x10".to_string()), + "a1" => return Some("x11".to_string()), + "a2" => return Some("x12".to_string()), + "a3" => return Some("x13".to_string()), + "a4" => return Some("x14".to_string()), + "a5" => return Some("x15".to_string()), + "a6" => return Some("x16".to_string()), + "a7" => return Some("x17".to_string()), + "s2" => return Some("x18".to_string()), + "s3" => return Some("x19".to_string()), + "s4" => return Some("x20".to_string()), + "s5" => return Some("x21".to_string()), + "s6" => return Some("x22".to_string()), + "s7" => return Some("x23".to_string()), + "s8" => return Some("x24".to_string()), + "s9" => return Some("x25".to_string()), + "s10" => return Some("x26".to_string()), + "s11" => return Some("x27".to_string()), + "t3" => return Some("x28".to_string()), + "t4" => return Some("x29".to_string()), + "t5" => return Some("x30".to_string()), + "t6" => return Some("x31".to_string()), + _ => {} + } + + if let Some(num) = token.strip_prefix('x') { + if num.chars().all(|c| c.is_ascii_digit()) && !num.is_empty() { + if let Ok(n) = num.parse::() { + if n <= 31 { + return Some(format!("x{}", n)); + } + } + } + } + + None +} + /// Decide whether user token is a real register or a constraint class. fn parse_token(target: CodegenTarget, raw: &str) -> RegToken { let raw_norm = normalize_token(raw); let phys_group = match target { - CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => { + CodegenTarget::LinuxX86_64 + | CodegenTarget::DarwinX86_64 + | CodegenTarget::FreestandingX86_64 => { reg_phys_group_x86_64(&raw_norm).map(|s| s.to_string()) } - CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => reg_phys_group_arm64(&raw_norm), + CodegenTarget::LinuxArm64 + | CodegenTarget::DarwinArm64 + | CodegenTarget::FreestandingArm64 => reg_phys_group_arm64(&raw_norm), + CodegenTarget::FreestandingRISCV64 => reg_phys_group_riscv64(&raw_norm), }; RegToken { raw_norm, @@ -141,6 +196,10 @@ fn parse_token(target: CodegenTarget, raw: &str) -> RegToken { } } +fn is_valid_constraint_class(token: &str) -> bool { + matches!(token, "r" | "m" | "rm" | "i" | "ri" | "im" | "irm") +} + /// For conservative kernel mode: /// - ALWAYS clobber memory + flags-ish /// - If ANY operand uses a *class constraint* (no concrete phys reg), @@ -155,15 +214,20 @@ fn build_default_clobbers( match mode { AsmSafetyMode::ConservativeKernel => { let mut clobbers = match target { - CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => vec![ + CodegenTarget::LinuxX86_64 + | CodegenTarget::DarwinX86_64 + | CodegenTarget::FreestandingX86_64 => vec![ "~{memory}".to_string(), "~{dirflag}".to_string(), "~{fpsr}".to_string(), "~{flags}".to_string(), ], - CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => { + CodegenTarget::LinuxArm64 + | CodegenTarget::DarwinArm64 + | CodegenTarget::FreestandingArm64 => { vec!["~{memory}".to_string(), "~{cc}".to_string()] } + CodegenTarget::FreestandingRISCV64 => vec!["~{memory}".to_string()], }; // Empty barrier-like asm blocks must not implicitly clobber every GPR. @@ -199,7 +263,9 @@ fn build_default_clobbers( } match target { - CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => { + CodegenTarget::LinuxX86_64 + | CodegenTarget::DarwinX86_64 + | CodegenTarget::FreestandingX86_64 => { const GPRS: [&str; 16] = [ "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", @@ -211,7 +277,9 @@ fn build_default_clobbers( } } } - CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => { + CodegenTarget::LinuxArm64 + | CodegenTarget::DarwinArm64 + | CodegenTarget::FreestandingArm64 => { for n in 0..=30u32 { if n == 18 { continue; @@ -222,6 +290,17 @@ fn build_default_clobbers( } } } + CodegenTarget::FreestandingRISCV64 => { + for n in [ + 1u32, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, 31, + ] { + let r = format!("x{}", n); + if !used_phys.contains(&r) { + clobbers.push(format!("~{{{}}}", r)); + } + } + } } clobbers @@ -265,18 +344,26 @@ fn gcc_percent_to_llvm_dollar(s: &str) -> String { fn normalize_special_clobber(target: CodegenTarget, token: &str) -> Option { match target { - CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => match token { + CodegenTarget::LinuxX86_64 + | CodegenTarget::DarwinX86_64 + | CodegenTarget::FreestandingX86_64 => match token { "memory" => Some("~{memory}".to_string()), "cc" | "flags" | "eflags" | "rflags" => Some("~{flags}".to_string()), "dirflag" => Some("~{dirflag}".to_string()), "fpsr" => Some("~{fpsr}".to_string()), _ => None, }, - CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => match token { + CodegenTarget::LinuxArm64 + | CodegenTarget::DarwinArm64 + | CodegenTarget::FreestandingArm64 => match token { "memory" => Some("~{memory}".to_string()), "cc" | "flags" | "eflags" | "rflags" => Some("~{cc}".to_string()), _ => None, }, + CodegenTarget::FreestandingRISCV64 => match token { + "memory" => Some("~{memory}".to_string()), + _ => None, + }, } } @@ -376,6 +463,13 @@ impl<'a> AsmPlan<'a> { for (reg, out_target) in outputs_raw { let t = parse_token(target, reg); + if t.phys_group.is_none() && !is_valid_constraint_class(&t.raw_norm) { + panic!( + "asm output register/constraint '{}' is not valid for target {:?}", + reg, target + ); + } + // real reg outputs: disallow duplicates by physical group if let Some(pg) = &t.phys_group { if !used_out_phys.insert(pg.clone()) { @@ -404,6 +498,13 @@ impl<'a> AsmPlan<'a> { for (reg, expr) in inputs_raw { let t = parse_token(target, reg); + if t.phys_group.is_none() && !is_valid_constraint_class(&t.raw_norm) { + panic!( + "asm input register/constraint '{}' is not valid for target {:?}", + reg, target + ); + } + // real reg inputs: disallow duplicates by physical group if let Some(pg) = &t.phys_group { if !used_in_phys.insert(pg.clone()) { diff --git a/llvm/src/codegen/target.rs b/llvm/src/codegen/target.rs index dcca8436..c2249b79 100644 --- a/llvm/src/codegen/target.rs +++ b/llvm/src/codegen/target.rs @@ -19,6 +19,9 @@ pub enum CodegenTarget { LinuxArm64, DarwinX86_64, DarwinArm64, + FreestandingX86_64, + FreestandingArm64, + FreestandingRISCV64, } impl CodegenTarget { @@ -27,8 +30,10 @@ impl CodegenTarget { let is_x86_64 = t.starts_with("x86_64"); let is_arm64 = t.starts_with("arm64") || t.starts_with("aarch64"); + let is_riscv64 = t.starts_with("riscv64"); let is_linux = t.contains("linux"); let is_darwin = t.contains("darwin"); + let is_freestanding = t.contains("-none-") || t.ends_with("-none") || t.contains("elf"); if is_x86_64 && is_linux { return Some(Self::LinuxX86_64); @@ -42,6 +47,15 @@ impl CodegenTarget { if is_arm64 && is_darwin { return Some(Self::DarwinArm64); } + if is_x86_64 && is_freestanding { + return Some(Self::FreestandingX86_64); + } + if is_arm64 && is_freestanding { + return Some(Self::FreestandingArm64); + } + if is_riscv64 && is_freestanding { + return Some(Self::FreestandingRISCV64); + } None } @@ -62,6 +76,9 @@ impl CodegenTarget { Self::LinuxArm64 => "linux arm64", Self::DarwinX86_64 => "darwin x86_64", Self::DarwinArm64 => "darwin arm64", + Self::FreestandingX86_64 => "freestanding x86_64", + Self::FreestandingArm64 => "freestanding arm64", + Self::FreestandingRISCV64 => "freestanding riscv64", } } } @@ -73,7 +90,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 and darwin x86_64/arm64 (Windows not supported yet)", + "unsupported target triple '{}': Wave currently supports linux x86_64/arm64, darwin x86_64/arm64, and freestanding x86_64/arm64/riscv64", raw ); } @@ -86,7 +103,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 and darwin x86_64/arm64 (Windows not supported yet)", + "unsupported target triple '{}': Wave currently supports linux x86_64/arm64, darwin x86_64/arm64, and freestanding x86_64/arm64/riscv64", raw ); } diff --git a/llvm/src/expression/rvalue/asm.rs b/llvm/src/expression/rvalue/asm.rs index 7c4df10f..bb0369d6 100644 --- a/llvm/src/expression/rvalue/asm.rs +++ b/llvm/src/expression/rvalue/asm.rs @@ -23,8 +23,13 @@ use parser::ast::{Expression, Literal, WaveType}; fn inline_asm_dialect_for_target(target: CodegenTarget) -> InlineAsmDialect { match target { - CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => InlineAsmDialect::Intel, - CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => InlineAsmDialect::ATT, + CodegenTarget::LinuxX86_64 + | CodegenTarget::DarwinX86_64 + | CodegenTarget::FreestandingX86_64 => InlineAsmDialect::Intel, + CodegenTarget::LinuxArm64 + | CodegenTarget::DarwinArm64 + | CodegenTarget::FreestandingArm64 + | CodegenTarget::FreestandingRISCV64 => InlineAsmDialect::ATT, } } diff --git a/llvm/src/expression/rvalue/assign.rs b/llvm/src/expression/rvalue/assign.rs index e7c109c8..6279a566 100644 --- a/llvm/src/expression/rvalue/assign.rs +++ b/llvm/src/expression/rvalue/assign.rs @@ -14,7 +14,7 @@ use super::ExprGenEnv; use crate::codegen::types::TypeFlavor; use crate::codegen::{generate_address_ir, wave_type_to_llvm_type}; use crate::statement::variable::{coerce_basic_value, CoercionMode}; -use inkwell::types::{AnyTypeEnum, AsTypeRef, BasicTypeEnum}; +use inkwell::types::{AsTypeRef, BasicTypeEnum}; use inkwell::values::{BasicValue, BasicValueEnum}; use parser::ast::{AssignOperator, Expression, WaveType}; diff --git a/llvm/src/expression/rvalue/index.rs b/llvm/src/expression/rvalue/index.rs index ddd917b3..833595ae 100644 --- a/llvm/src/expression/rvalue/index.rs +++ b/llvm/src/expression/rvalue/index.rs @@ -12,7 +12,6 @@ use super::ExprGenEnv; use crate::codegen::generate_address_and_type_ir; -use inkwell::types::BasicType; use inkwell::values::{BasicValue, BasicValueEnum}; use parser::ast::Expression; diff --git a/llvm/src/expression/rvalue/literals.rs b/llvm/src/expression/rvalue/literals.rs index 0a2dd5fc..397b4a35 100644 --- a/llvm/src/expression/rvalue/literals.rs +++ b/llvm/src/expression/rvalue/literals.rs @@ -24,14 +24,6 @@ fn parse_signed_decimal<'a>(s: &'a str) -> (bool, &'a str) { } } -fn is_zero_decimal(s: &str) -> bool { - let s = s.trim(); - let s = s.strip_prefix('+').unwrap_or(s); - let s = s.strip_prefix('-').unwrap_or(s); - - !s.is_empty() && s.chars().all(|c| c == '0') -} - fn parse_int_radix(s: &str) -> (StringRadix, &str) { if let Some(rest) = s.strip_prefix("0b").or_else(|| s.strip_prefix("0B")) { (StringRadix::Binary, rest) diff --git a/llvm/src/expression/rvalue/pointers.rs b/llvm/src/expression/rvalue/pointers.rs index d32cf055..13800915 100644 --- a/llvm/src/expression/rvalue/pointers.rs +++ b/llvm/src/expression/rvalue/pointers.rs @@ -19,6 +19,7 @@ use inkwell::types::{BasicType, BasicTypeEnum}; use inkwell::values::{BasicValue, BasicValueEnum}; use parser::ast::{Expression, WaveType}; +#[allow(dead_code)] fn push_deref_into_base(expr: &Expression) -> Expression { match expr { Expression::Grouped(inner) => Expression::Grouped(Box::new(push_deref_into_base(inner))), diff --git a/llvm/src/expression/rvalue/structs.rs b/llvm/src/expression/rvalue/structs.rs index 8a025902..93a74916 100644 --- a/llvm/src/expression/rvalue/structs.rs +++ b/llvm/src/expression/rvalue/structs.rs @@ -45,7 +45,7 @@ pub(crate) fn gen_struct_literal<'ctx, 'a>( .get_field_type_at_index(idx) .unwrap_or_else(|| panic!("No field type at index {} for struct '{}'", idx, name)); - let mut field_val = env.gen(field_expr, Some(expected_field_ty)); + let field_val = env.gen(field_expr, Some(expected_field_ty)); if field_val.get_type() != expected_field_ty { panic!( diff --git a/llvm/src/lib.rs b/llvm/src/lib.rs index c8051d72..468a9ea0 100644 --- a/llvm/src/lib.rs +++ b/llvm/src/lib.rs @@ -17,11 +17,6 @@ pub mod importgen; pub mod statement; pub fn backend() -> Option { - unsafe { - let mut major: u32 = 0; - let mut minor: u32 = 0; - let mut patch: u32 = 0; - - Some(format!("LLVM {}.{}.{}", major, minor, patch)) - } + let (major, minor, patch) = (0_u32, 0_u32, 0_u32); + Some(format!("LLVM {}.{}.{}", major, minor, patch)) } diff --git a/llvm/src/statement/asm.rs b/llvm/src/statement/asm.rs index d02ce1f7..1c227208 100644 --- a/llvm/src/statement/asm.rs +++ b/llvm/src/statement/asm.rs @@ -65,8 +65,12 @@ fn reg_width_bits(reg: &str) -> Option { fn reg_width_bits_for_target(target: CodegenTarget, reg: &str) -> Option { match target { - CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => reg_width_bits(reg), - CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => { + CodegenTarget::LinuxX86_64 + | CodegenTarget::DarwinX86_64 + | CodegenTarget::FreestandingX86_64 => reg_width_bits(reg), + CodegenTarget::LinuxArm64 + | CodegenTarget::DarwinArm64 + | CodegenTarget::FreestandingArm64 => { if reg.len() >= 2 { let (prefix, num) = reg.split_at(1); if num.chars().all(|c| c.is_ascii_digit()) && !num.is_empty() { @@ -83,6 +87,57 @@ fn reg_width_bits_for_target(target: CodegenTarget, reg: &str) -> Option { } None } + CodegenTarget::FreestandingRISCV64 => { + if reg == "zero" { + return Some(64); + } + if matches!( + reg, + "ra" | "sp" + | "gp" + | "tp" + | "t0" + | "t1" + | "t2" + | "t3" + | "t4" + | "t5" + | "t6" + | "s0" + | "fp" + | "s1" + | "s2" + | "s3" + | "s4" + | "s5" + | "s6" + | "s7" + | "s8" + | "s9" + | "s10" + | "s11" + | "a0" + | "a1" + | "a2" + | "a3" + | "a4" + | "a5" + | "a6" + | "a7" + ) { + return Some(64); + } + if let Some(num) = reg.strip_prefix('x') { + if num.chars().all(|c| c.is_ascii_digit()) && !num.is_empty() { + if let Ok(n) = num.parse::() { + if n <= 31 { + return Some(64); + } + } + } + } + None + } } } @@ -101,8 +156,13 @@ fn extract_reg_from_constraint(c: &str) -> Option { fn inline_asm_dialect_for_target(target: CodegenTarget) -> InlineAsmDialect { match target { - CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => InlineAsmDialect::Intel, - CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => InlineAsmDialect::ATT, + CodegenTarget::LinuxX86_64 + | CodegenTarget::DarwinX86_64 + | CodegenTarget::FreestandingX86_64 => InlineAsmDialect::Intel, + CodegenTarget::LinuxArm64 + | CodegenTarget::DarwinArm64 + | CodegenTarget::FreestandingArm64 + | CodegenTarget::FreestandingRISCV64 => InlineAsmDialect::ATT, } } diff --git a/llvm/src/statement/assign.rs b/llvm/src/statement/assign.rs index 1c14d5b5..08f16567 100644 --- a/llvm/src/statement/assign.rs +++ b/llvm/src/statement/assign.rs @@ -19,8 +19,8 @@ use crate::expression::rvalue::generate_expression_ir; use crate::statement::variable::{coerce_basic_value, CoercionMode}; use inkwell::module::Module; use inkwell::targets::TargetData; -use inkwell::types::{AnyTypeEnum, BasicType, BasicTypeEnum, StructType}; -use inkwell::values::{BasicValue, BasicValueEnum}; +use inkwell::types::{BasicType, BasicTypeEnum, StructType}; +use inkwell::values::BasicValueEnum; use parser::ast::{Expression, Mutability}; use std::collections::HashMap; diff --git a/llvm/src/statement/control.rs b/llvm/src/statement/control.rs index 0637a94a..4dab10ba 100644 --- a/llvm/src/statement/control.rs +++ b/llvm/src/statement/control.rs @@ -25,7 +25,7 @@ use parser::ast::{ASTNode, Expression, MatchArm, MatchPattern, WaveType}; use std::collections::{HashMap, HashSet}; fn truthy_to_i1<'ctx>( - context: &'ctx inkwell::context::Context, + _context: &'ctx inkwell::context::Context, builder: &'ctx inkwell::builder::Builder<'ctx>, v: BasicValueEnum<'ctx>, name: &str, diff --git a/llvm/src/statement/io.rs b/llvm/src/statement/io.rs index ad44caf9..ae20ffff 100644 --- a/llvm/src/statement/io.rs +++ b/llvm/src/statement/io.rs @@ -218,10 +218,9 @@ pub(super) fn gen_print_literal_ir<'ctx>( global.set_linkage(Linkage::Private); global.set_constant(true); - let printf_type = context.i32_type().fn_type( - &[context.i8_type().ptr_type(AddressSpace::default()).into()], - true, - ); + let printf_type = context + .i32_type() + .fn_type(&[context.ptr_type(AddressSpace::default()).into()], true); let printf_func = module .get_function("printf") .unwrap_or_else(|| module.add_function("printf", printf_type, None)); @@ -261,7 +260,6 @@ pub(super) fn gen_print_format_ir<'ctx>( let mut printf_vals: Vec> = Vec::with_capacity(args.len()); let void_ptr_ty = context - .i8_type() .ptr_type(AddressSpace::default()) .as_basic_type_enum(); @@ -375,10 +373,9 @@ pub(super) fn gen_print_format_ir<'ctx>( global.set_linkage(Linkage::Private); global.set_constant(true); - let printf_type = context.i32_type().fn_type( - &[context.i8_type().ptr_type(AddressSpace::default()).into()], - true, - ); + let printf_type = context + .i32_type() + .fn_type(&[context.ptr_type(AddressSpace::default()).into()], true); let printf_func = module .get_function("printf") .unwrap_or_else(|| module.add_function("printf", printf_type, None)); @@ -466,10 +463,9 @@ pub(super) fn gen_input_ir<'ctx>( global.set_linkage(Linkage::Private); global.set_constant(true); - let scanf_type = context.i32_type().fn_type( - &[context.i8_type().ptr_type(AddressSpace::default()).into()], - true, - ); + let scanf_type = context + .i32_type() + .fn_type(&[context.ptr_type(AddressSpace::default()).into()], true); let scanf_func = module .get_function("scanf") .unwrap_or_else(|| module.add_function("scanf", scanf_type, None)); diff --git a/llvm/src/statement/variable.rs b/llvm/src/statement/variable.rs index 52f18e30..1882c512 100644 --- a/llvm/src/statement/variable.rs +++ b/llvm/src/statement/variable.rs @@ -32,7 +32,7 @@ pub enum CoercionMode { } pub fn coerce_basic_value<'ctx>( - context: &'ctx inkwell::context::Context, + _context: &'ctx inkwell::context::Context, builder: &'ctx inkwell::builder::Builder<'ctx>, val: BasicValueEnum<'ctx>, expected: BasicTypeEnum<'ctx>, diff --git a/src/cli.rs b/src/cli.rs index 5c4b26e5..80aa8215 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -234,7 +234,11 @@ fn dispatch_build(global: &Global, build: &BuildRequest) -> Result<(), CliError> if build.emit.is_check() { for input in &classified { unsafe { - runner::check_wave_file(&input.path, &effective_global.debug, &effective_global.dep); + runner::check_wave_file( + &input.path, + &effective_global.debug, + &effective_global.dep, + ); } } return Ok(()); @@ -1016,7 +1020,9 @@ fn parse_build(args: &[String]) -> Result { } "--error-format" => { let Some(v) = args.get(i + 1) else { - return Err(CliError::usage("missing value: --error-format ")); + return Err(CliError::usage( + "missing value: --error-format ", + )); }; build.error_format = parse_error_format(v)?; i += 2; @@ -1100,10 +1106,7 @@ fn parse_print(args: &[String]) -> Result { continue; } - return Err(CliError::usage(format!( - "unknown option for print: {}", - a - ))); + return Err(CliError::usage(format!("unknown option for print: {}", a))); } Ok(CliCommand::Print { item, target }) @@ -1343,7 +1346,9 @@ fn validate_build_request( )); } if build.run { - return Err(CliError::usage("--emit=check cannot be combined with --run")); + return Err(CliError::usage( + "--emit=check cannot be combined with --run", + )); } if build.output.is_some() || build.out_dir.is_some() { return Err(CliError::usage( @@ -1400,7 +1405,8 @@ fn validate_build_request( } let need_link = emit_set.contains(&EmitKind::Bin) || build.run; - if (build.entry.is_some() || build.linker_script.is_some() || build.no_start_files) && !need_link + if (build.entry.is_some() || build.linker_script.is_some() || build.no_start_files) + && !need_link { return Err(CliError::usage( "--entry/--linker-script/--no-start-files require a link stage (emit includes bin)", @@ -1408,7 +1414,10 @@ fn validate_build_request( } if build.output.is_some() { - let compile_count = classified.iter().filter(|i| i.kind != InputKind::Obj).count(); + let compile_count = classified + .iter() + .filter(|i| i.kind != InputKind::Obj) + .count(); let has_bin = emit_set.contains(&EmitKind::Bin) || build.run; if !has_bin { @@ -1433,14 +1442,18 @@ fn create_build_plan( } let emit_set = build.emit.as_set().expect("non-check emit set expected"); - let need_objects = emit_set.contains(&EmitKind::Obj) || emit_set.contains(&EmitKind::Bin) || build.run; + let need_objects = + emit_set.contains(&EmitKind::Obj) || emit_set.contains(&EmitKind::Bin) || build.run; let need_link = emit_set.contains(&EmitKind::Bin) || build.run; if !need_objects && !need_link { return Ok(BuildPlan::default()); } - let compile_total = classified.iter().filter(|i| i.kind != InputKind::Obj).count(); + let compile_total = classified + .iter() + .filter(|i| i.kind != InputKind::Obj) + .count(); let mut compile_index = 0usize; let mut plan = BuildPlan::default(); @@ -1664,12 +1677,15 @@ fn execute_explicit_emit_artifacts( continue; } - let output = resolve_extra_emit_output_path(build, input, kind, input_index, total_inputs); + let output = + resolve_extra_emit_output_path(build, input, kind, input_index, total_inputs); ensure_parent_dir(&output)?; match kind { EmitKind::Ast => { - let text = unsafe { runner::emit_wave_ast_text(&input.path, &global.debug, &global.dep) }; + let text = unsafe { + runner::emit_wave_ast_text(&input.path, &global.debug, &global.dep) + }; fs::write(output, text)?; } EmitKind::Ir => match input.kind { @@ -1727,7 +1743,13 @@ fn execute_explicit_emit_artifacts( emit_ir_text_via_clang(global, &text, &output, EmitKind::Asm)?; } InputKind::Ir | InputKind::Bc => { - compile_lowering_with_clang(global, &input.path, input.kind, &output, EmitKind::Asm)?; + compile_lowering_with_clang( + global, + &input.path, + input.kind, + &output, + EmitKind::Asm, + )?; } InputKind::Asm => copy_if_different(&input.path, &output)?, _ => {} @@ -1753,13 +1775,16 @@ fn compile_lowering_with_clang( emit_kind: EmitKind, ) -> Result<(), CliError> { let (bin, args) = build_clang_lowering_args(global, input, input_kind, output, emit_kind); - let output = ProcessCommand::new(&bin).args(&args).output().map_err(|e| { - if e.kind() == ErrorKind::NotFound && bin == "clang" { - CliError::ExternalToolMissing("clang") - } else { - CliError::Io(e) - } - })?; + let output = ProcessCommand::new(&bin) + .args(&args) + .output() + .map_err(|e| { + if e.kind() == ErrorKind::NotFound && bin == "clang" { + CliError::ExternalToolMissing("clang") + } else { + CliError::Io(e) + } + })?; if output.status.success() { return Ok(()); @@ -2014,7 +2039,8 @@ fn dry_run_explicit_emit_steps( continue; } - let output = resolve_extra_emit_output_path(build, input, kind, input_index, total_inputs); + let output = + resolve_extra_emit_output_path(build, input, kind, input_index, total_inputs); let step = match (kind, input.kind) { (EmitKind::Ast, InputKind::Wave) => { format!( @@ -2158,9 +2184,17 @@ fn print_dry_run_json( append_json_field(&mut text, "mode", &json_string(build_mode_label(build))); text.push(','); - append_json_field(&mut text, "emit", &json_string(&render_emit_spec(&build.emit))); + append_json_field( + &mut text, + "emit", + &json_string(&render_emit_spec(&build.emit)), + ); text.push(','); - append_json_field(&mut text, "link_only", if build.link_only { "true" } else { "false" }); + append_json_field( + &mut text, + "link_only", + if build.link_only { "true" } else { "false" }, + ); text.push(','); append_json_field(&mut text, "run", if build.run { "true" } else { "false" }); text.push(','); @@ -2173,7 +2207,11 @@ fn print_dry_run_json( append_json_field( &mut text, "no_start_files", - if build.no_start_files { "true" } else { "false" }, + if build.no_start_files { + "true" + } else { + "false" + }, ); text.push(','); text.push_str("\"entry\":"); @@ -2208,11 +2246,7 @@ fn print_dry_run_json( text.push(','); } text.push('{'); - append_json_field( - &mut text, - "path", - &json_string(&i.path.to_string_lossy()), - ); + append_json_field(&mut text, "path", &json_string(&i.path.to_string_lossy())); text.push(','); append_json_field(&mut text, "kind", &json_string(i.kind.as_str())); text.push('}'); @@ -2275,7 +2309,11 @@ fn print_dry_run_json( if let Some(link_output) = &plan.link_output { let (bin, args) = build_linker_args(global, build, &plan.link_inputs, link_output); text.push('{'); - append_json_field(&mut text, "output", &json_string(&link_output.to_string_lossy())); + append_json_field( + &mut text, + "output", + &json_string(&link_output.to_string_lossy()), + ); text.push(','); append_json_field(&mut text, "command", &json_string(&shell_join(&bin, &args))); text.push('}'); @@ -2326,8 +2364,7 @@ fn shell_quote(s: &str) -> String { } if s.chars().all(|c| { - c.is_ascii_alphanumeric() - || matches!(c, '_' | '-' | '.' | '/' | ':' | '=' | '+' | ',' ) + c.is_ascii_alphanumeric() || matches!(c, '_' | '-' | '.' | '/' | ':' | '=' | '+' | ',') }) { return s.to_string(); } @@ -2395,6 +2432,9 @@ fn supported_targets() -> &'static [&'static str] { "aarch64-unknown-linux-gnu", "x86_64-apple-darwin", "aarch64-apple-darwin", + "x86_64-unknown-none-elf", + "aarch64-unknown-none-elf", + "riscv64-unknown-none-elf", ] } @@ -2414,6 +2454,8 @@ fn cpu_list_for_target(target: &str) -> Vec<&'static str> { vec!["generic", "x86-64", "x86-64-v2", "x86-64-v3"] } else if target.starts_with("aarch64-") { vec!["generic", "cortex-a53", "cortex-a72", "apple-m1"] + } else if target.starts_with("riscv64-") { + vec!["generic-rv64", "rocket", "sifive-u74"] } else { vec!["generic"] } @@ -2424,6 +2466,8 @@ fn target_features_for_target(target: &str) -> Vec<&'static str> { vec!["sse2", "sse4.1", "avx", "avx2"] } else if target.starts_with("aarch64-") { vec!["neon", "fp", "crypto"] + } else if target.starts_with("riscv64-") { + vec!["m", "a", "f", "d", "c"] } else { vec![] } @@ -2556,7 +2600,11 @@ pub fn print_help() { "--no-start-files".color("38,139,235"), "Pass -nostartfiles to linker (link stage only)" ); - println!(" {:<24} {}", "-o ".color("38,139,235"), "Output file"); + println!( + " {:<24} {}", + "-o ".color("38,139,235"), + "Output file" + ); println!( " {:<24} {}", "--out-dir ".color("38,139,235"), @@ -2589,7 +2637,11 @@ pub fn print_help() { "--static".color("38,139,235"), "Request static link mode" ); - println!(" {:<24} {}", "--pie".color("38,139,235"), "Enable PIE mode"); + println!( + " {:<24} {}", + "--pie".color("38,139,235"), + "Enable PIE mode" + ); println!( " {:<24} {}", "--no-pie".color("38,139,235"), diff --git a/src/runner.rs b/src/runner.rs index 231a6d0b..0e1d41f0 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -882,6 +882,7 @@ fn materialize_output_path( output.display().to_string() } +#[allow(dead_code)] fn resolve_output_target( default_output: &str, output: Option<&Path>, @@ -1050,7 +1051,9 @@ pub(crate) unsafe fn emit_wave_ir_text( let ir = match run_panic_guarded(|| unsafe { generate_ir(&ast, opt_flag, &backend_opts) }) { Ok(ir) => ir, - Err((msg, loc)) => emit_codegen_panic_and_exit(file_path, &code, "llvm-ir-generation", msg, loc), + Err((msg, loc)) => { + emit_codegen_panic_and_exit(file_path, &code, "llvm-ir-generation", msg, loc) + } }; if debug.ir { @@ -1060,6 +1063,7 @@ pub(crate) unsafe fn emit_wave_ir_text( ir } +#[allow(dead_code)] pub(crate) unsafe fn run_wave_file( file_path: &Path, opt_flag: &str, @@ -1319,6 +1323,7 @@ pub(crate) unsafe fn object_build_wave_file( object_path } +#[allow(dead_code)] pub(crate) unsafe fn build_wave_file( file_path: &Path, opt_flag: &str, diff --git a/src/version.rs b/src/version.rs index fd79f898..7d16f6d6 100644 --- a/src/version.rs +++ b/src/version.rs @@ -10,7 +10,6 @@ // 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::fs; use std::process::Command; const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -23,6 +22,8 @@ pub(crate) fn version() -> &'static str { pub fn get_os_pretty_name() -> String { #[cfg(target_os = "linux")] { + use std::fs; + if let Ok(content) = fs::read_to_string("/etc/os-release") { for line in content.lines() { if line.starts_with("PRETTY_NAME=") { diff --git a/test/test100.wave b/test/test100.wave new file mode 100644 index 00000000..b3521448 --- /dev/null +++ b/test/test100.wave @@ -0,0 +1,41 @@ +// wave-test: host-os=linux, host-arch=aarch64 +fun main() { + var sockfd: i64; + asm { + "mov x8, #198" + "svc #0" + out("x0") sockfd + in("x0") 2 + in("x1") 2 + in("x2") 0 + } + + var addr: array = [2, 0, 0x1F, 0x90, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + var ret: i64; + asm { + "mov x8, #200" + "svc #0" + out("x0") ret + in("x0") sockfd + in("x1") &addr + in("x2") 16 + } + + var buf: array; + var src: array; + var srclen: i64 = 16; + var n: i64; + asm { + "mov x8, #207" + "svc #0" + out("x0") n + in("x0") sockfd + in("x1") &buf + in("x2") 128 + in("x3") 0 + in("x4") &src + in("x5") &srclen + } + + println("{} {} {}", sockfd, ret, n); +} diff --git a/test/test101.wave b/test/test101.wave new file mode 100644 index 00000000..4979fbd5 --- /dev/null +++ b/test/test101.wave @@ -0,0 +1,24 @@ +// wave-test: host-os=linux, host-arch=riscv64 +fun do_recv(sockfd: i64, buf: ptr>, src: ptr>, srclen: ptr) -> i64 { + var n: i64; + asm { + "ecall" + out("a0") n + in("a7") 207 + in("a0") sockfd + in("a1") buf + in("a2") 128 + in("a3") 0 + in("a4") src + in("a5") srclen + } + return n; +} + +fun main() { + var buf: array; + var src: array; + var srclen: i64 = 16; + let n: i64 = do_recv(3, &buf, &src, &srclen); + println("recvfrom got {} bytes", n); +} diff --git a/test/test102.wave b/test/test102.wave new file mode 100644 index 00000000..3258df2e --- /dev/null +++ b/test/test102.wave @@ -0,0 +1,54 @@ +// wave-test: host-arch=aarch64 +fun main() { + println("=== test102: clobber test start ==="); + + test_clobber_x0(); + test_clobber_multiple(); + test_clobber_memory(); + + println("=== test102: done ==="); +} + +fun test_clobber_x0() { + var before: i64 = 42; + + asm { + "mov x0, #99" + clobber("x0") + } + + println("[clobber x0]"); + println("before = {}", before); +} + +fun test_clobber_multiple() { + var sum1: i64 = 1 + 2 + 3; + + asm { + "mov x0, #0" + "mov x1, #0" + "mov x2, #0" + clobber("x0") + clobber("x1") + clobber("x2") + } + + println("[clobber x0 x1 x2]"); + println("sum before = {}", sum1); +} + +fun test_clobber_memory() { + var buf: i64 = 123; + + println("[clobber memory]"); + println("before = {}", buf); + + asm { + "str x1, [$0]" + in("r") &buf + in("x1") 777 + clobber("memory") + } + + println("after = {}", buf); +} diff --git a/test/test30.wave b/test/test30.wave index 0c5737b3..a65b2c8e 100644 --- a/test/test30.wave +++ b/test/test30.wave @@ -1,3 +1,4 @@ +// wave-test: host-os=linux, host-arch=x86_64 fun main() { var msg_ptr: ptr = "Hello from syscall!\n"; var ret_val: i64; diff --git a/test/test42.wave b/test/test42.wave index 5beb42f4..eb326894 100644 --- a/test/test42.wave +++ b/test/test42.wave @@ -1,3 +1,4 @@ +// wave-test: host-arch=x86_64 fun main() { var result: i64 = asm { "mov rax, 123" diff --git a/test/test49.wave b/test/test49.wave index 8d244284..7338e6fd 100644 --- a/test/test49.wave +++ b/test/test49.wave @@ -1,3 +1,4 @@ +// wave-test: host-os=linux, host-arch=x86_64 fun main() { var dummy_ptr: ptr = "dummy"; var ret_val: i64; diff --git a/test/test50.wave b/test/test50.wave index cf7773ec..c200a738 100644 --- a/test/test50.wave +++ b/test/test50.wave @@ -1,3 +1,4 @@ +// wave-test: host-os=linux, host-arch=x86_64 fun calc_sum(x: i32, y: i32) -> i32 { println("Calculating sum of {} and {}", x, y); var result: i32 = x + y; diff --git a/test/test51.wave b/test/test51.wave index a56579ee..4f344e19 100644 --- a/test/test51.wave +++ b/test/test51.wave @@ -1,3 +1,4 @@ +// wave-test: host-os=linux, host-arch=x86_64 fun factorial_simple(n: i32) -> i32 { var result: i32 = 1; var i: i32 = 1; diff --git a/test/test54.wave b/test/test54.wave index e09d69b7..5e1eede0 100644 --- a/test/test54.wave +++ b/test/test54.wave @@ -1,7 +1,8 @@ +// wave-test: host-arch=x86_64 fun main() { asm { "mov ah, 0x0e" "mov al, 0x48" "int 0x10" } -} \ No newline at end of file +} diff --git a/test/test56.wave b/test/test56.wave index 73a2f062..3db8f0ae 100644 --- a/test/test56.wave +++ b/test/test56.wave @@ -1,3 +1,4 @@ +// wave-test: host-os=linux, host-arch=x86_64 // ========================= // Linux x86_64 syscalls // args: rax, rdi, rsi, rdx, r10, r8, r9 diff --git a/test/test60.wave b/test/test60.wave index 1ebf1751..079c2d9f 100644 --- a/test/test60.wave +++ b/test/test60.wave @@ -1,3 +1,4 @@ +// wave-test: host-os=linux, host-arch=x86_64 const SYS_WRITE: i32 = 1; const SYS_READ: i32 = 0; diff --git a/test/test61.wave b/test/test61.wave index 9f388cdd..37906dde 100644 --- a/test/test61.wave +++ b/test/test61.wave @@ -1,3 +1,4 @@ +// wave-test: host-os=linux, host-arch=x86_64 const AF_INET: i32 = 2; const SOCK_DGRAM: i32 = 2; const SYS_SOCKET: i64 = 41; diff --git a/test/test62.wave b/test/test62.wave index 2716c6a9..29522c90 100644 --- a/test/test62.wave +++ b/test/test62.wave @@ -1,3 +1,4 @@ +// wave-test: host-os=linux, host-arch=x86_64 const AF_INET: i32 = 2; const SOCK_DGRAM: i32 = 2; const SYS_SOCKET: i64 = 41; diff --git a/test/test77.wave b/test/test77.wave index cf46e5dc..339a6c19 100644 --- a/test/test77.wave +++ b/test/test77.wave @@ -1,3 +1,4 @@ +// wave-test: host-arch=x86_64 fun main() { println("=== test77: clobber test start ==="); diff --git a/test/test92.wave b/test/test92.wave new file mode 100644 index 00000000..a20e0f0c --- /dev/null +++ b/test/test92.wave @@ -0,0 +1,14 @@ +// wave-test: host-os=linux, host-arch=aarch64 +fun main() { + var msg_ptr: ptr = "Hello from syscall!\n"; + var ret_val: i64; + + asm { + "mov x8, #64" + "svc #0" + in("x0") 1 + in("x1") msg_ptr + in("x2") 20 + out("x0") ret_val + } +} diff --git a/test/test93.wave b/test/test93.wave new file mode 100644 index 00000000..79417af7 --- /dev/null +++ b/test/test93.wave @@ -0,0 +1,9 @@ +// wave-test: host-arch=aarch64 +fun main() { + var result: i64 = asm { + "mov x0, #123" + out("x0") result + }; + + println("Result is: {}", result); +} diff --git a/test/test94.wave b/test/test94.wave new file mode 100644 index 00000000..8f125259 --- /dev/null +++ b/test/test94.wave @@ -0,0 +1,16 @@ +// wave-test: host-os=linux, host-arch=riscv64 +fun main() { + var dummy_ptr: ptr = "dummy"; + var ret_val: i64; + + asm { + "li a7, 56" + "ecall" + in("a0") -100 + in("a1") dummy_ptr + in("a2") 0 + out("a0") ret_val + } + + println("syscall result: {}", ret_val); +} diff --git a/test/test95.wave b/test/test95.wave new file mode 100644 index 00000000..5dda30f1 --- /dev/null +++ b/test/test95.wave @@ -0,0 +1,23 @@ +// wave-test: host-os=linux, host-arch=aarch64 +fun calc_sum(x: i32, y: i32) -> i32 { + println("Calculating sum of {} and {}", x, y); + var result: i32 = x + y; + return result; +} + +fun main() { + let total: i32 = calc_sum(3, 4); + println("sum={}", total); + + var msg_ptr: ptr = "Syscall says hi!\n"; + var ret_val: i64; + asm { + "mov x8, #64" + "svc #0" + in("x0") 1 + in("x1") msg_ptr + in("x2") 17 + out("x0") ret_val + } + println("syscall returned: {}", ret_val); +} diff --git a/test/test96.wave b/test/test96.wave new file mode 100644 index 00000000..45d99389 --- /dev/null +++ b/test/test96.wave @@ -0,0 +1,26 @@ +// wave-test: host-os=linux, host-arch=riscv64 +fun factorial_simple(n: i32) -> i32 { + var result: i32 = 1; + var i: i32 = 1; + while (i <= n) { + result *= i; + i += 1; + } + return result; +} + +fun main() { + println("fact={}", factorial_simple(5)); + + var msg_ptr: ptr = "Hello from syscall!\n"; + var ret_val: i64; + asm { + "li a7, 64" + "ecall" + in("a0") 1 + in("a1") msg_ptr + in("a2") 20 + out("a0") ret_val + } + println("syscall returned {}", ret_val); +} diff --git a/test/test97.wave b/test/test97.wave new file mode 100644 index 00000000..1d53d985 --- /dev/null +++ b/test/test97.wave @@ -0,0 +1,7 @@ +// wave-test: host-arch=aarch64 +fun main() { + asm { + "mov x9, #72" + "mov x10, x9" + } +} diff --git a/test/test98.wave b/test/test98.wave new file mode 100644 index 00000000..0e4c1e40 --- /dev/null +++ b/test/test98.wave @@ -0,0 +1,46 @@ +// wave-test: host-os=linux, host-arch=aarch64 +fun syscall0(id: i64) -> i64 { + var ret: i64; + asm { + "svc #0" + in("x8") id + out("x0") ret + } + return ret; +} + +fun syscall3(id: i64, a1: i64, a2: i64, a3: i64) -> i64 { + var ret: i64; + asm { + "svc #0" + in("x8") id + in("x0") a1 + in("x1") a2 + in("x2") a3 + out("x0") ret + } + return ret; +} + +fun syscall6(id: i64, a1: i64, a2: i64, a3: i64, a4: i64, a5: i64, a6: i64) -> i64 { + var ret: i64; + asm { + "svc #0" + in("x8") id + in("x0") a1 + in("x1") a2 + in("x2") a3 + in("x3") a4 + in("x4") a5 + in("x5") a6 + out("x0") ret + } + return ret; +} + +fun main() { + let a: i64 = syscall0(172); + let b: i64 = syscall3(64, 1, 2, 3); + let c: i64 = syscall6(222, 1, 2, 3, 4, 5, 6); + println("{} {} {}", a, b, c); +} diff --git a/test/test99.wave b/test/test99.wave new file mode 100644 index 00000000..fc85d9fd --- /dev/null +++ b/test/test99.wave @@ -0,0 +1,27 @@ +// wave-test: host-os=linux, host-arch=riscv64 +fun syscall_mmap(addr: ptr, length: i64, prot: i64, flags: i64, fd: i64, offset: i64) -> ptr { + let r: ptr; + asm { + "li a7, 222" + "ecall" + in("a0") addr + in("a1") length + in("a2") prot + in("a3") flags + in("a4") fd + in("a5") offset + out("a0") r + } + return r; +} + +fun main() { + let mem: ptr = syscall_mmap(null, 4096, 1, 2, -1, 0); + let raw: i64; + asm { + "mv a0, a1" + in("a1") mem + out("a0") raw + } + println("mem={}", raw); +} diff --git a/tools/run_tests.py b/tools/run_tests.py index 44c5c4eb..46b830f6 100644 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -17,10 +17,11 @@ from pathlib import Path import threading import socket +import sys +import platform ROOT = Path(__file__).resolve().parent.parent TEST_DIR = ROOT / "test" -WAVEC = ROOT / "target" / "release" / "wavec" TIMEOUT_SEC = 5 @@ -40,8 +41,11 @@ "WaveError", "WaveErrorKind", "SyntaxError", + "error[E", "failed to parse", "Failed to run", + "clang failed:", + "compiler internal error during code generation", "thread 'main' panicked", "panicked at", "LLVM ERROR", @@ -49,17 +53,106 @@ "stack overflow", ] -if not WAVEC.exists(): - print("wavec not found. Run `cargo build --release` first.") - exit(1) +def resolve_wavec() -> Path: + candidates = [ + ROOT / "target" / "release" / "wavec", + ROOT / "target" / "debug" / "wavec", + ] + for candidate in candidates: + if candidate.exists(): + return candidate + + print("wavec not found. Run `cargo build --release` or `cargo build` first.") + sys.exit(1) + + +WAVEC = resolve_wavec() results = [] +HOST_OS = platform.system().lower() +HOST_ARCH = platform.machine().lower() + + +def normalize_arch(arch: str) -> str: + aliases = { + "amd64": "x86_64", + "arm64": "aarch64", + } + return aliases.get(arch.lower(), arch.lower()) + + +HOST_ARCH = normalize_arch(HOST_ARCH) + + +def iter_test_entries(): + for path in sorted(TEST_DIR.glob("test*.wave")): + yield path.name, path.relative_to(ROOT).as_posix() + + for main_wave in sorted(TEST_DIR.glob("test*/main.wave")): + name = f"{main_wave.parent.name} (dir)" + yield name, main_wave.relative_to(ROOT).as_posix() + + +def parse_test_metadata(rel_path: str): + path = ROOT / rel_path + meta = { + "host_os": None, + "host_arch": None, + } + + try: + for line in path.read_text().splitlines(): + stripped = line.strip() + if not stripped.startswith("//"): + if stripped: + break + continue + + marker = "// wave-test:" + if not stripped.startswith(marker): + continue + + body = stripped[len(marker):].strip() + for item in body.split(","): + item = item.strip() + if not item or "=" not in item: + continue + key, value = item.split("=", 1) + key = key.strip() + value = value.strip() + if key == "host-os": + meta["host_os"] = value.lower() + elif key == "host-arch": + meta["host_arch"] = normalize_arch(value) + except OSError: + pass + + return meta + + +def skip_reason_for_metadata(name: str, rel_path: str): + meta = parse_test_metadata(rel_path) + host_os = meta["host_os"] + host_arch = meta["host_arch"] + + if host_os and host_os != HOST_OS: + return f"{name} requires host OS {host_os}, current host is {HOST_OS}" + + if host_arch and host_arch != HOST_ARCH: + return f"{name} requires host arch {host_arch}, current host is {HOST_ARCH}" + + return None + def send_udp_for_test61(): time.sleep(0.5) - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.sendto(b"hello from python\n", ("127.0.0.1", 8080)) - sock.close() + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.sendto(b"hello from python\n", ("127.0.0.1", 8080)) + sock.close() + except OSError: + # Some CI/sandbox environments block local sockets. + pass def run_test56_server(cmd): print(f"{BLUE}RUN test56.wave (server test){RESET}") @@ -122,9 +215,14 @@ def looks_like_fail(stderr: str) -> bool: # 0 = FAIL # 2 = SKIP # -1 = TIMEOUT -def run_and_classify(name, cmd): +def run_and_classify(name, rel_path, cmd): print(f"{BLUE}RUN {name}{RESET}") + skip_reason = skip_reason_for_metadata(name, rel_path) + if skip_reason is not None: + print(f"{CYAN}→ SKIP ({skip_reason}){RESET}\n") + return 2 + stdin_data = None if name == "test22.wave": stdin_data = "3\n" @@ -184,22 +282,19 @@ def run_and_classify(name, cmd): print(f"{YELLOW}→ TIMEOUT ({TIMEOUT_SEC}s){RESET}\n") return -1 -for path in sorted(TEST_DIR.glob("test*.wave")): - name = path.name - cmd = [str(WAVEC), "run", f"test/{name}"] - - result = run_and_classify(name, cmd) - results.append((name, result)) +try: + for name, rel_path in iter_test_entries(): + result = run_and_classify( + name, + rel_path, + [str(WAVEC), "run", rel_path] + ) + results.append((name, result)) - time.sleep(0.3) - -test28 = TEST_DIR / "test28" / "main.wave" -if test28.exists(): - result = run_and_classify( - "test28 (dir)", - [str(WAVEC), "run", "test/test28/main.wave"] - ) - results.append(("test28 (dir)", result)) + time.sleep(0.3) +except KeyboardInterrupt: + print(f"\n{YELLOW}Interrupted by user.{RESET}") + sys.exit(130) pass_zero = [name for name, r in results if r == 1] pass_nonzero = [name for name, r in results if r == 3] @@ -238,3 +333,6 @@ def run_and_classify(name, cmd): print(f"{RED}FAIL: {len(fail_tests)}{RESET}") print(f"{YELLOW}TIMEOUT: {len(timeout_tests)}{RESET}") print("=========================\n") + +if fail_tests or timeout_tests: + sys.exit(1)