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)
+
+---
+
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"],