From 3fac392a93b1e002bd7beed5e0c3bc646408b591 Mon Sep 17 00:00:00 2001 From: LunaStev Date: Sat, 20 Jun 2026 12:20:37 +0900 Subject: [PATCH 01/12] chore: improve test runner, LLVM type inference, and environment setup - Added selective test execution via `--only`/`--skip`. - Enhanced Wave test metadata parsing with new fields (`mode`, `expected_exit`, `freestanding`, etc.). - Improved compatibility for Windows by accommodating environment variables for MinGW (`WAVE_MINGW_CC`, `WAVE_MINGW_CXX`) and validating LLVM paths. - Upgraded IR generation to better handle nested dereferences and field/index accesses with specific types. - Changed Rust and Python CI steps to include Clippy, Rustfmt, and Python validation. - Added lint exceptions for development ergonomics and explicit code clarity. Signed-off-by: LunaStev --- .github/workflows/rust.yml | 61 ++++++++++-- .gitignore | 3 +- RELEASING.md | 42 +++++++++ front/error/src/error.rs | 4 +- front/lexer/src/lib.rs | 3 + front/lexer/src/scan.rs | 1 + front/parser/src/generics.rs | 60 +++++++++++- front/parser/src/lib.rs | 15 +++ llvm/src/codegen/address.rs | 7 ++ llvm/src/expression/lvalue.rs | 62 ++++++++++-- llvm/src/expression/rvalue/assign.rs | 17 +++- llvm/src/expression/rvalue/pointers.rs | 6 ++ llvm/src/lib.rs | 20 ++++ llvm/src/statement/variable.rs | 7 +- src/cli.rs | 2 +- src/lib.rs | 8 ++ src/runner.rs | 8 +- src/version.rs | 5 +- test/test107.wave | 2 + test/test108.wave | 2 + test/test22.wave | 2 - test/test27.wave | 9 +- test/test54.wave | 2 +- tests/codegen_regressions.rs | 25 +++++ tools/run_tests.py | 125 +++++++++++++++++++++++-- utils/src/json.rs | 8 +- x.py | 31 +++++- 27 files changed, 477 insertions(+), 60 deletions(-) create mode 100644 RELEASING.md diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6bbc3487..89b0be5a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,4 +1,4 @@ -name: Rust +name: Wave CI on: push: @@ -8,13 +8,26 @@ on: env: CARGO_TERM_COLOR: always + PYTHONUNBUFFERED: "1" + +permissions: + contents: read + +concurrency: + group: wave-ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: build-ubuntu: runs-on: ubuntu-latest + timeout-minutes: 45 steps: - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install LLVM 21 run: | sudo apt-get update @@ -26,20 +39,36 @@ jobs: echo "LLVM_CONFIG_PATH=/usr/lib/llvm-21/bin/llvm-config" >> "$GITHUB_ENV" echo "/usr/lib/llvm-21/bin" >> "$GITHUB_PATH" - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose + - name: Check Rust formatting + run: cargo fmt --all --check + + - name: Run Clippy + run: cargo clippy --locked --all-targets -- -D warnings + + - name: Validate Python tooling + run: python3 -m py_compile x.py tools/run_tests.py + + - name: Build release compiler + run: cargo build --locked --release --verbose + + - name: Run Rust tests + run: cargo test --locked --all-targets --verbose + + - name: Run Wave end-to-end tests + run: python3 tools/run_tests.py build-macos: runs-on: macos-latest + timeout-minutes: 45 steps: - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" - name: Install LLVM 21 run: | - brew update brew install llvm@21 - name: Set LLVM env @@ -48,8 +77,20 @@ jobs: echo "LLVM_CONFIG_PATH=$(brew --prefix llvm@21)/bin/llvm-config" >> $GITHUB_ENV echo "$(brew --prefix llvm@21)/bin" >> $GITHUB_PATH - - name: Build - run: cargo build --verbose + - name: Check Rust formatting + run: cargo fmt --all --check + + - name: Run Clippy + run: cargo clippy --locked --all-targets -- -D warnings + + - name: Validate Python tooling + run: python3 -m py_compile x.py tools/run_tests.py + + - name: Build release compiler + run: cargo build --locked --release --verbose + + - name: Run Rust tests + run: cargo test --locked --all-targets --verbose - - name: Run tests - run: cargo test --verbose + - name: Run Wave end-to-end tests + run: python3 tools/run_tests.py diff --git a/.gitignore b/.gitignore index 74c142cb..c1299679 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,8 @@ /target/ # Rust build output directory **/*.rs.bk # Rust backup files *.crate # Compiled crate files -Cargo.lock +# Cargo.lock is tracked because wavec is a release binary. +.codex dist/ diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 00000000..d7776ee6 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,42 @@ +# Releasing Wave + +Wave releases are created by the manual `Manual Release` GitHub Actions workflow. +The workflow validates the compiler before it creates any tag or GitHub release. + +## Release prerequisites + +1. Merge the release candidate into `master`. +2. Set the release version in `Cargo.toml` and update `Cargo.lock`. +3. Confirm that the normal `Wave CI` workflow passes on `master`. +4. Prepare and review the release post separately in the Wave blog repository. + +## Run the release + +1. Open the repository's **Actions** page. +2. Select **Manual Release**. +3. Select **Run workflow** and choose the `master` branch. +4. Enter the version without a `v` prefix, such as `0.1.9-pre-beta`. +5. Keep **draft** and **prerelease** enabled for a pre-beta release. + +The workflow rejects non-`master` revisions, malformed versions, a version that +does not match `Cargo.toml`, and an existing release tag. It then runs formatting, +Clippy, Rust tests, a release build, compiler version checks, and the Wave +end-to-end test suite. + +After validation, separate jobs package and smoke-test these toolchains: + +- `x86_64-linux-gnu` +- native macOS (`aarch64-apple-darwin` or `x86_64-apple-darwin`) +- `x86_64-pc-windows-gnu` + +The final job verifies every archive checksum and creates the GitHub release. +No release is created if validation, packaging, or a smoke test fails. With the +default inputs, the result remains a draft until a maintainer reviews and +publishes it from GitHub. + +## Recovery + +Rerun a failed workflow only after fixing the cause on `master`. If the final +release creation failed after a tag was created, inspect and remove or retain the +partial GitHub release deliberately before retrying; do not overwrite a release +tag automatically. diff --git a/front/error/src/error.rs b/front/error/src/error.rs index 54e06163..4302c0b8 100644 --- a/front/error/src/error.rs +++ b/front/error/src/error.rs @@ -252,14 +252,14 @@ impl WaveError { let end = (idx + 1).min(lines.len().saturating_sub(1)); let width = (end + 1).to_string().len().max(2); - for i in start..=end { + for (i, source_line) in lines.iter().enumerate().take(end + 1).skip(start) { let ln = i + 1; let ln_str = format!("{:>width$}", ln, width = width); eprintln!( " {} {} {}", ln_str.color("38,139,235").bold(), pipe, - lines[i] + source_line ); } diff --git a/front/lexer/src/lib.rs b/front/lexer/src/lib.rs index 34e2226b..cd62d86e 100644 --- a/front/lexer/src/lib.rs +++ b/front/lexer/src/lib.rs @@ -10,6 +10,9 @@ // 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. +// Lexer errors intentionally retain complete diagnostic context for rendering. +#![allow(clippy::result_large_err)] + pub mod core; pub mod cursor; pub mod ident; diff --git a/front/lexer/src/scan.rs b/front/lexer/src/scan.rs index 912048ef..c6a2c7fa 100644 --- a/front/lexer/src/scan.rs +++ b/front/lexer/src/scan.rs @@ -15,6 +15,7 @@ use crate::{Lexer, Token}; use error::{WaveError, WaveErrorKind}; impl<'a> Lexer<'a> { + #[allow(clippy::never_loop)] pub fn next_token(&mut self) -> Result { loop { self.skip_trivia()?; diff --git a/front/parser/src/generics.rs b/front/parser/src/generics.rs index 6ee0ee42..0cac50a3 100644 --- a/front/parser/src/generics.rs +++ b/front/parser/src/generics.rs @@ -11,8 +11,9 @@ // 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 crate::ast::{ - ASTNode, EnumNode, Expression, ExternFunctionNode, FunctionNode, MatchArm, MatchPattern, - ParameterNode, ProtoImplNode, StatementNode, StructNode, TypeAliasNode, VariableNode, WaveType, + ASTNode, EnumNode, Expression, ExternFunctionNode, FunctionNode, Literal, MatchArm, + MatchPattern, ParameterNode, ProtoImplNode, StatementNode, StructNode, TypeAliasNode, Value, + VariableNode, WaveType, }; use crate::types::{parse_type, split_top_level_generic_args, token_type_to_wave_type}; use std::collections::{BTreeMap, HashMap, HashSet}; @@ -20,6 +21,7 @@ use std::collections::{BTreeMap, HashMap, HashSet}; #[derive(Default)] struct GenericEnv { function_templates: HashMap, + function_parameters: HashMap>, struct_templates: HashMap, function_instances: BTreeMap, @@ -34,7 +36,19 @@ pub fn monomorphize_generics(ast: Vec) -> Result, String> for node in &ast { match node { - ASTNode::Function(f) if !f.generic_params.is_empty() => { + ASTNode::Function(f) => { + if env + .function_parameters + .insert(f.name.clone(), f.parameters.clone()) + .is_some() + { + return Err(format!("duplicate function '{}'", f.name)); + } + + if f.generic_params.is_empty() { + continue; + } + if env .function_templates .insert(f.name.clone(), f.clone()) @@ -446,7 +460,8 @@ fn rewrite_expression( type_args, args, } => { - let args = rewrite_expr_list(args, subst, env)?; + let mut args = rewrite_expr_list(args, subst, env)?; + append_default_arguments(&name, &mut args, env)?; if type_args.is_empty() { if env.function_templates.contains_key(&name) { @@ -572,6 +587,43 @@ fn rewrite_expression( } } +fn append_default_arguments( + name: &str, + args: &mut Vec, + env: &GenericEnv, +) -> Result<(), String> { + let Some(parameters) = env.function_parameters.get(name) else { + return Ok(()); + }; + + if args.len() > parameters.len() { + return Err(format!( + "function '{}' expects at most {} arguments, got {}", + name, + parameters.len(), + args.len() + )); + } + + for parameter in parameters.iter().skip(args.len()) { + let default = parameter + .initial_value + .as_ref() + .ok_or_else(|| format!("function '{}' requires argument '{}'", name, parameter.name))?; + args.push(value_to_expression(default)); + } + + Ok(()) +} + +fn value_to_expression(value: &Value) -> Expression { + match value { + Value::Int(value) => Expression::Literal(Literal::Int(value.to_string())), + Value::Float(value) => Expression::Literal(Literal::Float(*value)), + Value::Text(value) => Expression::Literal(Literal::String(value.clone())), + } +} + fn rewrite_wave_type( ty: &WaveType, subst: &HashMap, diff --git a/front/parser/src/lib.rs b/front/parser/src/lib.rs index 96230dde..3339faf4 100644 --- a/front/parser/src/lib.rs +++ b/front/parser/src/lib.rs @@ -10,6 +10,21 @@ // 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. +// These legacy parser APIs are being migrated incrementally; keep new lints fatal +// without forcing risky mechanical rewrites into a release hardening change. +#![allow( + clippy::clone_on_copy, + clippy::explicit_auto_deref, + clippy::manual_strip, + clippy::needless_borrow, + clippy::ptr_arg, + clippy::result_large_err, + clippy::type_complexity, + clippy::unnecessary_map_or, + clippy::useless_format, + clippy::while_let_on_iterator +)] + // Legacy parser modules still call `println!("Error: ...")` on failure paths. // Keep diagnostics single-sourced through `ParseError` by silencing those prints. macro_rules! println { diff --git a/llvm/src/codegen/address.rs b/llvm/src/codegen/address.rs index 839aa471..73a17895 100644 --- a/llvm/src/codegen/address.rs +++ b/llvm/src/codegen/address.rs @@ -213,6 +213,13 @@ fn addr_and_ty<'ctx>( struct_field_indices, ); + if matches!( + inner.as_ref(), + Expression::IndexAccess { .. } | Expression::FieldAccess { .. } + ) { + return (slot_ptr, slot_ty); + } + if !slot_ty.is_pointer_type() { // Legacy compatibility: // allow redundant `deref` on already-addressable lvalues diff --git a/llvm/src/expression/lvalue.rs b/llvm/src/expression/lvalue.rs index 4bbf98a4..9fea22e8 100644 --- a/llvm/src/expression/lvalue.rs +++ b/llvm/src/expression/lvalue.rs @@ -119,6 +119,24 @@ fn generate_lvalue_ir_typed<'ctx>( ), Expression::Deref(inner) => { + if matches!( + inner.as_ref(), + Expression::IndexAccess { .. } | Expression::FieldAccess { .. } + ) { + return generate_lvalue_ir_typed( + context, + builder, + inner, + variables, + module, + global_consts, + struct_types, + struct_field_indices, + target_data, + extern_c_info, + ); + } + let v = generate_expression_ir( context, builder, @@ -298,15 +316,41 @@ fn generate_lvalue_ir_typed<'ctx>( WaveType::Pointer(inner) => { let base_ptr = load_ptr_value(context, builder, base_addr, "load_index_base"); - let elem_llvm = - wave_type_to_llvm_type(context, &inner, struct_types, TypeFlavor::Value); - - let gep = unsafe { - builder - .build_gep(elem_llvm, base_ptr, &[idx], "ptr_elem_ptr") - .unwrap() - }; - (gep, *inner) + match *inner { + WaveType::Array(element, size) => { + let array_ty = wave_type_to_llvm_type( + context, + &WaveType::Array(element.clone(), size), + struct_types, + TypeFlavor::Value, + ); + let gep = unsafe { + builder + .build_gep( + array_ty, + base_ptr, + &[zero, idx], + "ptr_array_elem_ptr", + ) + .unwrap() + }; + (gep, *element) + } + element => { + let elem_llvm = wave_type_to_llvm_type( + context, + &element, + struct_types, + TypeFlavor::Value, + ); + let gep = unsafe { + builder + .build_gep(elem_llvm, base_ptr, &[idx], "ptr_elem_ptr") + .unwrap() + }; + (gep, element) + } + } } WaveType::String => { diff --git a/llvm/src/expression/rvalue/assign.rs b/llvm/src/expression/rvalue/assign.rs index 568a74b9..f3110b38 100644 --- a/llvm/src/expression/rvalue/assign.rs +++ b/llvm/src/expression/rvalue/assign.rs @@ -80,6 +80,12 @@ fn wave_type_of_lvalue<'ctx, 'a>(env: &ExprGenEnv<'ctx, 'a>, e: &Expression) -> } Expression::Deref(inner) => { let inner_ty = wave_type_of_lvalue(env, inner)?; + if matches!( + inner.as_ref(), + Expression::IndexAccess { .. } | Expression::FieldAccess { .. } + ) { + return Some(inner_ty); + } match inner_ty { WaveType::Pointer(t) => Some(*t), WaveType::String => Some(WaveType::Byte), @@ -90,7 +96,10 @@ fn wave_type_of_lvalue<'ctx, 'a>(env: &ExprGenEnv<'ctx, 'a>, e: &Expression) -> let t = wave_type_of_lvalue(env, target)?; match t { WaveType::Array(inner, _) => Some(*inner), - WaveType::Pointer(inner) => Some(*inner), + WaveType::Pointer(inner) => match *inner { + WaveType::Array(element, _) => Some(*element), + other => Some(other), + }, WaveType::String => Some(WaveType::Byte), _ => None, } @@ -315,12 +324,13 @@ pub(crate) fn gen_assign_operation<'ctx, 'a>( rhs = materialize_for_store(env, rhs, element_type, "assign_agg_load"); if rhs.get_type() != element_type { + let tag = format!("assignment to {:?}", target); rhs = coerce_basic_value( env.context, env.builder, rhs, element_type, - "assign_cast", + &tag, CoercionMode::Implicit, ); } @@ -469,12 +479,13 @@ pub(crate) fn gen_assignment<'ctx, 'a>( v = materialize_for_store(env, v, element_type, "assign_rhs_agg_load"); if v.get_type() != element_type { + let tag = format!("assignment to {:?}", target); v = coerce_basic_value( env.context, env.builder, v, element_type, - "assign_cast", + &tag, CoercionMode::Implicit, ); } diff --git a/llvm/src/expression/rvalue/pointers.rs b/llvm/src/expression/rvalue/pointers.rs index 13800915..02ae2e7f 100644 --- a/llvm/src/expression/rvalue/pointers.rs +++ b/llvm/src/expression/rvalue/pointers.rs @@ -102,6 +102,12 @@ fn infer_wave_type_of_expr<'ctx, 'a>( Expression::Deref(inner) => { let inner_ty = infer_wave_type_of_expr(env, inner)?; + if matches!( + inner.as_ref(), + Expression::IndexAccess { .. } | Expression::FieldAccess { .. } + ) { + return Some(inner_ty); + } match inner_ty { WaveType::Pointer(t) => Some(*t), WaveType::String => Some(WaveType::Byte), diff --git a/llvm/src/lib.rs b/llvm/src/lib.rs index 7833bb21..3fdb7565 100644 --- a/llvm/src/lib.rs +++ b/llvm/src/lib.rs @@ -10,6 +10,26 @@ // 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. +// Backend lowering APIs mirror LLVM's explicit context and ABI structures. +// Refactor these lints separately from release hardening to avoid ABI regressions. +#![allow( + clippy::box_collection, + clippy::clone_on_copy, + clippy::get_first, + clippy::match_like_matches_macro, + clippy::missing_safety_doc, + clippy::needless_lifetimes, + clippy::needless_range_loop, + clippy::needless_return, + clippy::only_used_in_recursion, + clippy::redundant_closure, + clippy::result_large_err, + clippy::single_match, + clippy::too_many_arguments, + clippy::type_complexity, + clippy::unnecessary_cast +)] + pub mod backend; pub mod codegen; pub mod expression; diff --git a/llvm/src/statement/variable.rs b/llvm/src/statement/variable.rs index 1882c512..fa0fa0e1 100644 --- a/llvm/src/statement/variable.rs +++ b/llvm/src/statement/variable.rs @@ -108,7 +108,10 @@ pub fn coerce_basic_value<'ctx>( // ptr -> int (BasicValueEnum::PointerValue(pv), BasicTypeEnum::IntType(dst)) => match mode { CoercionMode::Implicit => { - panic!("Implicit ptr->int is not allowed (use explicit cast)."); + panic!( + "Implicit ptr->int is not allowed during '{}' (use explicit cast).", + tag + ); } CoercionMode::Asm | CoercionMode::Explicit => builder .build_ptr_to_int(pv, dst, tag) @@ -264,7 +267,7 @@ pub(super) fn gen_variable_ir<'ctx>( builder, raw, llvm_type, - "init_cast", + &format!("variable '{}' initializer", name), CoercionMode::Implicit, ); diff --git a/src/cli.rs b/src/cli.rs index ce9066fa..7edf9c0a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -3115,7 +3115,7 @@ fn is_windows_gnu_target_global(global: &Global) -> bool { } fn ensure_supported_target(target: &str) -> Result<(), CliError> { - if target == host_target_triple() || supported_targets().iter().any(|t| *t == target) { + if target == host_target_triple() || supported_targets().contains(&target) { return Ok(()); } diff --git a/src/lib.rs b/src/lib.rs index 38dc140f..1e7f318f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,14 @@ // 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. +// CLI tables and compiler phase boundaries intentionally favor explicit data. +#![allow( + clippy::print_literal, + clippy::result_large_err, + clippy::too_many_arguments, + clippy::type_complexity +)] + pub mod cli; pub mod errors; pub mod flags; diff --git a/src/runner.rs b/src/runner.rs index e5bd56d8..4b9b9c23 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -759,8 +759,10 @@ fn emit_codegen_panic_and_exit( } fn build_import_config(dep: &DepFlags, target_os: Option) -> ImportConfig { - let mut config = ImportConfig::default(); - config.target_os = target_os; + let mut config = ImportConfig { + target_os, + ..ImportConfig::default() + }; for root in &dep.roots { config.dep_roots.push(PathBuf::from(root)); @@ -1232,7 +1234,7 @@ pub(crate) unsafe fn run_wave_file( if let Err((msg, loc)) = run_panic_guarded(|| { link_objects( - &[object_patch.clone()], + std::slice::from_ref(&object_patch), &exe_patch, &link.libs, &link.paths, diff --git a/src/version.rs b/src/version.rs index 7d16f6d6..49ef6673 100644 --- a/src/version.rs +++ b/src/version.rs @@ -15,8 +15,7 @@ use std::process::Command; const VERSION: &str = env!("CARGO_PKG_VERSION"); pub(crate) fn version() -> &'static str { - let version = VERSION; - version.into() + VERSION } pub fn get_os_pretty_name() -> String { @@ -41,7 +40,7 @@ pub fn get_os_pretty_name() -> String { return format!("Linux ({})", text.trim()); } - return "Linux (unknown version)".to_string(); + "Linux (unknown version)".to_string() } #[cfg(target_os = "windows")] diff --git a/test/test107.wave b/test/test107.wave index 3222eb99..9bb597a9 100644 --- a/test/test107.wave +++ b/test/test107.wave @@ -1,3 +1,5 @@ +// wave-test: mode=build, emit=obj + export(c, "wave_add_i32") fun add_i32(a: i32, b: i32) -> i32 { return a + b; } diff --git a/test/test108.wave b/test/test108.wave index e78ec474..24c1a71d 100644 --- a/test/test108.wave +++ b/test/test108.wave @@ -1,3 +1,5 @@ +// wave-test: mode=build, target=x86_64-pc-windows-gnu, emit=obj, freestanding=true + const KERNEL_ENTRY_POINT: u64 = 0x200000; const KERNEL_IMAGE_SIZE: u64 = 4; diff --git a/test/test22.wave b/test/test22.wave index 606e4db3..bb88867e 100644 --- a/test/test22.wave +++ b/test/test22.wave @@ -1,5 +1,3 @@ -import("std::iosys"); - fun hello() { var a: i32 = 0; println("hello world"); diff --git a/test/test27.wave b/test/test27.wave index bf1859b6..0daaf700 100644 --- a/test/test27.wave +++ b/test/test27.wave @@ -1,3 +1,8 @@ -fun main(name: str = "World") { +fun greet(name: str = "World") { println("Hello {}", name); -} \ No newline at end of file +} + +fun main() { + greet(); + greet("Wave"); +} diff --git a/test/test54.wave b/test/test54.wave index 5e1eede0..f27367de 100644 --- a/test/test54.wave +++ b/test/test54.wave @@ -1,4 +1,4 @@ -// wave-test: host-arch=x86_64 +// wave-test: host-arch=x86_64, mode=build, target=x86_64-unknown-none-elf, emit=obj, freestanding=true fun main() { asm { "mov ah, 0x0e" diff --git a/tests/codegen_regressions.rs b/tests/codegen_regressions.rs index 3eb48e19..f1609e02 100644 --- a/tests/codegen_regressions.rs +++ b/tests/codegen_regressions.rs @@ -93,6 +93,10 @@ struct Pair { b: i32; } +struct PointerBox { + data: ptr; +} + fun write_deref(p: ptr, v: i32) { deref p = v; } @@ -105,6 +109,14 @@ fun write_field(p: ptr, v: i32) { p.b = v; } +fun write_array_pointer(p: ptr>, v: i32) { + deref p[1] = v; +} + +fun write_pointer_field(p: ptr, value: ptr) { + deref p.data = value; +} + fun id_ptr(p: ptr) -> ptr { return p; } @@ -138,6 +150,19 @@ fun main() -> i32 { return 5; } + let mut array_ptr_target: array = [4, 5, 6]; + write_array_pointer(&array_ptr_target, 23); + if (array_ptr_target[1] != 23) { + return 6; + } + + let mut y: i32 = 88; + let mut pointer_box: PointerBox = PointerBox { data: &x }; + write_pointer_field(&pointer_box, &y); + if (pointer_box.data != &y) { + return 7; + } + return 0; } "#, diff --git a/tools/run_tests.py b/tools/run_tests.py index dfa62f63..06b7e408 100644 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -14,11 +14,14 @@ import subprocess import time +import argparse from pathlib import Path import threading import socket import sys import platform +import shutil +import tempfile ROOT = Path(__file__).resolve().parent.parent TEST_DIR = ROOT / "test" @@ -83,15 +86,40 @@ def normalize_arch(arch: str) -> str: HOST_ARCH = normalize_arch(HOST_ARCH) +TEST_OUTPUT_DIR = Path(tempfile.mkdtemp(prefix="wave-test-output-")) + + +def parse_args(): + parser = argparse.ArgumentParser(description="Run Wave end-to-end tests") + parser.add_argument( + "--only", + action="append", + default=[], + metavar="NAME", + help="run only the named test; may be repeated", + ) + parser.add_argument( + "--skip", + action="append", + default=[], + metavar="NAME", + help="skip the named test; may be repeated", + ) + return parser.parse_args() + + +ARGS = parse_args() def iter_test_entries(): for path in sorted(TEST_DIR.glob("test*.wave")): - yield path.name, path.relative_to(ROOT).as_posix() + if (not ARGS.only or path.name in ARGS.only) and path.name not in ARGS.skip: + 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() + if (not ARGS.only or name in ARGS.only) and name not in ARGS.skip: + yield name, main_wave.relative_to(ROOT).as_posix() def parse_test_metadata(rel_path: str): @@ -99,6 +127,11 @@ def parse_test_metadata(rel_path: str): meta = { "host_os": None, "host_arch": None, + "mode": "run", + "target": None, + "emit": "obj", + "freestanding": False, + "expected_exit": 0, } try: @@ -125,6 +158,16 @@ def parse_test_metadata(rel_path: str): meta["host_os"] = value.lower() elif key == "host-arch": meta["host_arch"] = normalize_arch(value) + elif key == "mode": + meta["mode"] = value.lower() + elif key == "target": + meta["target"] = value + elif key == "emit": + meta["emit"] = value.lower() + elif key == "freestanding": + meta["freestanding"] = value.lower() in {"1", "true", "yes"} + elif key == "expected-exit": + meta["expected_exit"] = int(value) except OSError: pass @@ -144,6 +187,38 @@ def skip_reason_for_metadata(name: str, rel_path: str): return None + +def command_for_test(name: str, rel_path: str): + meta = parse_test_metadata(rel_path) + mode = meta["mode"] + + if mode == "run": + return [str(WAVEC), "run", rel_path] + + if mode == "check": + return [str(WAVEC), "check", rel_path] + + if mode == "build": + output_dir = TEST_OUTPUT_DIR / name.replace(" ", "-") + output_dir.mkdir(parents=True, exist_ok=True) + + cmd = [ + str(WAVEC), + "build", + rel_path, + f"--emit={meta['emit']}", + "--out-dir", + str(output_dir), + ] + if meta["target"]: + cmd.extend(["--target", meta["target"]]) + if meta["freestanding"]: + cmd.append("--freestanding") + return cmd + + raise ValueError(f"unsupported wave-test mode '{mode}' in {rel_path}") + + def send_udp_for_test61(): time.sleep(0.5) try: @@ -211,7 +286,7 @@ def looks_like_fail(stderr: str) -> bool: # Return Type: # 1 = PASS (exit 0) -# 3 = PASS (exit nonzero) +# 3 = PASS (explicit expected nonzero exit) # 0 = FAIL # 2 = SKIP # -1 = TIMEOUT @@ -223,6 +298,8 @@ def run_and_classify(name, rel_path, cmd): print(f"{CYAN}→ SKIP ({skip_reason}){RESET}\n") return 2 + expected_exit = parse_test_metadata(rel_path)["expected_exit"] + stdin_data = None if name == "test22.wave": stdin_data = "3\n" @@ -267,12 +344,24 @@ def run_and_classify(name, rel_path, cmd): print() return 0 - if result.returncode == 0: + if result.returncode == expected_exit: + if expected_exit != 0: + print(f"{MAGENTA}→ PASS (expected exit={expected_exit}){RESET}\n") + return 3 print(f"{GREEN}→ PASS{RESET}\n") return 1 - print(f"{MAGENTA}→ PASS (non-zero exit={result.returncode}){RESET}\n") - return 3 + print( + f"{RED}→ FAIL (exit={result.returncode}, expected={expected_exit}){RESET}" + ) + if result.stdout.strip(): + print(f"{BLUE}--- STDOUT ---{RESET}") + print(result.stdout.rstrip()) + if result.stderr.strip(): + print(f"{YELLOW}--- STDERR ---{RESET}") + print(result.stderr.rstrip()) + print() + return 0 except subprocess.TimeoutExpired: if name in KNOWN_TIMEOUT: @@ -282,12 +371,26 @@ def run_and_classify(name, rel_path, cmd): print(f"{YELLOW}→ TIMEOUT ({TIMEOUT_SEC}s){RESET}\n") return -1 +entries = list(iter_test_entries()) +selected_names = {name for name, _ in entries} +missing_names = sorted(set(ARGS.only) - selected_names) + +if missing_names: + shutil.rmtree(TEST_OUTPUT_DIR, ignore_errors=True) + print(f"unknown test name(s): {', '.join(missing_names)}", file=sys.stderr) + sys.exit(2) + +if not entries: + shutil.rmtree(TEST_OUTPUT_DIR, ignore_errors=True) + print("no tests selected", file=sys.stderr) + sys.exit(2) + try: - for name, rel_path in iter_test_entries(): + for name, rel_path in entries: result = run_and_classify( name, rel_path, - [str(WAVEC), "run", rel_path] + command_for_test(name, rel_path) ) results.append((name, result)) @@ -295,6 +398,8 @@ def run_and_classify(name, rel_path, cmd): except KeyboardInterrupt: print(f"\n{YELLOW}Interrupted by user.{RESET}") sys.exit(130) +finally: + shutil.rmtree(TEST_OUTPUT_DIR, ignore_errors=True) pass_zero = [name for name, r in results if r == 1] pass_nonzero = [name for name, r in results if r == 3] @@ -310,7 +415,7 @@ def run_and_classify(name, rel_path, cmd): for name in pass_zero: print(f" - {name}") -print(f"\n{MAGENTA}PASS (non-zero exit) ({len(pass_nonzero)}){RESET}") +print(f"\n{MAGENTA}PASS (expected non-zero exit) ({len(pass_nonzero)}){RESET}") for name in pass_nonzero: print(f" - {name}") @@ -328,7 +433,7 @@ def run_and_classify(name, rel_path, cmd): print("\n=========================") print(f"{GREEN}PASS(0): {len(pass_zero)}{RESET}") -print(f"{MAGENTA}PASS(!0): {len(pass_nonzero)}{RESET}") +print(f"{MAGENTA}PASS(expected !0): {len(pass_nonzero)}{RESET}") print(f"{CYAN}SKIP: {len(skip_tests)}{RESET}") print(f"{RED}FAIL: {len(fail_tests)}{RESET}") print(f"{YELLOW}TIMEOUT: {len(timeout_tests)}{RESET}") diff --git a/utils/src/json.rs b/utils/src/json.rs index fac206e8..bd7164bf 100644 --- a/utils/src/json.rs +++ b/utils/src/json.rs @@ -292,7 +292,7 @@ impl Json { Json::Arr(arr) => { write!(w, "[")?; if pretty && !arr.is_empty() { - write!(w, "\n")?; + writeln!(w)?; } for (i, v) in arr.iter().enumerate() { @@ -305,7 +305,7 @@ impl Json { write!(w, ",")?; } if pretty { - write!(w, "\n")?; + writeln!(w)?; } } @@ -318,7 +318,7 @@ impl Json { Json::Obj(kv) => { write!(w, "{{")?; if pretty && !kv.is_empty() { - write!(w, "\n")?; + writeln!(w)?; } for (i, (k, v)) in kv.iter().enumerate() { @@ -337,7 +337,7 @@ impl Json { write!(w, ",")?; } if pretty { - write!(w, "\n")?; + writeln!(w)?; } } diff --git a/x.py b/x.py index a8b9414a..a878809a 100644 --- a/x.py +++ b/x.py @@ -44,8 +44,14 @@ "WAVE_WINDOWS_RUST_TOOLCHAIN", "stable-x86_64-pc-windows-gnu", ) -MINGW_CC = "x86_64-w64-mingw32-gcc" -MINGW_CXX = "x86_64-w64-mingw32-g++" +MINGW_CC = os.environ.get( + "WAVE_MINGW_CC", + "gcc" if platform.system() == "Windows" else "x86_64-w64-mingw32-gcc", +) +MINGW_CXX = os.environ.get( + "WAVE_MINGW_CXX", + "g++" if platform.system() == "Windows" else "x86_64-w64-mingw32-g++", +) TARGET_MATRIX = { "x86_64-unknown-linux-gnu": ["Linux"], @@ -183,6 +189,25 @@ def require_tool(tool): sys.exit(1) def configure_windows_gnu_env(env): + if platform.system() == "Windows": + prefix = early_llvm_prefix() + llvm_config = env.get("LLVM_CONFIG_PATH") or shutil.which("llvm-config") + + if prefix is None or llvm_config is None: + print("[!] Native Windows release builds require LLVM 21 and llvm-config.exe.") + print(" Set WAVE_LLVM_HOME and LLVM_CONFIG_PATH to the LLVM 21 prefix.") + sys.exit(1) + + require_tool(MINGW_CC) + require_tool(MINGW_CXX) + + env["LLVM_SYS_211_PREFIX"] = str(prefix) + env["LLVM_CONFIG_PATH"] = str(llvm_config) + env["CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER"] = MINGW_CC + env["CC_x86_64_pc_windows_gnu"] = MINGW_CC + env["CXX_x86_64_pc_windows_gnu"] = MINGW_CXX + return + if not WINDOWS_LLVM_PREFIX.exists(): print(f"[!] Missing Windows LLVM prefix wrapper: {WINDOWS_LLVM_PREFIX}") print(" Expected tools/llvm-win-prefix/bin/llvm-config to exist.") @@ -218,7 +243,7 @@ def configure_linux_release_env(env): ]) def cargo_build_args(target): - args = ["cargo", "build", "--target", target, "--release"] + args = ["cargo", "build", "--locked", "--target", target, "--release"] if is_windows_gnu_target(target): args.extend(["--no-default-features", "--features", "llvm-target-x86"]) return args From 476c9b5c7aa8db2c5139471f758a3b8a75844787 Mon Sep 17 00:00:00 2001 From: LunaStev Date: Sat, 20 Jun 2026 12:26:32 +0900 Subject: [PATCH 02/12] chore: add Cargo.lock for dependency tracking and reproducible builds Signed-off-by: LunaStev --- Cargo.lock | 203 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..3312063e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,203 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "cc" +version = "1.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "error" +version = "0.1.0" +dependencies = [ + "utils", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "inkwell" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1def4112dfb2ce2993db7027f7acdb43c1f4ee1c70a082a2eef306ed5d0df365" +dependencies = [ + "inkwell_internals", + "libc", + "llvm-sys", + "once_cell", + "thiserror", +] + +[[package]] +name = "inkwell_internals" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63736175c9a30ea123f7018de9f26163e0b39cd6978990ae486b510c4f3bad69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lexer" +version = "0.1.0" +dependencies = [ + "error", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "llvm" +version = "0.1.0" +dependencies = [ + "error", + "inkwell", + "lexer", + "llvm-sys", + "parser", +] + +[[package]] +name = "llvm-sys" +version = "211.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44007a7a44b73bdd877fa9c9ccef256036511220e90f65b4d50e7a15773c0ee3" +dependencies = [ + "anyhow", + "cc", + "lazy_static", + "libc", + "regex-lite", + "semver", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "parser" +version = "0.1.0" +dependencies = [ + "error", + "lexer", + "utils", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "utils" +version = "0.1.0" + +[[package]] +name = "wavec" +version = "0.1.9-pre-beta" +dependencies = [ + "error", + "lexer", + "llvm", + "parser", + "utils", +] From 74359f50d2dbbbf0d551ef3fab8d0cc151db343c Mon Sep 17 00:00:00 2001 From: LunaStev Date: Sat, 20 Jun 2026 12:27:27 +0900 Subject: [PATCH 03/12] chore(ci): add manual release workflow for cross-platform compiler packaging and GitHub release creation Signed-off-by: LunaStev --- .github/workflows/release.yml | 382 ++++++++++++++++++++++++++++++++++ 1 file changed, 382 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..32baf844 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,382 @@ +# This file is part of the Wave language project. +# SPDX-License-Identifier: MPL-2.0 +# AI TRAINING NOTICE: Prohibited without prior written permission. + +name: Manual Release + +on: + workflow_dispatch: + inputs: + version: + description: "Cargo version without the v prefix (for example: 0.1.9-pre-beta)" + required: true + type: string + draft: + description: "Create the GitHub release as a draft" + required: true + default: true + type: boolean + prerelease: + description: "Mark the GitHub release as a prerelease" + required: true + default: true + type: boolean + +permissions: + contents: read + +concurrency: + group: manual-release-${{ inputs.version }} + cancel-in-progress: false + +env: + CARGO_TERM_COLOR: always + NO_COLOR: "1" + PYTHONUNBUFFERED: "1" + LLVM_VERSION: "21" + RELEASE_VERSION: ${{ inputs.version }} + +jobs: + validate: + name: Validate release candidate + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Validate branch, version, and tag + shell: bash + run: | + set -euo pipefail + + if [[ "$GITHUB_REF" != "refs/heads/master" ]]; then + echo "Release workflow must run from master, got: $GITHUB_REF" >&2 + exit 1 + fi + + if [[ ! "$RELEASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then + echo "Invalid release version: $RELEASE_VERSION" >&2 + exit 1 + fi + + cargo_version="$(python3 -c 'import tomllib; print(tomllib.load(open("Cargo.toml", "rb"))["package"]["version"])')" + if [[ "$cargo_version" != "$RELEASE_VERSION" ]]; then + echo "Cargo.toml version is $cargo_version, input is $RELEASE_VERSION" >&2 + exit 1 + fi + + tag="v$RELEASE_VERSION" + remote_tag="$(git ls-remote --tags origin "refs/tags/$tag")" + if [[ -n "$remote_tag" ]]; then + echo "Tag already exists: $tag" >&2 + exit 1 + fi + + - name: Install LLVM 21 + shell: bash + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y wget software-properties-common + wget -q https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh "$LLVM_VERSION" + echo "LLVM_SYS_211_PREFIX=/usr/lib/llvm-21" >> "$GITHUB_ENV" + echo "LLVM_CONFIG_PATH=/usr/lib/llvm-21/bin/llvm-config" >> "$GITHUB_ENV" + echo "/usr/lib/llvm-21/bin" >> "$GITHUB_PATH" + + - name: Check formatting + run: cargo fmt --all --check + + - name: Run Clippy + run: cargo clippy --locked --all-targets -- -D warnings + + - name: Validate Python tooling + run: python3 -m py_compile x.py tools/run_tests.py + + - name: Run Rust tests + run: cargo test --locked --all-targets --verbose + + - name: Build release compiler + run: cargo build --locked --release --verbose + + - name: Verify compiler version + shell: bash + run: | + set -euo pipefail + target/release/wavec -V | tee /tmp/wavec-version.txt + grep -F "wavec $RELEASE_VERSION" /tmp/wavec-version.txt + grep -F "backend: LLVM 21." /tmp/wavec-version.txt + + - name: Run Wave end-to-end tests + run: python3 tools/run_tests.py + + package-linux: + name: Package Linux x86_64 + needs: validate + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install packaging dependencies + shell: bash + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y file patchelf wget software-properties-common + wget -q https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh "$LLVM_VERSION" + echo "LLVM_SYS_211_PREFIX=/usr/lib/llvm-21" >> "$GITHUB_ENV" + echo "LLVM_CONFIG_PATH=/usr/lib/llvm-21/bin/llvm-config" >> "$GITHUB_ENV" + echo "/usr/lib/llvm-21/bin" >> "$GITHUB_PATH" + + - name: Build and package + run: python3 x.py release x86_64-unknown-linux-gnu + + - name: Smoke test packaged compiler + shell: bash + run: | + set -euo pipefail + archive="wave-v${RELEASE_VERSION}-x86_64-linux-gnu.tar.gz" + temp_dir="$(mktemp -d)" + trap 'rm -rf "$temp_dir"' EXIT + tar -xzf "$archive" -C "$temp_dir" + package="$temp_dir/wave-v${RELEASE_VERSION}-x86_64-linux-gnu" + + env -i PATH=/usr/bin:/bin HOME=/tmp "$package/wavec" -V | tee /tmp/wavec-version.txt + grep -F "wavec $RELEASE_VERSION" /tmp/wavec-version.txt + grep -F "backend: LLVM 21." /tmp/wavec-version.txt + + printf 'fun main() { println("release smoke"); }\n' > "$temp_dir/smoke.wave" + env -i PATH=/usr/bin:/bin HOME=/tmp "$package/wavec" run "$temp_dir/smoke.wave" \ + | grep -Fx "release smoke" + + sha256sum "$archive" > "$archive.sha256" + + - uses: actions/upload-artifact@v4 + with: + name: release-linux-x86_64 + path: | + wave-v${{ inputs.version }}-x86_64-linux-gnu.tar.gz + wave-v${{ inputs.version }}-x86_64-linux-gnu.tar.gz.sha256 + if-no-files-found: error + retention-days: 7 + + package-macos: + name: Package macOS + needs: validate + runs-on: macos-latest + timeout-minutes: 60 + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install LLVM 21 + shell: bash + run: | + set -euo pipefail + brew install llvm@21 + llvm_prefix="$(brew --prefix llvm@21)" + echo "LLVM_SYS_211_PREFIX=$llvm_prefix" >> "$GITHUB_ENV" + echo "LLVM_CONFIG_PATH=$llvm_prefix/bin/llvm-config" >> "$GITHUB_ENV" + echo "$llvm_prefix/bin" >> "$GITHUB_PATH" + + - name: Select native target + shell: bash + run: | + set -euo pipefail + case "$(uname -m)" in + arm64) target="aarch64-apple-darwin" ;; + x86_64) target="x86_64-apple-darwin" ;; + *) echo "Unsupported macOS architecture: $(uname -m)" >&2; exit 1 ;; + esac + echo "MACOS_TARGET=$target" >> "$GITHUB_ENV" + + - name: Build and package + run: python3 x.py release "$MACOS_TARGET" + + - name: Smoke test packaged compiler + shell: bash + run: | + set -euo pipefail + archive="wave-v${RELEASE_VERSION}-${MACOS_TARGET}.tar.gz" + temp_dir="$(mktemp -d)" + trap 'rm -rf "$temp_dir"' EXIT + tar -xzf "$archive" -C "$temp_dir" + package="$temp_dir/wave-v${RELEASE_VERSION}-${MACOS_TARGET}" + + env -i PATH=/usr/bin:/bin HOME=/tmp "$package/wavec" -V | tee /tmp/wavec-version.txt + grep -F "wavec $RELEASE_VERSION" /tmp/wavec-version.txt + grep -F "backend: LLVM 21." /tmp/wavec-version.txt + + printf 'fun main() { println("release smoke"); }\n' > "$temp_dir/smoke.wave" + env -i PATH=/usr/bin:/bin HOME=/tmp "$package/wavec" run "$temp_dir/smoke.wave" \ + | grep -Fx "release smoke" + + shasum -a 256 "$archive" > "$archive.sha256" + + - uses: actions/upload-artifact@v4 + with: + name: release-macos + path: | + wave-v${{ inputs.version }}-*-apple-darwin.tar.gz + wave-v${{ inputs.version }}-*-apple-darwin.tar.gz.sha256 + if-no-files-found: error + retention-days: 7 + + package-windows: + name: Package Windows x86_64 + needs: validate + runs-on: windows-latest + timeout-minutes: 90 + + steps: + - uses: actions/checkout@v4 + + - uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: true + path-type: inherit + install: >- + file + unzip + zip + mingw-w64-x86_64-gcc + mingw-w64-x86_64-python + mingw-w64-x86_64-llvm-21 + mingw-w64-x86_64-lld-21 + + - name: Install Rust GNU toolchains + shell: msys2 {0} + run: | + set -euo pipefail + rustup target add x86_64-pc-windows-gnu + rustup toolchain install stable-x86_64-pc-windows-gnu --profile minimal + + - name: Configure native Windows LLVM + shell: msys2 {0} + run: | + set -euo pipefail + llvm_root="$(cygpath -w /mingw64/opt/llvm-21)" + llvm_bin="$(cygpath -w /mingw64/opt/llvm-21/bin)" + llvm_config="$(cygpath -w /mingw64/opt/llvm-21/bin/llvm-config.exe)" + echo "WAVE_LLVM_HOME=$llvm_root" >> "$GITHUB_ENV" + echo "WAVE_WINDOWS_LLVM_BIN=$llvm_bin" >> "$GITHUB_ENV" + echo "LLVM_SYS_211_PREFIX=$llvm_root" >> "$GITHUB_ENV" + echo "LLVM_CONFIG_PATH=$llvm_config" >> "$GITHUB_ENV" + echo "$llvm_bin" >> "$GITHUB_PATH" + echo "$(cygpath -w /mingw64/bin)" >> "$GITHUB_PATH" + echo "$(cygpath -w /usr/bin)" >> "$GITHUB_PATH" + + - name: Build and package + shell: msys2 {0} + run: python x.py release x86_64-pc-windows-gnu + + - name: Smoke test packaged compiler + shell: msys2 {0} + run: | + set -euo pipefail + archive="wave-v${RELEASE_VERSION}-x86_64-pc-windows-gnu.zip" + temp_dir="$(mktemp -d)" + trap 'rm -rf "$temp_dir"' EXIT + unzip -q "$archive" -d "$temp_dir" + package="$temp_dir/wave-v${RELEASE_VERSION}-x86_64-pc-windows-gnu" + + "$package/wavec.exe" -V | tee /tmp/wavec-version.txt + grep -F "wavec $RELEASE_VERSION" /tmp/wavec-version.txt + grep -F "backend: LLVM 21." /tmp/wavec-version.txt + + printf 'fun main() { println("release smoke"); }\n' > "$temp_dir/smoke.wave" + "$package/wavec.exe" run "$(cygpath -w "$temp_dir/smoke.wave")" | grep -Fx "release smoke" + + sha256sum "$archive" > "$archive.sha256" + + - uses: actions/upload-artifact@v4 + with: + name: release-windows-x86_64 + path: | + wave-v${{ inputs.version }}-x86_64-pc-windows-gnu.zip + wave-v${{ inputs.version }}-x86_64-pc-windows-gnu.zip.sha256 + if-no-files-found: error + retention-days: 7 + + publish: + name: Create GitHub release + needs: [validate, package-linux, package-macos, package-windows] + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: write + + steps: + - uses: actions/download-artifact@v4 + with: + pattern: release-* + path: release-assets + merge-multiple: true + + - name: Verify release assets + shell: bash + working-directory: release-assets + run: | + set -euo pipefail + test -f "wave-v${RELEASE_VERSION}-x86_64-linux-gnu.tar.gz" + test -f "wave-v${RELEASE_VERSION}-x86_64-pc-windows-gnu.zip" + + mapfile -t macos_archives < <(find . -maxdepth 1 -name "wave-v${RELEASE_VERSION}-*-apple-darwin.tar.gz" -print) + if [[ "${#macos_archives[@]}" -ne 1 ]]; then + echo "Expected exactly one macOS archive, found ${#macos_archives[@]}" >&2 + exit 1 + fi + + for archive in *.tar.gz *.zip; do + test -f "$archive.sha256" + done + cat ./*.sha256 > SHA256SUMS + sha256sum --check SHA256SUMS + + - name: Create GitHub release + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + tag="v$RELEASE_VERSION" + args=( + release create "$tag" + release-assets/*.tar.gz + release-assets/*.zip + release-assets/SHA256SUMS + --repo "$GITHUB_REPOSITORY" + --target "$GITHUB_SHA" + --title "Wave $tag" + --generate-notes + ) + + if [[ "${{ inputs.draft }}" == "true" ]]; then + args+=(--draft) + fi + if [[ "${{ inputs.prerelease }}" == "true" ]]; then + args+=(--prerelease) + fi + + gh "${args[@]}" From 0e68bc29e6d0f8f756f2d8dfc2e354a9b81a0000 Mon Sep 17 00:00:00 2001 From: LunaStev Date: Sat, 20 Jun 2026 12:31:37 +0900 Subject: [PATCH 04/12] chore: add Clippy lint exceptions for collapsible match statements Signed-off-by: LunaStev --- front/parser/src/generics.rs | 9 +++++---- front/parser/src/verification.rs | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/front/parser/src/generics.rs b/front/parser/src/generics.rs index 0cac50a3..65f069de 100644 --- a/front/parser/src/generics.rs +++ b/front/parser/src/generics.rs @@ -57,6 +57,7 @@ pub fn monomorphize_generics(ast: Vec) -> Result, String> return Err(format!("duplicate generic function template '{}'", f.name)); } } + #[allow(clippy::collapsible_match)] ASTNode::Struct(s) if !s.generic_params.is_empty() => { if env .struct_templates @@ -84,11 +85,11 @@ pub fn monomorphize_generics(ast: Vec) -> Result, String> )?)); } } - ASTNode::Struct(s) => { - if s.generic_params.is_empty() { - out.push(ASTNode::Struct(rewrite_struct(s, &empty_subst, &mut env)?)); - } + ASTNode::Struct(s) if s.generic_params.is_empty() => { + out.push(ASTNode::Struct(rewrite_struct(s, &empty_subst, &mut env)?)); } + + ASTNode::Struct(_) => {} ASTNode::Variable(v) => { out.push(ASTNode::Variable(rewrite_variable( v, diff --git a/front/parser/src/verification.rs b/front/parser/src/verification.rs index 48a3f6ed..33608343 100644 --- a/front/parser/src/verification.rs +++ b/front/parser/src/verification.rs @@ -150,6 +150,7 @@ fn validate_expr( Expression::Literal(_) => {} + #[allow(clippy::collapsible_match)] Expression::Variable(name) => { if lookup_mutability(name, scopes, globals).is_none() { return Err(format!("use of undeclared identifier `{}`", name)); @@ -297,6 +298,7 @@ fn validate_node( scopes.pop(); } + #[allow(clippy::collapsible_match)] ASTNode::ExternFunction(ext) => { if !ext.abi.eq_ignore_ascii_case("c") { return Err(format!( From 3fb19aad3a0621e574730cbd1cf0be82b73f753d Mon Sep 17 00:00:00 2001 From: LunaStev Date: Sat, 20 Jun 2026 12:35:56 +0900 Subject: [PATCH 05/12] chore: allow Clippy lint exceptions for collapsible match statements in binary.rs and plan.rs Signed-off-by: LunaStev --- llvm/src/codegen/plan.rs | 1 + llvm/src/expression/rvalue/binary.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/llvm/src/codegen/plan.rs b/llvm/src/codegen/plan.rs index cdedac6e..9fa976e4 100644 --- a/llvm/src/codegen/plan.rs +++ b/llvm/src/codegen/plan.rs @@ -524,6 +524,7 @@ fn strip_inline_asm_comment(line: &str) -> &str { fn asm_instruction_text(line: &str) -> String { let mut code = strip_inline_asm_comment(line).trim().to_ascii_lowercase(); + #[allow(clippy::collapsible_match)] loop { let Some((label, rest)) = code.split_once(':') else { break; diff --git a/llvm/src/expression/rvalue/binary.rs b/llvm/src/expression/rvalue/binary.rs index 714bbf91..8623fad7 100644 --- a/llvm/src/expression/rvalue/binary.rs +++ b/llvm/src/expression/rvalue/binary.rs @@ -321,6 +321,7 @@ pub(crate) fn gen<'ctx, 'a>( _ => panic!("Unsupported float operator"), }; + #[allow(clippy::collapsible_match)] if let Some(exp) = expected_type { match (result, exp) { ( From 908733460b04979b561aaf85b3f2338abf61bcf9 Mon Sep 17 00:00:00 2001 From: LunaStev Date: Sat, 20 Jun 2026 12:37:22 +0900 Subject: [PATCH 06/12] chore: allow Clippy lint exception for collapsible match in plan.rs Signed-off-by: LunaStev --- llvm/src/codegen/plan.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm/src/codegen/plan.rs b/llvm/src/codegen/plan.rs index 9fa976e4..80480ead 100644 --- a/llvm/src/codegen/plan.rs +++ b/llvm/src/codegen/plan.rs @@ -521,10 +521,10 @@ fn strip_inline_asm_comment(line: &str) -> &str { .unwrap_or(line) } +#[allow(clippy::collapsible_match)] fn asm_instruction_text(line: &str) -> String { let mut code = strip_inline_asm_comment(line).trim().to_ascii_lowercase(); - #[allow(clippy::collapsible_match)] loop { let Some((label, rest)) = code.split_once(':') else { break; From 11699eef118d65d51ec280e0909edd0cc5dfb7b8 Mon Sep 17 00:00:00 2001 From: LunaStev Date: Sat, 20 Jun 2026 12:39:34 +0900 Subject: [PATCH 07/12] refactor: replace `loop` with `while let` for improved clarity in `asm_instruction_text` function Signed-off-by: LunaStev --- llvm/src/codegen/plan.rs | 7 +------ src/version.rs | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/llvm/src/codegen/plan.rs b/llvm/src/codegen/plan.rs index 80480ead..7c601d7f 100644 --- a/llvm/src/codegen/plan.rs +++ b/llvm/src/codegen/plan.rs @@ -521,15 +521,10 @@ fn strip_inline_asm_comment(line: &str) -> &str { .unwrap_or(line) } -#[allow(clippy::collapsible_match)] fn asm_instruction_text(line: &str) -> String { let mut code = strip_inline_asm_comment(line).trim().to_ascii_lowercase(); - loop { - let Some((label, rest)) = code.split_once(':') else { - break; - }; - + while let Some((label, rest)) = code.split_once(':') { let label = label.trim(); if label.is_empty() || !label diff --git a/src/version.rs b/src/version.rs index 49ef6673..7c2529e2 100644 --- a/src/version.rs +++ b/src/version.rs @@ -60,7 +60,7 @@ pub fn get_os_pretty_name() -> String { return format!("macOS {}", version); } - return "macOS (unknown version)".to_string(); + "macOS (unknown version)".to_string(); } #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))] From d9c1236948bd62a49f301e321b6301de50d0681e Mon Sep 17 00:00:00 2001 From: LunaStev Date: Sat, 20 Jun 2026 12:51:20 +0900 Subject: [PATCH 08/12] chore(ci): add Wave end-to-end testing step to Rust workflow and fix macOS version string syntax in `version.rs` Signed-off-by: LunaStev --- .github/workflows/rust.yml | 12 ++++++++++++ src/version.rs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 89b0be5a..6eb5e104 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -28,6 +28,12 @@ jobs: with: python-version: "3.12" + - name: Run Wave end-to-end tests + run: | + mkdir -p "$HOME/.wave/lib/wave" + cp -R std "$HOME/.wave/lib/wave/std" + python3 tools/run_tests.py + - name: Install LLVM 21 run: | sudo apt-get update @@ -71,6 +77,12 @@ jobs: run: | brew install llvm@21 + - name: Run Wave end-to-end tests + run: | + mkdir -p "$HOME/.wave/lib/wave" + cp -R std "$HOME/.wave/lib/wave/std" + python3 tools/run_tests.py + - name: Set LLVM env run: | echo "LLVM_SYS_211_PREFIX=$(brew --prefix llvm@21)" >> $GITHUB_ENV diff --git a/src/version.rs b/src/version.rs index 7c2529e2..ac667786 100644 --- a/src/version.rs +++ b/src/version.rs @@ -60,7 +60,7 @@ pub fn get_os_pretty_name() -> String { return format!("macOS {}", version); } - "macOS (unknown version)".to_string(); + "macOS (unknown version)".to_string() } #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))] From d3619ba143b2a02a739b0973c8ffcc88350c0636 Mon Sep 17 00:00:00 2001 From: LunaStev Date: Sat, 20 Jun 2026 12:54:07 +0900 Subject: [PATCH 09/12] chore(ci): restructure Rust workflow for improved clarity and modularity - Added explicit "Setup Rust" step using `dtolnay/rust-toolchain` (v1.89.0) with `rustfmt` and `clippy`. - Refactored Wave stdlib installation into a dedicated step. - Separated LLVM environment configuration into its own step for better readability. - Adjusted step ordering for consistency between Linux and macOS workflows. Signed-off-by: LunaStev --- .github/workflows/rust.yml | 42 ++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6eb5e104..03667d9a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -24,16 +24,16 @@ jobs: steps: - uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@1.89.0 + with: + components: rustfmt, clippy + - uses: actions/setup-python@v5 with: python-version: "3.12" - - name: Run Wave end-to-end tests - run: | - mkdir -p "$HOME/.wave/lib/wave" - cp -R std "$HOME/.wave/lib/wave/std" - python3 tools/run_tests.py - - name: Install LLVM 21 run: | sudo apt-get update @@ -45,6 +45,12 @@ jobs: echo "LLVM_CONFIG_PATH=/usr/lib/llvm-21/bin/llvm-config" >> "$GITHUB_ENV" echo "/usr/lib/llvm-21/bin" >> "$GITHUB_PATH" + - name: Install Wave stdlib + run: | + mkdir -p "$HOME/.wave/lib/wave" + rm -rf "$HOME/.wave/lib/wave/std" + cp -R std "$HOME/.wave/lib/wave/std" + - name: Check Rust formatting run: cargo fmt --all --check @@ -69,6 +75,12 @@ jobs: steps: - uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@1.89.0 + with: + components: rustfmt, clippy + - uses: actions/setup-python@v5 with: python-version: "3.12" @@ -77,17 +89,17 @@ jobs: run: | brew install llvm@21 - - name: Run Wave end-to-end tests + - name: Set LLVM env run: | - mkdir -p "$HOME/.wave/lib/wave" - cp -R std "$HOME/.wave/lib/wave/std" - python3 tools/run_tests.py + echo "LLVM_SYS_211_PREFIX=$(brew --prefix llvm@21)" >> "$GITHUB_ENV" + echo "LLVM_CONFIG_PATH=$(brew --prefix llvm@21)/bin/llvm-config" >> "$GITHUB_ENV" + echo "$(brew --prefix llvm@21)/bin" >> "$GITHUB_PATH" - - name: Set LLVM env + - name: Install Wave stdlib run: | - echo "LLVM_SYS_211_PREFIX=$(brew --prefix llvm@21)" >> $GITHUB_ENV - echo "LLVM_CONFIG_PATH=$(brew --prefix llvm@21)/bin/llvm-config" >> $GITHUB_ENV - echo "$(brew --prefix llvm@21)/bin" >> $GITHUB_PATH + mkdir -p "$HOME/.wave/lib/wave" + rm -rf "$HOME/.wave/lib/wave/std" + cp -R std "$HOME/.wave/lib/wave/std" - name: Check Rust formatting run: cargo fmt --all --check @@ -105,4 +117,4 @@ jobs: run: cargo test --locked --all-targets --verbose - name: Run Wave end-to-end tests - run: python3 tools/run_tests.py + run: python3 tools/run_tests.py \ No newline at end of file From c15630e894bd89edb286dca446d2e62763303261 Mon Sep 17 00:00:00 2001 From: LunaStev Date: Sat, 20 Jun 2026 12:58:57 +0900 Subject: [PATCH 10/12] chore(ci): enhance LLVM setup in Rust workflow and add tool verification step Signed-off-by: LunaStev --- .github/workflows/rust.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 03667d9a..47ca5235 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -91,9 +91,19 @@ jobs: - name: Set LLVM env run: | - echo "LLVM_SYS_211_PREFIX=$(brew --prefix llvm@21)" >> "$GITHUB_ENV" - echo "LLVM_CONFIG_PATH=$(brew --prefix llvm@21)/bin/llvm-config" >> "$GITHUB_ENV" - echo "$(brew --prefix llvm@21)/bin" >> "$GITHUB_PATH" + LLVM_PREFIX="$(brew --prefix llvm@21)" + echo "LLVM_SYS_211_PREFIX=$LLVM_PREFIX" >> "$GITHUB_ENV" + echo "LLVM_CONFIG_PATH=$LLVM_PREFIX/bin/llvm-config" >> "$GITHUB_ENV" + echo "$LLVM_PREFIX/bin" >> "$GITHUB_PATH" + echo "WAVE_LD64_LLD=$LLVM_PREFIX/bin/ld64.lld" >> "$GITHUB_ENV" + + - name: Verify LLVM tools + run: | + llvm-config --version + which clang || true + which lld || true + which ld64.lld || true + test -x "$WAVE_LD64_LLD" - name: Install Wave stdlib run: | From 2c442c5d624665321c20fd9aec8767dda6dd832c Mon Sep 17 00:00:00 2001 From: LunaStev Date: Sat, 20 Jun 2026 13:02:53 +0900 Subject: [PATCH 11/12] chore(ci): update Rust workflow to install LLD and adjust LLVM/LLD environment setup Signed-off-by: LunaStev --- .github/workflows/rust.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 47ca5235..a3074ebf 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -89,21 +89,25 @@ jobs: run: | brew install llvm@21 + - name: Install LLVM 21 and LLD + run: | + brew install llvm@21 lld + - name: Set LLVM env run: | LLVM_PREFIX="$(brew --prefix llvm@21)" + LLD_PREFIX="$(brew --prefix lld)" echo "LLVM_SYS_211_PREFIX=$LLVM_PREFIX" >> "$GITHUB_ENV" echo "LLVM_CONFIG_PATH=$LLVM_PREFIX/bin/llvm-config" >> "$GITHUB_ENV" echo "$LLVM_PREFIX/bin" >> "$GITHUB_PATH" - echo "WAVE_LD64_LLD=$LLVM_PREFIX/bin/ld64.lld" >> "$GITHUB_ENV" + echo "$LLD_PREFIX/bin" >> "$GITHUB_PATH" + echo "WAVE_LD64_LLD=$LLD_PREFIX/bin/ld64.lld" >> "$GITHUB_ENV" - name: Verify LLVM tools run: | llvm-config --version - which clang || true - which lld || true - which ld64.lld || true - test -x "$WAVE_LD64_LLD" + command -v ld64.lld + ld64.lld --version || true - name: Install Wave stdlib run: | From 2733a62af66c4b758f202735c9c0b8282a7543a0 Mon Sep 17 00:00:00 2001 From: LunaStev Date: Sat, 20 Jun 2026 13:12:10 +0900 Subject: [PATCH 12/12] chore(tests, codegen): add host-arch metadata to tests and refactor register normalization in plan.rs Signed-off-by: LunaStev --- llvm/src/codegen/plan.rs | 5 +++-- test/test103.wave | 2 ++ test/test104.wave | 2 ++ test/test105.wave | 2 ++ test/test106.wave | 2 ++ test/test84.wave | 2 ++ 6 files changed, 13 insertions(+), 2 deletions(-) diff --git a/llvm/src/codegen/plan.rs b/llvm/src/codegen/plan.rs index 7c601d7f..3c8a08f9 100644 --- a/llvm/src/codegen/plan.rs +++ b/llvm/src/codegen/plan.rs @@ -858,9 +858,10 @@ impl<'a> AsmPlan<'a> { } // class constraints (r/rm/m/...) -> allow duplicates + let reg_norm = t.phys_group.clone().unwrap_or_else(|| t.raw_norm.clone()); outputs.push(AsmOut { reg_raw: reg.clone(), - reg_norm: t.raw_norm, + reg_norm, phys_group: t.phys_group, target: out_target, }); @@ -900,7 +901,7 @@ impl<'a> AsmPlan<'a> { } inputs.push(AsmIn { - constraint: format!("{{{}}}", t.raw_norm), // "{rax}", "{dl}", "{r8d}", ... + constraint: format!("{{{}}}", pg), // "{rax}", "{dl}", "{r8d}", ... phys_group: Some(pg.clone()), value: expr, }); diff --git a/test/test103.wave b/test/test103.wave index f3d8814c..42c7cacb 100644 --- a/test/test103.wave +++ b/test/test103.wave @@ -1,3 +1,5 @@ +// wave-test: host-arch=x86_64 + import("std::string::len"); import("std::fs::consts"); import("std::fs::file"); diff --git a/test/test104.wave b/test/test104.wave index 159596e9..732a9361 100644 --- a/test/test104.wave +++ b/test/test104.wave @@ -1,3 +1,5 @@ +// wave-test: host-arch=x86_64 + import("std::io::fd"); import("std::net::poll"); import("std::net::socket_base"); diff --git a/test/test105.wave b/test/test105.wave index a3b4e31a..01551bbd 100644 --- a/test/test105.wave +++ b/test/test105.wave @@ -1,3 +1,5 @@ +// wave-test: host-arch=x86_64 + import("std::bytes::endian"); import("std::math::bits"); import("std::mem::alloc"); diff --git a/test/test106.wave b/test/test106.wave index dd1d2c42..55ea6126 100644 --- a/test/test106.wave +++ b/test/test106.wave @@ -1,3 +1,5 @@ +// wave-test: host-arch=x86_64 + import("std::io::consts"); import("std::io::fd"); import("std::process::core"); diff --git a/test/test84.wave b/test/test84.wave index 40dd82a8..c98885a6 100644 --- a/test/test84.wave +++ b/test/test84.wave @@ -1,3 +1,5 @@ +// wave-test: host-arch=x86_64 + import("std::time::clock"); import("std::time::diff"); import("std::time::sleep");