diff --git a/.github/doom-demo.gif b/.github/doom-demo.gif new file mode 100644 index 00000000..8b5da655 Binary files /dev/null and b/.github/doom-demo.gif differ diff --git a/README.md b/README.md index fdf77a78..413ccbc7 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,20 @@ Useful global options: --- +## Contributing + +Contributions are welcome! Please read the [contributing guidelines](CONTRIBUTING.md) before submitting a pull request. + +--- + +## What can do? + +### [Doom Domo](https://github.com/wavefnd/Wave/tree/master/examples/doom.wave) + +[doom-demo](.github/doom-demo.gif) + +--- +

Star History Chart diff --git a/front/parser/src/import.rs b/front/parser/src/import.rs index a503a423..71b34071 100644 --- a/front/parser/src/import.rs +++ b/front/parser/src/import.rs @@ -57,14 +57,98 @@ fn is_supported_target_item_start(line: &str) -> bool { false } -fn consume_target_item( - lines: &[&str], - mut idx: usize, - keep: bool, - out: &mut Vec, -) -> usize { +fn scan_target_item_line( + line: &str, + in_block_comment: &mut bool, + depth: &mut i32, + seen_open: &mut bool, + saw_semicolon: &mut bool, +) { + let mut chars = line.chars().peekable(); + let mut in_string = false; + let mut in_char = false; + let mut escape = false; + + while let Some(ch) = chars.next() { + if *in_block_comment { + if ch == '*' && chars.peek() == Some(&'/') { + chars.next(); + *in_block_comment = false; + } + continue; + } + + if in_string { + if escape { + escape = false; + continue; + } + if ch == '\\' { + escape = true; + continue; + } + if ch == '"' { + in_string = false; + } + continue; + } + + if in_char { + if escape { + escape = false; + continue; + } + if ch == '\\' { + escape = true; + continue; + } + if ch == '\'' { + in_char = false; + } + continue; + } + + if ch == '/' { + if chars.peek() == Some(&'/') { + break; + } + if chars.peek() == Some(&'*') { + chars.next(); + *in_block_comment = true; + continue; + } + } + + if ch == '"' { + in_string = true; + continue; + } + if ch == '\'' { + in_char = true; + continue; + } + + if ch == '{' { + *depth += 1; + *seen_open = true; + continue; + } + if ch == '}' { + if *depth > 0 { + *depth -= 1; + } + continue; + } + if ch == ';' { + *saw_semicolon = true; + } + } +} + +fn consume_target_item(lines: &[&str], mut idx: usize, keep: bool, out: &mut Vec) -> usize { let mut depth: i32 = 0; let mut seen_open = false; + let mut in_block_comment = false; while idx < lines.len() { let line = lines[idx]; @@ -74,19 +158,22 @@ fn consume_target_item( out.push(String::new()); } - for ch in line.chars() { - if ch == '{' { - depth += 1; - seen_open = true; - } else if ch == '}' && depth > 0 { - depth -= 1; - } - } + let mut saw_semicolon = false; + scan_target_item_line( + line, + &mut in_block_comment, + &mut depth, + &mut seen_open, + &mut saw_semicolon, + ); idx += 1; - let trimmed = line.trim_end(); - if seen_open && depth == 0 { + if seen_open { + if depth == 0 { + break; + } + } else if saw_semicolon { break; } } diff --git a/llvm/src/backend.rs b/llvm/src/backend.rs index 51ace7c5..a4dc4f06 100644 --- a/llvm/src/backend.rs +++ b/llvm/src/backend.rs @@ -9,10 +9,20 @@ // // SPDX-License-Identifier: MPL-2.0 -use std::fs; -use std::path::Path; use std::process::Command; +#[derive(Debug, Default, Clone)] +pub struct BackendOptions { + pub target: Option, + pub cpu: Option, + pub features: Option, + pub abi: Option, + pub sysroot: Option, + pub linker: Option, + pub link_args: Vec, + pub no_default_libs: bool, +} + fn normalize_clang_opt_flag(opt_flag: &str) -> &str { match opt_flag { // LLVM pass pipeline currently has no dedicated Ofast preset, so keep @@ -22,12 +32,29 @@ fn normalize_clang_opt_flag(opt_flag: &str) -> &str { } } -pub fn compile_ir_to_object(ir: &str, file_stem: &str, opt_flag: &str) -> String { +pub fn compile_ir_to_object( + ir: &str, + file_stem: &str, + opt_flag: &str, + backend: &BackendOptions, +) -> String { let object_path = format!("{}.o", file_stem); let normalized_opt = normalize_clang_opt_flag(opt_flag); let mut cmd = Command::new("clang"); + 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); + } + if !normalized_opt.is_empty() { cmd.arg(normalized_opt); } @@ -61,8 +88,27 @@ pub fn compile_ir_to_object(ir: &str, file_stem: &str, opt_flag: &str) -> String object_path } -pub fn link_objects(objects: &[String], output: &str, libs: &[String], lib_paths: &[String]) { - let mut cmd = Command::new("clang"); +pub fn link_objects( + objects: &[String], + output: &str, + libs: &[String], + lib_paths: &[String], + backend: &BackendOptions, +) { + let linker_bin = backend.linker.as_deref().unwrap_or("clang"); + let mut cmd = Command::new(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); + } + } for obj in objects { cmd.arg(obj); @@ -76,7 +122,15 @@ pub fn link_objects(objects: &[String], output: &str, libs: &[String], lib_paths cmd.arg(format!("-l{}", lib)); } - cmd.arg("-o").arg(output).arg("-lc").arg("-lm"); + for arg in &backend.link_args { + cmd.arg(arg); + } + + cmd.arg("-o").arg(output); + + if !backend.no_default_libs { + cmd.arg("-lc").arg("-lm"); + } let output = cmd.output().expect("Failed to link"); if !output.status.success() { diff --git a/llvm/src/codegen/abi_c.rs b/llvm/src/codegen/abi_c.rs index 4e103e6e..19c1131a 100644 --- a/llvm/src/codegen/abi_c.rs +++ b/llvm/src/codegen/abi_c.rs @@ -402,8 +402,12 @@ fn classify_param<'ctx>( t: BasicTypeEnum<'ctx>, ) -> ParamLowering<'ctx> { match target { - CodegenTarget::LinuxX86_64 => classify_param_x86_64_sysv(context, td, t), - CodegenTarget::DarwinArm64 => classify_param_arm64_darwin(td, t), + CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => { + classify_param_x86_64_sysv(context, td, t) + } + CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => { + classify_param_arm64_darwin(td, t) + } } } @@ -414,8 +418,10 @@ fn classify_ret<'ctx>( t: Option>, ) -> RetLowering<'ctx> { match target { - CodegenTarget::LinuxX86_64 => classify_ret_x86_64_sysv(context, td, t), - CodegenTarget::DarwinArm64 => classify_ret_arm64_darwin(td, t), + CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => { + classify_ret_x86_64_sysv(context, td, t) + } + CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => classify_ret_arm64_darwin(td, t), } } diff --git a/llvm/src/codegen/ir.rs b/llvm/src/codegen/ir.rs index 9ccd9e89..a7a04a80 100644 --- a/llvm/src/codegen/ir.rs +++ b/llvm/src/codegen/ir.rs @@ -16,7 +16,7 @@ use inkwell::values::{BasicValue, BasicValueEnum, FunctionValue}; use inkwell::OptimizationLevel; use inkwell::targets::{ - CodeModel, InitializationConfig, RelocMode, Target, TargetData, TargetMachine, + CodeModel, InitializationConfig, RelocMode, Target, TargetData, TargetMachine, TargetTriple, }; use parser::ast::{ ASTNode, EnumNode, ExternFunctionNode, FunctionNode, Mutability, ParameterNode, ProtoImplNode, @@ -24,6 +24,7 @@ use parser::ast::{ }; use std::collections::{HashMap, HashSet}; +use crate::backend::BackendOptions; use crate::codegen::target::require_supported_target_from_triple; use crate::statement::generate_statement_ir; @@ -48,7 +49,21 @@ fn normalize_opt_flag_for_passes(opt_flag: &str) -> &str { } } -pub unsafe fn generate_ir(ast_nodes: &[ASTNode], opt_flag: &str) -> String { +fn target_opt_level_from_flag(opt_flag: &str) -> OptimizationLevel { + match normalize_opt_flag_for_passes(opt_flag) { + "" | "-O0" => OptimizationLevel::None, + "-O1" => OptimizationLevel::Less, + "-O2" | "-Os" | "-Oz" => OptimizationLevel::Default, + "-O3" => OptimizationLevel::Aggressive, + other => panic!("unknown opt flag for target machine: {}", other), + } +} + +pub unsafe fn generate_ir( + ast_nodes: &[ASTNode], + opt_flag: &str, + backend: &BackendOptions, +) -> String { let context: &'static Context = Box::leak(Box::new(Context::create())); let module: &'static _ = Box::leak(Box::new(context.create_module("main"))); 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 { .map(|n| resolve_ast_node(n, &named_types)) .collect(); - Target::initialize_native(&InitializationConfig::default()).unwrap(); - let triple = TargetMachine::get_default_triple(); + Target::initialize_all(&InitializationConfig::default()); + 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); let target = Target::from_triple(&triple).unwrap(); + let cpu = backend.cpu.as_deref().unwrap_or("generic"); + let features = backend.features.as_deref().unwrap_or(""); let tm = target .create_target_machine( &triple, - "generic", - "", - OptimizationLevel::Default, + cpu, + features, + target_opt_level_from_flag(opt_flag), RelocMode::Default, CodeModel::Default, ) diff --git a/llvm/src/codegen/plan.rs b/llvm/src/codegen/plan.rs index 460fac77..9ea0c738 100644 --- a/llvm/src/codegen/plan.rs +++ b/llvm/src/codegen/plan.rs @@ -129,8 +129,10 @@ fn reg_phys_group_arm64(token: &str) -> Option { fn parse_token(target: CodegenTarget, raw: &str) -> RegToken { let raw_norm = normalize_token(raw); let phys_group = match target { - CodegenTarget::LinuxX86_64 => reg_phys_group_x86_64(&raw_norm).map(|s| s.to_string()), - CodegenTarget::DarwinArm64 => reg_phys_group_arm64(&raw_norm), + CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => { + reg_phys_group_x86_64(&raw_norm).map(|s| s.to_string()) + } + CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => reg_phys_group_arm64(&raw_norm), }; RegToken { raw_norm, @@ -152,15 +154,23 @@ fn build_default_clobbers( match mode { AsmSafetyMode::ConservativeKernel => { let mut clobbers = match target { - CodegenTarget::LinuxX86_64 => vec![ + CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => vec![ "~{memory}".to_string(), "~{dirflag}".to_string(), "~{fpsr}".to_string(), "~{flags}".to_string(), ], - CodegenTarget::DarwinArm64 => vec!["~{memory}".to_string(), "~{cc}".to_string()], + CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => { + vec!["~{memory}".to_string(), "~{cc}".to_string()] + } }; + // Empty barrier-like asm blocks must not implicitly clobber every GPR. + // Users can still declare explicit register clobbers when needed. + if inputs.is_empty() && outputs.is_empty() { + return clobbers; + } + // Collect concrete used physical register groups let mut used_phys: HashSet = HashSet::new(); let mut has_class_constraint = false; @@ -188,7 +198,7 @@ fn build_default_clobbers( } match target { - CodegenTarget::LinuxX86_64 => { + CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => { const GPRS: [&str; 16] = [ "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", @@ -200,7 +210,7 @@ fn build_default_clobbers( } } } - CodegenTarget::DarwinArm64 => { + CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => { for n in 0..=30u32 { if n == 18 { continue; @@ -254,14 +264,14 @@ fn gcc_percent_to_llvm_dollar(s: &str) -> String { fn normalize_special_clobber(target: CodegenTarget, token: &str) -> Option { match target { - CodegenTarget::LinuxX86_64 => match token { + CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_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::DarwinArm64 => match token { + CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => match token { "memory" => Some("~{memory}".to_string()), "cc" | "flags" | "eflags" | "rflags" => Some("~{cc}".to_string()), _ => None, diff --git a/llvm/src/codegen/target.rs b/llvm/src/codegen/target.rs index 5d2b4c91..7c60a156 100644 --- a/llvm/src/codegen/target.rs +++ b/llvm/src/codegen/target.rs @@ -15,6 +15,8 @@ use inkwell::targets::TargetTriple; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CodegenTarget { LinuxX86_64, + LinuxArm64, + DarwinX86_64, DarwinArm64, } @@ -30,6 +32,12 @@ impl CodegenTarget { if is_x86_64 && is_linux { return Some(Self::LinuxX86_64); } + if is_arm64 && is_linux { + return Some(Self::LinuxArm64); + } + if is_x86_64 && is_darwin { + return Some(Self::DarwinX86_64); + } if is_arm64 && is_darwin { return Some(Self::DarwinArm64); } @@ -50,6 +58,8 @@ impl CodegenTarget { pub fn desc(self) -> &'static str { match self { Self::LinuxX86_64 => "linux x86_64", + Self::LinuxArm64 => "linux arm64", + Self::DarwinX86_64 => "darwin x86_64", Self::DarwinArm64 => "darwin arm64", } } @@ -62,7 +72,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 only linux x86_64 and darwin arm64 (Windows not supported yet)", + "unsupported target triple '{}': Wave currently supports linux x86_64/arm64 and darwin x86_64/arm64 (Windows not supported yet)", raw ); } @@ -75,7 +85,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 only linux x86_64 and darwin arm64 (Windows not supported yet)", + "unsupported target triple '{}': Wave currently supports linux x86_64/arm64 and darwin x86_64/arm64 (Windows not supported yet)", raw ); } diff --git a/llvm/src/expression/rvalue/asm.rs b/llvm/src/expression/rvalue/asm.rs index 66d3b85c..c9d3a7e9 100644 --- a/llvm/src/expression/rvalue/asm.rs +++ b/llvm/src/expression/rvalue/asm.rs @@ -22,8 +22,8 @@ use parser::ast::{Expression, Literal, WaveType}; fn inline_asm_dialect_for_target(target: CodegenTarget) -> InlineAsmDialect { match target { - CodegenTarget::LinuxX86_64 => InlineAsmDialect::Intel, - CodegenTarget::DarwinArm64 => InlineAsmDialect::ATT, + CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => InlineAsmDialect::Intel, + CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => InlineAsmDialect::ATT, } } diff --git a/llvm/src/statement/asm.rs b/llvm/src/statement/asm.rs index 1494660a..d6a5ad86 100644 --- a/llvm/src/statement/asm.rs +++ b/llvm/src/statement/asm.rs @@ -64,8 +64,8 @@ fn reg_width_bits(reg: &str) -> Option { fn reg_width_bits_for_target(target: CodegenTarget, reg: &str) -> Option { match target { - CodegenTarget::LinuxX86_64 => reg_width_bits(reg), - CodegenTarget::DarwinArm64 => { + CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => reg_width_bits(reg), + CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => { if reg.len() >= 2 { let (prefix, num) = reg.split_at(1); if num.chars().all(|c| c.is_ascii_digit()) && !num.is_empty() { @@ -100,8 +100,8 @@ fn extract_reg_from_constraint(c: &str) -> Option { fn inline_asm_dialect_for_target(target: CodegenTarget) -> InlineAsmDialect { match target { - CodegenTarget::LinuxX86_64 => InlineAsmDialect::Intel, - CodegenTarget::DarwinArm64 => InlineAsmDialect::ATT, + CodegenTarget::LinuxX86_64 | CodegenTarget::DarwinX86_64 => InlineAsmDialect::Intel, + CodegenTarget::LinuxArm64 | CodegenTarget::DarwinArm64 => InlineAsmDialect::ATT, } } diff --git a/src/cli.rs b/src/cli.rs index b015153a..77553dca 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -10,7 +10,9 @@ // SPDX-License-Identifier: MPL-2.0 use crate::errors::CliError; -use crate::flags::{validate_opt_flag, DebugFlags, DepFlags, DepPackage, LinkFlags}; +use crate::flags::{ + validate_opt_flag, DebugFlags, DepFlags, DepPackage, LinkFlags, LlvmFlags, WhaleFlags, +}; use crate::{runner, std as wave_std, version}; use crate::version::get_os_pretty_name; @@ -42,6 +44,8 @@ struct Global { debug: DebugFlags, link: LinkFlags, dep: DepFlags, + llvm: LlvmFlags, + whale: WhaleFlags, } pub fn run() -> Result<(), CliError> { @@ -57,6 +61,13 @@ pub fn run() -> Result<(), CliError> { } fn dispatch(global: Global, cmd: Command) -> Result<(), CliError> { + // TODO(whale): wire Whale toolchain backend pipeline once backend implementation is available. + if global.whale.enabled { + return Err(CliError::usage( + "TODO: --whale backend is reserved but not implemented yet", + )); + } + match cmd { Command::Version => { print_version(); @@ -70,7 +81,14 @@ fn dispatch(global: Global, cmd: Command) -> Result<(), CliError> { Command::Run { file } => { unsafe { - runner::run_wave_file(&file, &global.opt, &global.debug, &global.link, &global.dep); + runner::run_wave_file( + &file, + &global.opt, + &global.debug, + &global.link, + &global.dep, + &global.llvm, + ); } Ok(()) } @@ -87,6 +105,7 @@ fn dispatch(global: Global, cmd: Command) -> Result<(), CliError> { &global.opt, &global.debug, &global.dep, + &global.llvm, output.as_deref(), ); println!("{}", out); @@ -97,6 +116,7 @@ fn dispatch(global: Global, cmd: Command) -> Result<(), CliError> { &global.debug, &global.link, &global.dep, + &global.llvm, output.as_deref(), ); } @@ -115,6 +135,8 @@ fn parse_global(args: Vec) -> Result<(Global, Vec), CliError> { debug: DebugFlags::default(), link: LinkFlags::default(), dep: DepFlags::default(), + llvm: LlvmFlags::default(), + whale: WhaleFlags::default(), }; let mut rest: Vec = Vec::new(); @@ -128,6 +150,26 @@ fn parse_global(args: Vec) -> Result<(Global, Vec), CliError> { break; } + // --llvm + if a == "--llvm" { + i += 1; + while i < args.len() { + if parse_llvm_backend_option(&args, &mut i, &mut g.llvm)? { + continue; + } + break; + } + continue; + } + + // --whale + // TODO(whale): parse Whale backend options after this marker once Whale toolchain lands. + if a == "--whale" { + g.whale.enabled = true; + i += 1; + continue; + } + // -O* if a.starts_with("-O") { if !validate_opt_flag(a) { @@ -288,6 +330,172 @@ fn parse_dep_spec(spec: &str) -> Result { }) } +fn parse_llvm_backend_option( + args: &[String], + i: &mut usize, + llvm: &mut LlvmFlags, +) -> Result { + let a = &args[*i]; + + if let Some(v) = a.strip_prefix("--target=") { + if v.trim().is_empty() { + return Err(CliError::usage("missing value: --target=")); + } + llvm.target = Some(v.to_string()); + *i += 1; + return Ok(true); + } + if a == "--target" { + let v = args + .get(*i + 1) + .ok_or_else(|| CliError::usage("missing value: --target "))?; + if v.trim().is_empty() { + return Err(CliError::usage("missing value: --target ")); + } + llvm.target = Some(v.to_string()); + *i += 2; + return Ok(true); + } + + if let Some(v) = a.strip_prefix("--cpu=") { + if v.trim().is_empty() { + return Err(CliError::usage("missing value: --cpu=")); + } + llvm.cpu = Some(v.to_string()); + *i += 1; + return Ok(true); + } + if a == "--cpu" { + let v = args + .get(*i + 1) + .ok_or_else(|| CliError::usage("missing value: --cpu "))?; + if v.trim().is_empty() { + return Err(CliError::usage("missing value: --cpu ")); + } + llvm.cpu = Some(v.to_string()); + *i += 2; + return Ok(true); + } + + if let Some(v) = a.strip_prefix("--features=") { + if v.trim().is_empty() { + return Err(CliError::usage("missing value: --features=")); + } + llvm.features = Some(v.to_string()); + *i += 1; + return Ok(true); + } + if a == "--features" { + let v = args + .get(*i + 1) + .ok_or_else(|| CliError::usage("missing value: --features "))?; + if v.trim().is_empty() { + return Err(CliError::usage("missing value: --features ")); + } + llvm.features = Some(v.to_string()); + *i += 2; + return Ok(true); + } + + if let Some(v) = a.strip_prefix("--abi=") { + if v.trim().is_empty() { + return Err(CliError::usage("missing value: --abi=")); + } + llvm.abi = Some(v.to_string()); + *i += 1; + return Ok(true); + } + if a == "--abi" { + let v = args + .get(*i + 1) + .ok_or_else(|| CliError::usage("missing value: --abi "))?; + if v.trim().is_empty() { + return Err(CliError::usage("missing value: --abi ")); + } + llvm.abi = Some(v.to_string()); + *i += 2; + return Ok(true); + } + + if let Some(v) = a.strip_prefix("--sysroot=") { + if v.trim().is_empty() { + return Err(CliError::usage("missing value: --sysroot=")); + } + llvm.sysroot = Some(v.to_string()); + *i += 1; + return Ok(true); + } + if a == "--sysroot" { + let v = args + .get(*i + 1) + .ok_or_else(|| CliError::usage("missing value: --sysroot "))?; + if v.trim().is_empty() { + return Err(CliError::usage("missing value: --sysroot ")); + } + llvm.sysroot = Some(v.to_string()); + *i += 2; + return Ok(true); + } + + if a == "-C" { + let spec = args + .get(*i + 1) + .ok_or_else(|| CliError::usage("missing value: -C [=]"))?; + parse_llvm_codegen_spec(spec, llvm)?; + *i += 2; + return Ok(true); + } + + if let Some(spec) = a.strip_prefix("-C") { + if spec.is_empty() { + return Err(CliError::usage("missing value: -C [=]")); + } + parse_llvm_codegen_spec(spec, llvm)?; + *i += 1; + return Ok(true); + } + + Ok(false) +} + +fn parse_llvm_codegen_spec(spec: &str, llvm: &mut LlvmFlags) -> Result<(), CliError> { + let spec = spec.trim(); + if spec.is_empty() { + return Err(CliError::usage("missing value: -C [=]")); + } + + if spec == "no-default-libs" { + llvm.no_default_libs = true; + return Ok(()); + } + + let Some((key, value)) = spec.split_once('=') else { + return Err(CliError::usage(format!( + "invalid -C option '{}': expected key=value or no-default-libs", + spec + ))); + }; + + let key = key.trim(); + let value = value.trim(); + if value.is_empty() { + return Err(CliError::usage(format!("missing value for -C {}", key))); + } + + match key { + "linker" => llvm.linker = Some(value.to_string()), + "link-arg" => llvm.link_args.push(value.to_string()), + _ => { + return Err(CliError::usage(format!( + "unsupported -C option '{}': supported keys are linker, link-arg, no-default-libs", + key + ))); + } + } + + Ok(()) +} + fn parse_command(rest: Vec) -> Result { if rest.is_empty() { return Err(CliError::usage("not enough arguments")); @@ -528,6 +736,53 @@ pub fn print_help() { "Library search path" ); + println!("\nBackend options (after --llvm):"); + println!( + " {:<22} {}", + "--target=".color("38,139,235"), + "Target triple (e.g. aarch64-unknown-linux-gnu)" + ); + println!( + " {:<22} {}", + "--cpu=".color("38,139,235"), + "Target CPU name for LLVM" + ); + println!( + " {:<22} {}", + "--features=".color("38,139,235"), + "Target feature list (comma-separated)" + ); + println!( + " {:<22} {}", + "--abi=".color("38,139,235"), + "Target ABI hint for backend tools" + ); + println!( + " {:<22} {}", + "--sysroot=".color("38,139,235"), + "Sysroot path for compile/link" + ); + println!( + " {:<22} {}", + "-C linker=".color("38,139,235"), + "Override linker executable" + ); + println!( + " {:<22} {}", + "-C link-arg=".color("38,139,235"), + "Append raw linker argument (repeatable)" + ); + println!( + " {:<22} {}", + "-C no-default-libs".color("38,139,235"), + "Disable automatic -lc -lm" + ); + println!( + " {:<22} {}", + "--whale".color("38,139,235"), + "Reserved backend selector (TODO, not implemented)" + ); + println!("\nDependency options:"); println!( " {:<22} {}", diff --git a/src/flags.rs b/src/flags.rs index b0a202c0..6b54148b 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -65,6 +65,23 @@ pub struct DepFlags { pub packages: Vec, } +#[derive(Default, Clone)] +pub struct LlvmFlags { + pub target: Option, + pub cpu: Option, + pub features: Option, + pub abi: Option, + pub sysroot: Option, + pub linker: Option, + pub link_args: Vec, + pub no_default_libs: bool, +} + +#[derive(Default, Clone)] +pub struct WhaleFlags { + pub enabled: bool, +} + pub fn validate_opt_flag(flag: &str) -> bool { matches!( flag, diff --git a/src/lib.rs b/src/lib.rs index b15c5e8c..2b9aa882 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,4 +16,4 @@ pub mod runner; pub mod std; pub mod version; -pub use flags::{DebugFlags, DepFlags, LinkFlags}; +pub use flags::{DebugFlags, DepFlags, LinkFlags, LlvmFlags, WhaleFlags}; diff --git a/src/runner.rs b/src/runner.rs index 721d4ca0..3d056447 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -9,7 +9,7 @@ // // SPDX-License-Identifier: MPL-2.0 -use crate::{DebugFlags, DepFlags, LinkFlags}; +use crate::{DebugFlags, DepFlags, LinkFlags, LlvmFlags}; use ::error::*; use ::parser::ast::*; use ::parser::import::*; @@ -65,14 +65,98 @@ fn is_supported_target_item_start(line: &str) -> bool { false } -fn consume_target_item( - lines: &[&str], - mut idx: usize, - keep: bool, - out: &mut Vec, -) -> usize { +fn scan_target_item_line( + line: &str, + in_block_comment: &mut bool, + depth: &mut i32, + seen_open: &mut bool, + saw_semicolon: &mut bool, +) { + let mut chars = line.chars().peekable(); + let mut in_string = false; + let mut in_char = false; + let mut escape = false; + + while let Some(ch) = chars.next() { + if *in_block_comment { + if ch == '*' && chars.peek() == Some(&'/') { + chars.next(); + *in_block_comment = false; + } + continue; + } + + if in_string { + if escape { + escape = false; + continue; + } + if ch == '\\' { + escape = true; + continue; + } + if ch == '"' { + in_string = false; + } + continue; + } + + if in_char { + if escape { + escape = false; + continue; + } + if ch == '\\' { + escape = true; + continue; + } + if ch == '\'' { + in_char = false; + } + continue; + } + + if ch == '/' { + if chars.peek() == Some(&'/') { + break; + } + if chars.peek() == Some(&'*') { + chars.next(); + *in_block_comment = true; + continue; + } + } + + if ch == '"' { + in_string = true; + continue; + } + if ch == '\'' { + in_char = true; + continue; + } + + if ch == '{' { + *depth += 1; + *seen_open = true; + continue; + } + if ch == '}' { + if *depth > 0 { + *depth -= 1; + } + continue; + } + if ch == ';' { + *saw_semicolon = true; + } + } +} + +fn consume_target_item(lines: &[&str], mut idx: usize, keep: bool, out: &mut Vec) -> usize { let mut depth: i32 = 0; let mut seen_open = false; + let mut in_block_comment = false; while idx < lines.len() { let line = lines[idx]; @@ -82,19 +166,22 @@ fn consume_target_item( out.push(String::new()); } - for ch in line.chars() { - if ch == '{' { - depth += 1; - seen_open = true; - } else if ch == '}' && depth > 0 { - depth -= 1; - } - } + let mut saw_semicolon = false; + scan_target_item_line( + line, + &mut in_block_comment, + &mut depth, + &mut seen_open, + &mut saw_semicolon, + ); idx += 1; - let trimmed = line.trim_end(); - if seen_open && depth == 0 { + if seen_open { + if depth == 0 { + break; + } + } else if saw_semicolon { break; } } @@ -847,12 +934,26 @@ fn resolve_output_target( output.display().to_string() } +fn build_backend_options(llvm: &LlvmFlags) -> BackendOptions { + BackendOptions { + target: llvm.target.clone(), + cpu: llvm.cpu.clone(), + features: llvm.features.clone(), + abi: llvm.abi.clone(), + sysroot: llvm.sysroot.clone(), + linker: llvm.linker.clone(), + link_args: llvm.link_args.clone(), + no_default_libs: llvm.no_default_libs, + } +} + pub(crate) unsafe fn run_wave_file( file_path: &Path, opt_flag: &str, debug: &DebugFlags, link: &LinkFlags, dep: &DepFlags, + llvm: &LlvmFlags, ) { let raw_code = match fs::read_to_string(file_path) { Ok(c) => c, @@ -902,7 +1003,9 @@ pub(crate) unsafe fn run_wave_file( validate_wave_ast_or_exit(file_path, &code, &ast); - let ir = match run_panic_guarded(|| unsafe { generate_ir(&ast, opt_flag) }) { + let backend_opts = build_backend_options(llvm); + + 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) @@ -914,12 +1017,13 @@ pub(crate) unsafe fn run_wave_file( } let file_stem = file_path.file_stem().unwrap().to_str().unwrap(); - let object_patch = match run_panic_guarded(|| compile_ir_to_object(&ir, file_stem, opt_flag)) { - Ok(path) => path, - Err((msg, loc)) => { - emit_codegen_panic_and_exit(file_path, &code, "object-emission", msg, loc) - } - }; + let object_patch = + match run_panic_guarded(|| compile_ir_to_object(&ir, file_stem, opt_flag, &backend_opts)) { + Ok(path) => path, + Err((msg, loc)) => { + emit_codegen_panic_and_exit(file_path, &code, "object-emission", msg, loc) + } + }; if debug.mc { println!("\n===== MACHINE CODE PATH ====="); @@ -941,7 +1045,13 @@ pub(crate) unsafe fn run_wave_file( let exe_patch = format!("target/{}", file_stem); if let Err((msg, loc)) = run_panic_guarded(|| { - link_objects(&[object_patch.clone()], &exe_patch, &link.libs, &link.paths); + link_objects( + &[object_patch.clone()], + &exe_patch, + &link.libs, + &link.paths, + &backend_opts, + ); }) { emit_codegen_panic_and_exit(file_path, &code, "native-link", msg, loc); } @@ -966,6 +1076,7 @@ pub(crate) unsafe fn object_build_wave_file( opt_flag: &str, debug: &DebugFlags, dep: &DepFlags, + llvm: &LlvmFlags, output: Option<&Path>, ) -> String { let raw_code = fs::read_to_string(file_path).unwrap_or_else(|_| { @@ -1009,7 +1120,9 @@ pub(crate) unsafe fn object_build_wave_file( validate_wave_ast_or_exit(file_path, &code, &ast); - let ir = match run_panic_guarded(|| unsafe { generate_ir(&ast, opt_flag) }) { + let backend_opts = build_backend_options(llvm); + + 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) @@ -1022,7 +1135,7 @@ pub(crate) unsafe fn object_build_wave_file( let file_stem = file_path.file_stem().unwrap().to_str().unwrap(); let generated_object_path = - match run_panic_guarded(|| compile_ir_to_object(&ir, file_stem, opt_flag)) { + match run_panic_guarded(|| compile_ir_to_object(&ir, file_stem, opt_flag, &backend_opts)) { Ok(path) => path, Err((msg, loc)) => { emit_codegen_panic_and_exit(file_path, &code, "object-emission", msg, loc) @@ -1062,18 +1175,26 @@ pub(crate) unsafe fn build_wave_file( debug: &DebugFlags, link: &LinkFlags, dep: &DepFlags, + llvm: &LlvmFlags, output: Option<&Path>, ) { - let object_path = object_build_wave_file(file_path, opt_flag, debug, dep, None); + let object_path = object_build_wave_file(file_path, opt_flag, debug, dep, llvm, None); let file_stem = file_path.file_stem().unwrap().to_str().unwrap(); let default_exe_path = format!("target/{}", file_stem); let source = fs::read_to_string(file_path).unwrap_or_default(); let exe_path = resolve_output_target(&default_exe_path, output, file_path, &source, "native-link"); + let backend_opts = build_backend_options(llvm); if let Err((msg, loc)) = run_panic_guarded(|| { - link_objects(&[object_path], &exe_path, &link.libs, &link.paths); + link_objects( + &[object_path], + &exe_path, + &link.libs, + &link.paths, + &backend_opts, + ); }) { emit_codegen_panic_and_exit(file_path, &source, "native-link", msg, loc); } diff --git a/test/test90.wave b/test/test90.wave new file mode 100644 index 00000000..6e9b8ad1 --- /dev/null +++ b/test/test90.wave @@ -0,0 +1,20 @@ +static TOTAL_COUNT: i32 = 0; + +#[target(os="linux")] +fun get_platform_name() -> str { + var linux: str = "Linux"; + return linux; +} + +#[target(os="macos")] +fun get_platform_name() -> str { + var macos: str = "macOS"; + return macos; +} + +fun main() { + var x: i64 = 1000; + var y: i64 = x as i32; + + println("Running on: {}", get_platform_name()); +} \ No newline at end of file diff --git a/x.py b/x.py index 553c5db5..1a0cff25 100644 --- a/x.py +++ b/x.py @@ -33,6 +33,7 @@ TARGET_MATRIX = { "x86_64-unknown-linux-gnu": ["Linux"], + "aarch64-unknown-linux-gnu": ["Linux"], "x86_64-unknown-freebsd": ["FreeBSD"], "x86_64-unknown-openbsd": ["OpenBSD"], "x86_64-unknown-netbsd": ["NetBSD"],