From e5c7770fbe7fe2eccfefa6833ae899fb8db27e94 Mon Sep 17 00:00:00 2001 From: Debin Date: Wed, 1 Apr 2026 13:44:40 +0800 Subject: [PATCH 1/3] feat: add axtest framework and coverage support with new runner and runtime --- .gitignore | 2 + Cargo.lock | 62 ++++- Cargo.toml | 5 +- Makefile | 11 +- examples/axtest-runner/Cargo.toml | 25 ++ .../axtest-runner/src/coverage_runtime.rs | 246 ++++++++++++++++++ examples/axtest-runner/src/main.rs | 129 +++++++++ modules/axhal/linker.lds.S | 64 +++++ scripts/coverage/coverage-report.sh | 124 +++++++++ scripts/make/cargo.mk | 10 + scripts/make/qemu.mk | 2 +- 11 files changed, 666 insertions(+), 14 deletions(-) create mode 100644 examples/axtest-runner/Cargo.toml create mode 100644 examples/axtest-runner/src/coverage_runtime.rs create mode 100644 examples/axtest-runner/src/main.rs create mode 100755 scripts/coverage/coverage-report.sh diff --git a/.gitignore b/.gitignore index 2a2c5a3dd2..88dc44ce6a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ qemu.log rusty-tags.vi /.project* /.axconfig.* +/crates +/coverage_report \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 1cf9ef8147..2858a11b25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,7 +149,7 @@ dependencies = [ "axfs_ramfs", "axfs_vfs", "axstd", - "crate_interface", + "crate_interface 0.1.4", ] [[package]] @@ -352,7 +352,7 @@ dependencies = [ "axdriver_virtio", "axhal", "cfg-if", - "crate_interface", + "crate_interface 0.1.4", "log", ] @@ -454,7 +454,7 @@ dependencies = [ "axtask", "cap_access", "cfg-if", - "crate_interface", + "crate_interface 0.1.4", "fatfs", "lazyinit", "log", @@ -557,7 +557,7 @@ dependencies = [ "axlog", "cfg-if", "chrono", - "crate_interface", + "crate_interface 0.1.4", "kspin", "log", ] @@ -600,7 +600,7 @@ name = "axns" version = "0.2.0" dependencies = [ "axns", - "crate_interface", + "crate_interface 0.1.4", "lazyinit", ] @@ -613,7 +613,7 @@ dependencies = [ "axplat-macros", "bitflags 2.9.2", "const-str", - "crate_interface", + "crate_interface 0.1.4", "handler_table", "kspin", "memory_addr", @@ -784,7 +784,7 @@ dependencies = [ "axplat", "axtask", "chrono", - "crate_interface", + "crate_interface 0.1.4", "ctor_bare", "percpu", ] @@ -831,7 +831,7 @@ dependencies = [ "axtask", "cfg-if", "cpumask", - "crate_interface", + "crate_interface 0.1.4", "kernel_guard", "kspin", "lazyinit", @@ -842,6 +842,37 @@ dependencies = [ "timer_list", ] +[[package]] +name = "axtest" +version = "0.2.0" +dependencies = [ + "axlog", + "axtest_macros", +] + +[[package]] +name = "axtest-runner" +version = "0.1.0" +dependencies = [ + "arceos_api", + "axfs_ramfs", + "axfs_vfs", + "axlog", + "axstd", + "axtest", + "crate_interface 0.3.0", + "log", +] + +[[package]] +name = "axtest_macros" +version = "0.2.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "base64" version = "0.13.1" @@ -1084,6 +1115,17 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "crate_interface" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e5c7109dea31fc91ab584e99752baa997f76d46e49bab0a17b5e9679248df7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "critical-section" version = "1.2.0" @@ -1214,7 +1256,7 @@ version = "0.2.0" source = "git+https://github.com/elliott10/fxmac_rs.git?rev=0dbc3916#0dbc3916d5d5a0086916deda0c4c2dd8651c69ce" dependencies = [ "aarch64-cpu 10.0.0", - "crate_interface", + "crate_interface 0.1.4", "log", ] @@ -1389,7 +1431,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "307e6be468f3d6b6d895e191f63c11602e4e76575ecca68325d8c8dbebe2870e" dependencies = [ "cfg-if", - "crate_interface", + "crate_interface 0.1.4", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 650f61816c..62cc879291 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ members = [ "ulib/axstd", "ulib/axlibc", + "examples/axtest-runner", "examples/helloworld", "examples/helloworld-myplat", "examples/httpclient", @@ -66,6 +67,8 @@ axsync = { path = "modules/axsync" } axtask = { path = "modules/axtask" } axdma = { path = "modules/axdma" } axipi = { path = "modules/axipi" } +axtest = { path = "crates/axtest/axtest" } +axtest_macros = { path = "crates/axtest/axtest_macros" } [profile.release] -lto = true +lto = true \ No newline at end of file diff --git a/Makefile b/Makefile index 81189c725a..09755c6ba1 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,8 @@ # - `A` or `APP`: Path to the application # - `FEATURES`: Features os ArceOS modules to be enabled. # - `APP_FEATURES`: Features of (rust) apps to be enabled. +# - `AX_TEST`: Enable axtest cfg injection for Rust apps (`RUSTFLAGS += --cfg axtest`) +# - `AX_TEST_COV`: Enable LLVM coverage instrumentation for axtest Rust apps. # * QEMU options: # - `BLK`: Enable storage devices (virtio-blk) # - `NET`: Enable network devices (virtio-net) @@ -54,6 +56,8 @@ A ?= examples/helloworld APP ?= $(A) FEATURES ?= APP_FEATURES ?= +AX_TEST ?= n +AX_TEST_COV ?= n # QEMU options BLK ?= n @@ -88,7 +92,7 @@ endif .DEFAULT_GOAL := all -ifneq ($(filter $(or $(MAKECMDGOALS), $(.DEFAULT_GOAL)), all build disasm run justrun debug defconfig oldconfig),) +ifneq ($(filter $(or $(MAKECMDGOALS), $(.DEFAULT_GOAL)), all build disasm run justrun debug coverage print_out_elf defconfig oldconfig),) # Install dependencies include scripts/make/deps.mk # Platform resolving @@ -180,6 +184,9 @@ run: build justrun justrun: $(call run_qemu) +coverage: + scripts/coverage/coverage-report.sh "$(DISK_IMG)" "$(OUT_ELF)" "$(PWD)/coverage_report" + debug: build $(call run_qemu_debug) & sleep 1 @@ -230,6 +237,6 @@ clean_c:: rm -rf $(app-objs) .PHONY: all defconfig oldconfig \ - build disasm run justrun debug \ + build disasm run justrun debug coverage print_out_elf \ clippy doc doc_check_missing fmt fmt_c unittest unittest_no_fail_fast \ disk_img clean clean_c diff --git a/examples/axtest-runner/Cargo.toml b/examples/axtest-runner/Cargo.toml new file mode 100644 index 0000000000..bbc912a860 --- /dev/null +++ b/examples/axtest-runner/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "axtest-runner" +version = "0.1.0" +edition.workspace = true +authors = ["Debin "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +use-ramfs = ["axstd/myfs", "dep:axfs_vfs", "dep:axfs_ramfs", "dep:crate_interface"] +axtest_cov = [] +default = ["axtest_cov"] + +[dependencies] +log = "0.4" +axfs_vfs = { version = "0.1", optional = true } +axfs_ramfs = { version = "0.1", optional = true } +crate_interface = { version = "0.3", optional = true } +axstd = { workspace = true, features = ["alloc", "fs", "multitask"] } +axlog = { workspace = true } +axtest = { workspace = true } +arceos_api = { workspace = true } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(axtest)'] } \ No newline at end of file diff --git a/examples/axtest-runner/src/coverage_runtime.rs b/examples/axtest-runner/src/coverage_runtime.rs new file mode 100644 index 0000000000..7865f1e1c7 --- /dev/null +++ b/examples/axtest-runner/src/coverage_runtime.rs @@ -0,0 +1,246 @@ +// LLVM coverage runtime implementation for bare metal environments +// This provides the minimal runtime needed when compiled with -Zno-profiler-runtime + +use core::{mem::size_of, ptr::addr_of}; + +const INSTR_PROF_RAW_MAGIC_64: u64 = ((255u64) << 56) + | ((b'l' as u64) << 48) + | ((b'p' as u64) << 40) + | ((b'r' as u64) << 32) + | ((b'o' as u64) << 24) + | ((b'f' as u64) << 16) + | ((b'r' as u64) << 8) + | 129u64; +const INSTR_PROF_RAW_VERSION: u64 = 10; +const VALUE_KIND_LAST: u64 = 2; +const VARIANT_MASK_BYTE_COVERAGE: u64 = 1u64 << 60; + +#[repr(C)] +struct RawHeader { + magic: u64, + version: u64, + binary_ids_size: u64, + num_data: u64, + padding_bytes_before_counters: u64, + num_counters: u64, + padding_bytes_after_counters: u64, + num_bitmap_bytes: u64, + padding_bytes_after_bitmap_bytes: u64, + names_size: u64, + counters_delta: u64, + bitmap_delta: u64, + names_delta: u64, + num_vtables: u64, + vnames_size: u64, + value_kind_last: u64, +} + +// Link with the profiling sections defined in linker script +unsafe extern "C" { + // Data section + static __start___llvm_prf_data: u8; + static __stop___llvm_prf_data: u8; + + // Counters section + static __start___llvm_prf_cnts: u8; + static __stop___llvm_prf_cnts: u8; + + // Names section + static __start___llvm_prf_names: u8; + static __stop___llvm_prf_names: u8; + + // Value profiling data + static __start___llvm_prf_vnds: u8; + static __stop___llvm_prf_vnds: u8; +} + +#[inline(always)] +fn align_up(value: usize, align: usize) -> usize { + (value + align - 1) & !(align - 1) +} + +#[inline(always)] +fn section_size(start: usize, end: usize) -> usize { + end.saturating_sub(start) +} + +#[inline(always)] +fn counter_entry_size(version: u64) -> usize { + if (version & VARIANT_MASK_BYTE_COVERAGE) != 0 { + 1 + } else { + size_of::() + } +} + +/// Calculate the size of coverage data to be written +#[unsafe(no_mangle)] +pub extern "C" fn __llvm_profile_get_size_for_buffer() -> u64 { + let data_start = addr_of!(__start___llvm_prf_data) as usize; + let data_end = addr_of!(__stop___llvm_prf_data) as usize; + let cnts_start = addr_of!(__start___llvm_prf_cnts) as usize; + let cnts_end = addr_of!(__stop___llvm_prf_cnts) as usize; + let names_start = addr_of!(__start___llvm_prf_names) as usize; + let names_end = addr_of!(__stop___llvm_prf_names) as usize; + + let data_size = section_size(data_start, data_end); + let cnts_size = section_size(cnts_start, cnts_end); + let names_size = section_size(names_start, names_end); + + let pad_before_counters = align_up(data_size, 8) - data_size; + let pad_after_counters = 0usize; + let pad_after_bitmap = 0usize; + let pad_after_names = align_up(names_size, 8) - names_size; + + let total = size_of::() + + data_size + + pad_before_counters + + cnts_size + + pad_after_counters + + pad_after_bitmap + + names_size; + let total = total + pad_after_names; + + total as u64 +} + +/// Write coverage data to a buffer +#[unsafe(no_mangle)] +pub extern "C" fn __llvm_profile_write_buffer(buffer: *mut u8) -> i32 { + if buffer.is_null() { + return 1; // Error + } + + let data_start = addr_of!(__start___llvm_prf_data) as usize; + let data_end = addr_of!(__stop___llvm_prf_data) as usize; + let cnts_start = addr_of!(__start___llvm_prf_cnts) as usize; + let cnts_end = addr_of!(__stop___llvm_prf_cnts) as usize; + let names_start = addr_of!(__start___llvm_prf_names) as usize; + let names_end = addr_of!(__stop___llvm_prf_names) as usize; + let vnds_start = addr_of!(__start___llvm_prf_vnds) as usize; + let vnds_end = addr_of!(__stop___llvm_prf_vnds) as usize; + + let data_size = section_size(data_start, data_end); + let cnts_size = section_size(cnts_start, cnts_end); + let names_size = section_size(names_start, names_end); + let vnds_size = section_size(vnds_start, vnds_end); + let version = INSTR_PROF_RAW_VERSION; + let ctr_size = counter_entry_size(version); + + let pad_before_counters = align_up(data_size, 8) - data_size; + let pad_after_counters = 0usize; + let pad_after_bitmap = 0usize; + let pad_after_names = align_up(names_size, 8) - names_size; + + let expected_size = __llvm_profile_get_size_for_buffer() as usize; + let out = unsafe { core::slice::from_raw_parts_mut(buffer, expected_size) }; + + let header = RawHeader { + magic: INSTR_PROF_RAW_MAGIC_64, + version, + binary_ids_size: 0, + num_data: (data_size / 64) as u64, + padding_bytes_before_counters: pad_before_counters as u64, + num_counters: (cnts_size.div_ceil(ctr_size)) as u64, + padding_bytes_after_counters: pad_after_counters as u64, + num_bitmap_bytes: 0, + padding_bytes_after_bitmap_bytes: pad_after_bitmap as u64, + names_size: names_size as u64, + counters_delta: cnts_start.wrapping_sub(data_start) as u64, + bitmap_delta: 0, + names_delta: names_start as u64, + num_vtables: 0, + vnames_size: 0, + value_kind_last: VALUE_KIND_LAST, + }; + + let mut offset = 0usize; + + macro_rules! write_u64 { + ($val:expr) => {{ + if offset + 8 > out.len() { + return 1; + } + let bytes = ($val).to_le_bytes(); + out[offset..offset + 8].copy_from_slice(&bytes); + offset += 8; + }}; + } + + write_u64!(header.magic); + write_u64!(header.version); + write_u64!(header.binary_ids_size); + write_u64!(header.num_data); + write_u64!(header.padding_bytes_before_counters); + write_u64!(header.num_counters); + write_u64!(header.padding_bytes_after_counters); + write_u64!(header.num_bitmap_bytes); + write_u64!(header.padding_bytes_after_bitmap_bytes); + write_u64!(header.names_size); + write_u64!(header.counters_delta); + write_u64!(header.bitmap_delta); + write_u64!(header.names_delta); + write_u64!(header.num_vtables); + write_u64!(header.vnames_size); + write_u64!(header.value_kind_last); + + if offset + data_size > out.len() { + return 1; + } + unsafe { + core::ptr::copy_nonoverlapping( + data_start as *const u8, + out.as_mut_ptr().add(offset), + data_size, + ) + }; + offset += data_size; + + if offset + pad_before_counters > out.len() { + return 1; + } + out[offset..offset + pad_before_counters].fill(0); + offset += pad_before_counters; + + if offset + cnts_size > out.len() { + return 1; + } + unsafe { + core::ptr::copy_nonoverlapping( + cnts_start as *const u8, + out.as_mut_ptr().add(offset), + cnts_size, + ) + }; + offset += cnts_size; + + if offset + names_size > out.len() { + return 1; + } + unsafe { + core::ptr::copy_nonoverlapping( + names_start as *const u8, + out.as_mut_ptr().add(offset), + names_size, + ) + }; + offset += names_size; + + if offset + pad_after_names > out.len() { + return 1; + } + out[offset..offset + pad_after_names].fill(0); + offset += pad_after_names; + + // Value profiling nodes are emitted in memory sections, but the raw format + // stores serialized value records. Keep it empty when there is no value data. + if vnds_size != 0 { + // vnds has data, but we do not serialize it directly into raw payload. + } + + if offset != expected_size { + return 1; + } + + 0 +} diff --git a/examples/axtest-runner/src/main.rs b/examples/axtest-runner/src/main.rs new file mode 100644 index 0000000000..15cfcd7223 --- /dev/null +++ b/examples/axtest-runner/src/main.rs @@ -0,0 +1,129 @@ +#![no_std] +#![no_main] + +use axlog::ax_println; + +extern crate alloc; +extern crate axstd; + +mod coverage_runtime; + +#[cfg(feature = "axtest_cov")] +fn dump_coverage_profraw() { + use crate::alloc::string::ToString; + ax_println!("[COVERAGE] Starting coverage dump..."); + unsafe extern "C" { + fn __llvm_profile_get_size_for_buffer() -> u64; + fn __llvm_profile_write_buffer(buffer: *mut u8) -> i32; + } + + const COV_PATH: &str = "/axtest_cov.profraw"; + + let size = unsafe { __llvm_profile_get_size_for_buffer() as usize }; + ax_println!("[COVERAGE] Size for buffer: {}", size); + if size == 0 { + ax_println!("coverage buffer is empty"); + return; + } + + let mut buffer = alloc::vec![0u8; size]; + let ret = unsafe { __llvm_profile_write_buffer(buffer.as_mut_ptr()) }; + ax_println!("[COVERAGE] Write buffer returned: {}", ret); + if ret != 0 { + ax_println!("failed to write coverage buffer, code: {}", ret); + return; + } + + match axstd::fs::write(COV_PATH, &buffer) { + Ok(()) => ax_println!("coverage_profraw dumped to {}", COV_PATH), + Err(e) => ax_println!("failed to dump coverage_profraw to {}: {:?}", COV_PATH, e), + } + + // ls / + match axstd::fs::read_dir("/") { + Ok(entries) => { + ax_println!("Files in root directory:"); + for entry in entries { + if let Ok(entry) = entry { + ax_println!(" - {}", entry.file_name().to_string()); + } + } + } + Err(e) => ax_println!("failed to read root directory: {:?}", e), + } +} + +#[derive(Default)] +struct ThreadedExecutor; + +impl axtest::AxTestExecutor for ThreadedExecutor { + fn name(&self) -> &'static str { + "thread" + } + + fn run( + &self, + test_fn: fn() -> axtest::AxTestResult, + ) -> Result { + let handle = axstd::thread::spawn(test_fn); + handle.join().map_err(|_| "test thread join failed") + } +} + +#[unsafe(no_mangle)] +fn main() { + ax_println!("Starting axtest runner..."); + let _summary = axtest::init() + .add_executor(axtest::InlineExecutor) + .add_executor(ThreadedExecutor) + .set_default(ThreadedExecutor) + .run_tests(); + + #[cfg(feature = "axtest_cov")] + dump_coverage_profraw(); +} + +// Define some example tests using the axtest framework +#[axtest::def_mod] +mod axtests { + use axlog::ax_println; + use axtest::{ax_assert_eq, ax_assert_ne, def_test}; + + fn axtest_init(sym: axtest::AxTestDescriptor) { + ax_println!("# hook init {}::{}", sym.module, sym.name); + } + + fn axtest_exit(sym: axtest::AxTestDescriptor) { + ax_println!("# hook exit {}::{}", sym.module, sym.name); + } + + /// Simple addition test + #[def_test(custom = "thread")] + fn test_basic_addition() { + let a = 2 + 2; + ax_assert_eq!(a, 4); + } + + /// String comparison test + #[def_test(custom = "inner")] + fn test_string_not_equal() { + let s1 = "hello"; + let s2 = "world"; + ax_assert_ne!(s1, s2); + } + + /// Ignored test example + #[ignore = "demonstrate ignored case"] + #[def_test] + fn test_ignored_demo() { + // This assertion should never run because the case is ignored. + ax_assert_eq!(1, 2); + } + + /// Failing test example (should panic) + #[def_test] + fn test_should_panic_demo() { + // In axtest, an expected panic is modeled as an expected failed test body. + ax_assert_eq!(1, 2); + } +} diff --git a/modules/axhal/linker.lds.S b/modules/axhal/linker.lds.S index 2b11d95fa0..44539819e8 100644 --- a/modules/axhal/linker.lds.S +++ b/modules/axhal/linker.lds.S @@ -97,5 +97,69 @@ SECTIONS { linkme_SYSCALL : { *(linkme_SYSCALL) } linkm2_SYSCALL : { *(linkm2_SYSCALL) } axns_resource : { *(axns_resource) } + + .axtest : ALIGN(8) { + __axtest_suites_start = .; + KEEP(*(.axtest)) + __axtest_suites_end = .; + } + + /* LLVM profiling sections */ + __llvm_prf_data : ALIGN(8) { + PROVIDE(__start___llvm_prf_data = .); + __llvm_prf_data_start = .; + KEEP(*(__llvm_prf_data)) + PROVIDE(__stop___llvm_prf_data = .); + __llvm_prf_data_end = .; + } + + __llvm_prf_vnds : ALIGN(8) { + PROVIDE(__start___llvm_prf_vnds = .); + __llvm_prf_vnds_start = .; + KEEP(*(__llvm_prf_vnds)) + PROVIDE(__stop___llvm_prf_vnds = .); + __llvm_prf_vnds_end = .; + } + + __llvm_prf_vns : ALIGN(8) { + PROVIDE(__start___llvm_prf_vns = .); + KEEP(*(__llvm_prf_vns)) + PROVIDE(__stop___llvm_prf_vns = .); + } + + __llvm_prf_vtab : ALIGN(8) { + PROVIDE(__start___llvm_prf_vtab = .); + KEEP(*(__llvm_prf_vtab)) + PROVIDE(__stop___llvm_prf_vtab = .); + } + + __llvm_prf_names : ALIGN(8) { + PROVIDE(__start___llvm_prf_names = .); + __llvm_prf_names_start = .; + KEEP(*(__llvm_prf_names)) + PROVIDE(__stop___llvm_prf_names = .); + __llvm_prf_names_end = .; + } + + __llvm_prf_bits : ALIGN(8) { + PROVIDE(__start___llvm_prf_bits = .); + KEEP(*(__llvm_prf_bits)) + PROVIDE(__stop___llvm_prf_bits = .); + } + + __llvm_prf_cnts : ALIGN(8) { + PROVIDE(__start___llvm_prf_cnts = .); + __llvm_prf_cnts_start = .; + KEEP(*(__llvm_prf_cnts)) + PROVIDE(__stop___llvm_prf_cnts = .); + __llvm_prf_cnts_end = .; + } + + __llvm_orderfile : ALIGN(8) { + PROVIDE(__start___llvm_orderfile = .); + KEEP(*(__llvm_orderfile)) + PROVIDE(__stop___llvm_orderfile = .); + } + } INSERT AFTER .tbss; diff --git a/scripts/coverage/coverage-report.sh b/scripts/coverage/coverage-report.sh new file mode 100755 index 0000000000..9cc8359eb6 --- /dev/null +++ b/scripts/coverage/coverage-report.sh @@ -0,0 +1,124 @@ +#!/bin/bash +# Extract axtest coverage raw data from disk image and generate HTML report. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" + +DISK_IMG="${1:-${PROJECT_ROOT}/disk.img}" +APP_ELF="${2:-${PROJECT_ROOT}/examples/axtest-runner/axtest-runner_aarch64-qemu-virt.elf}" +OUTPUT_DIR="${3:-${PROJECT_ROOT}/coverage_report}" + +RAW_NAME="axtest_cov.profraw" +RAW_PATH="${OUTPUT_DIR}/${RAW_NAME}" +PROFDATA_PATH="${OUTPUT_DIR}/coverage.profdata" +SUMMARY_PATH="${OUTPUT_DIR}/coverage_summary.txt" +COV_LOG="${OUTPUT_DIR}/llvm-cov.log" +EXPORT_JSON="${OUTPUT_DIR}/coverage.json" + +RED='\033[0;31m' +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +NC='\033[0m' + +find_llvm_tool() { + local tool="$1" + + if command -v "$tool" >/dev/null 2>&1; then + command -v "$tool" + return 0 + fi + + local sysroot + local host + sysroot="$(rustc --print sysroot)" + host="$(rustc -vV | sed -n 's/host: //p')" + + local candidates=( + "$sysroot/lib/rustlib/$host/bin/$tool" + "$sysroot/lib/rustlib/aarch64-apple-darwin/bin/$tool" + "$sysroot/lib/rustlib/x86_64-apple-darwin/bin/$tool" + "$sysroot/lib/rustlib/aarch64-unknown-linux-gnu/bin/$tool" + "$sysroot/lib/rustlib/x86_64-unknown-linux-gnu/bin/$tool" + ) + + local candidate + for candidate in "${candidates[@]}"; do + if [ -f "$candidate" ]; then + echo "$candidate" + return 0 + fi + done + + return 1 +} + +echo -e "${BLUE}=== Extract And Generate Coverage ===${NC}" +echo "disk image: $DISK_IMG" +echo "app elf: $APP_ELF" +echo "output dir: $OUTPUT_DIR" + +if [ ! -f "$DISK_IMG" ]; then + echo -e "${RED}Error: disk image not found: $DISK_IMG${NC}" + exit 1 +fi + +if [ ! -f "$APP_ELF" ]; then + echo -e "${RED}Error: app ELF not found: $APP_ELF${NC}" + exit 1 +fi + +if ! command -v mcopy >/dev/null 2>&1; then + echo -e "${RED}Error: mcopy not found. Install mtools first.${NC}" + exit 1 +fi + +mkdir -p "$OUTPUT_DIR" +rm -f "$RAW_PATH" "$PROFDATA_PATH" "$SUMMARY_PATH" "$COV_LOG" "$EXPORT_JSON" + +echo -e "\n${BLUE}[1/3] Extracting ${RAW_NAME} from disk image...${NC}" +mcopy -i "$DISK_IMG" "::/${RAW_NAME}" "$RAW_PATH" + +LLVM_PROFDATA="$(find_llvm_tool llvm-profdata || true)" +LLVM_COV="$(find_llvm_tool llvm-cov || true)" + +if [ -z "$LLVM_PROFDATA" ] || [ -z "$LLVM_COV" ]; then + echo -e "${YELLOW}llvm tools not found, trying to install llvm-tools-preview...${NC}" + rustup component add llvm-tools-preview >/dev/null + LLVM_PROFDATA="$(find_llvm_tool llvm-profdata || true)" + LLVM_COV="$(find_llvm_tool llvm-cov || true)" +fi + +if [ -z "$LLVM_PROFDATA" ] || [ -z "$LLVM_COV" ]; then + echo -e "${RED}Error: cannot find llvm-profdata/llvm-cov${NC}" + exit 1 +fi + +echo -e "\n${BLUE}[2/3] Merging profraw -> profdata...${NC}" +"$LLVM_PROFDATA" merge -sparse "$RAW_PATH" -o "$PROFDATA_PATH" + +echo -e "\n${BLUE}[3/3] Generating HTML report...${NC}" +"$LLVM_COV" show \ + --instr-profile="$PROFDATA_PATH" \ + --format=html \ + --output-dir="$OUTPUT_DIR" \ + "$APP_ELF" \ + > "$COV_LOG" 2>&1 + +"$LLVM_COV" report \ + --instr-profile="$PROFDATA_PATH" \ + "$APP_ELF" \ + | tee "$SUMMARY_PATH" + +"$LLVM_COV" export \ + --instr-profile="$PROFDATA_PATH" \ + --format=json \ + "$APP_ELF" \ + > "$EXPORT_JSON" 2>/dev/null || true + +echo -e "\n${GREEN}Done.${NC}" +echo "raw: $RAW_PATH" +echo "profdata: $PROFDATA_PATH" +echo "html: $OUTPUT_DIR/index.html" diff --git a/scripts/make/cargo.mk b/scripts/make/cargo.mk index 343b59ebdb..6766927d2f 100644 --- a/scripts/make/cargo.mk +++ b/scripts/make/cargo.mk @@ -18,6 +18,16 @@ build_args := \ $(verbose) RUSTFLAGS := -A unsafe_op_in_unsafe_fn +ifeq ($(AX_TEST),y) + RUSTFLAGS += --cfg axtest + ifeq ($(AX_TEST_COV),y) + # LLVM source-based coverage instrumentation for axtest runs. + APP_FEATURES += axtest_cov + RUSTFLAGS += -C instrument-coverage -Zno-profiler-runtime + endif +endif +# Recompute APP_FEAT after modifying APP_FEATURES +APP_FEAT := $(strip $(shell echo $(APP_FEATURES) | tr ',' ' ')) RUSTFLAGS_LINK_ARGS := -C link-arg=-T$(LD_SCRIPT) -C link-arg=-no-pie -C link-arg=-znostart-stop-gc RUSTDOCFLAGS := -Z unstable-options --enable-index-page -D rustdoc::broken_intra_doc_links diff --git a/scripts/make/qemu.mk b/scripts/make/qemu.mk index f35663a6c6..a0c7882a19 100644 --- a/scripts/make/qemu.mk +++ b/scripts/make/qemu.mk @@ -104,7 +104,7 @@ endif # Do not use KVM for debugging ifeq ($(shell uname), Darwin) - qemu_args-$(ACCEL) += -cpu host -accel hvf +# qemu_args-$(ACCEL) += -cpu host -accel hvf else ifneq ($(wildcard /dev/kvm),) qemu_args-$(ACCEL) += -cpu host -accel kvm endif From ddd6a8240b1ed49e6463f820aa514ced5fba02d8 Mon Sep 17 00:00:00 2001 From: Debin Date: Wed, 1 Apr 2026 14:16:46 +0800 Subject: [PATCH 2/3] feat: enhance coverage runtime and build configurations for axtest --- examples/axtest-runner/Cargo.toml | 5 +- .../axtest-runner/src/coverage_runtime.rs | 302 ++++++++++-------- examples/axtest-runner/src/main.rs | 12 +- scripts/make/build.mk | 2 + scripts/make/cargo.mk | 4 + 5 files changed, 178 insertions(+), 147 deletions(-) diff --git a/examples/axtest-runner/Cargo.toml b/examples/axtest-runner/Cargo.toml index bbc912a860..7d589ed5c1 100644 --- a/examples/axtest-runner/Cargo.toml +++ b/examples/axtest-runner/Cargo.toml @@ -9,17 +9,18 @@ authors = ["Debin "] [features] use-ramfs = ["axstd/myfs", "dep:axfs_vfs", "dep:axfs_ramfs", "dep:crate_interface"] axtest_cov = [] -default = ["axtest_cov"] +default = [] [dependencies] log = "0.4" axfs_vfs = { version = "0.1", optional = true } axfs_ramfs = { version = "0.1", optional = true } crate_interface = { version = "0.3", optional = true } -axstd = { workspace = true, features = ["alloc", "fs", "multitask"] } +axstd = { workspace = true, features = ["alloc", "fs", "multitask", "fp-simd"] } axlog = { workspace = true } axtest = { workspace = true } arceos_api = { workspace = true } +# minicov = { version = "0.3", optional = true } # TODO: Compile error on riscv64 [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(axtest)'] } \ No newline at end of file diff --git a/examples/axtest-runner/src/coverage_runtime.rs b/examples/axtest-runner/src/coverage_runtime.rs index 7865f1e1c7..b837b56fca 100644 --- a/examples/axtest-runner/src/coverage_runtime.rs +++ b/examples/axtest-runner/src/coverage_runtime.rs @@ -15,6 +15,105 @@ const INSTR_PROF_RAW_VERSION: u64 = 10; const VALUE_KIND_LAST: u64 = 2; const VARIANT_MASK_BYTE_COVERAGE: u64 = 1u64 << 60; +#[derive(Copy, Clone)] +struct Section { + start: usize, + end: usize, +} + +impl Section { + #[inline(always)] + fn len(self) -> usize { + self.end.saturating_sub(self.start) + } +} + +struct Layout { + data: Section, + counters: Section, + names: Section, + vnds: Section, + pad_before_counters: usize, + pad_after_counters: usize, + pad_after_bitmap: usize, + pad_after_names: usize, + version: u64, + counter_entry_size: usize, +} + +impl Layout { + fn collect() -> Self { + let data = Section { + start: addr_of!(__start___llvm_prf_data) as usize, + end: addr_of!(__stop___llvm_prf_data) as usize, + }; + let counters = Section { + start: addr_of!(__start___llvm_prf_cnts) as usize, + end: addr_of!(__stop___llvm_prf_cnts) as usize, + }; + let names = Section { + start: addr_of!(__start___llvm_prf_names) as usize, + end: addr_of!(__stop___llvm_prf_names) as usize, + }; + let vnds = Section { + start: addr_of!(__start___llvm_prf_vnds) as usize, + end: addr_of!(__stop___llvm_prf_vnds) as usize, + }; + + let version = INSTR_PROF_RAW_VERSION; + let counter_entry_size = counter_entry_size(version); + let pad_before_counters = align_up(data.len(), 8) - data.len(); + let pad_after_counters = 0usize; + let pad_after_bitmap = 0usize; + let pad_after_names = align_up(names.len(), 8) - names.len(); + + Self { + data, + counters, + names, + vnds, + pad_before_counters, + pad_after_counters, + pad_after_bitmap, + pad_after_names, + version, + counter_entry_size, + } + } + + fn total_size(&self) -> usize { + size_of::() + + self.data.len() + + self.pad_before_counters + + self.counters.len() + + self.pad_after_counters + + self.pad_after_bitmap + + self.names.len() + + self.pad_after_names + } + + fn header(&self) -> RawHeader { + RawHeader { + magic: INSTR_PROF_RAW_MAGIC_64, + version: self.version, + binary_ids_size: 0, + num_data: (self.data.len() / 64) as u64, + padding_bytes_before_counters: self.pad_before_counters as u64, + num_counters: self.counters.len().div_ceil(self.counter_entry_size) as u64, + padding_bytes_after_counters: self.pad_after_counters as u64, + num_bitmap_bytes: 0, + padding_bytes_after_bitmap_bytes: self.pad_after_bitmap as u64, + names_size: self.names.len() as u64, + counters_delta: self.counters.start.wrapping_sub(self.data.start) as u64, + bitmap_delta: 0, + names_delta: self.names.start as u64, + num_vtables: 0, + vnames_size: 0, + value_kind_last: VALUE_KIND_LAST, + } + } +} + #[repr(C)] struct RawHeader { magic: u64, @@ -60,8 +159,36 @@ fn align_up(value: usize, align: usize) -> usize { } #[inline(always)] -fn section_size(start: usize, end: usize) -> usize { - end.saturating_sub(start) +fn write_u64_le(out: &mut [u8], offset: &mut usize, value: u64) -> Result<(), i32> { + if *offset + 8 > out.len() { + return Err(1); + } + out[*offset..*offset + 8].copy_from_slice(&value.to_le_bytes()); + *offset += 8; + Ok(()) +} + +#[inline(always)] +fn write_zeros(out: &mut [u8], offset: &mut usize, len: usize) -> Result<(), i32> { + if *offset + len > out.len() { + return Err(1); + } + out[*offset..*offset + len].fill(0); + *offset += len; + Ok(()) +} + +#[inline(always)] +fn copy_section(out: &mut [u8], offset: &mut usize, section: Section) -> Result<(), i32> { + let len = section.len(); + if *offset + len > out.len() { + return Err(1); + } + unsafe { + core::ptr::copy_nonoverlapping(section.start as *const u8, out.as_mut_ptr().add(*offset), len) + }; + *offset += len; + Ok(()) } #[inline(always)] @@ -74,167 +201,66 @@ fn counter_entry_size(version: u64) -> usize { } /// Calculate the size of coverage data to be written -#[unsafe(no_mangle)] -pub extern "C" fn __llvm_profile_get_size_for_buffer() -> u64 { - let data_start = addr_of!(__start___llvm_prf_data) as usize; - let data_end = addr_of!(__stop___llvm_prf_data) as usize; - let cnts_start = addr_of!(__start___llvm_prf_cnts) as usize; - let cnts_end = addr_of!(__stop___llvm_prf_cnts) as usize; - let names_start = addr_of!(__start___llvm_prf_names) as usize; - let names_end = addr_of!(__stop___llvm_prf_names) as usize; - - let data_size = section_size(data_start, data_end); - let cnts_size = section_size(cnts_start, cnts_end); - let names_size = section_size(names_start, names_end); - - let pad_before_counters = align_up(data_size, 8) - data_size; - let pad_after_counters = 0usize; - let pad_after_bitmap = 0usize; - let pad_after_names = align_up(names_size, 8) - names_size; - - let total = size_of::() - + data_size - + pad_before_counters - + cnts_size - + pad_after_counters - + pad_after_bitmap - + names_size; - let total = total + pad_after_names; - - total as u64 +pub fn llvm_profile_get_size_for_buffer() -> usize { + Layout::collect().total_size() } /// Write coverage data to a buffer -#[unsafe(no_mangle)] -pub extern "C" fn __llvm_profile_write_buffer(buffer: *mut u8) -> i32 { +pub fn llvm_profile_write_buffer(buffer: *mut u8) -> i32 { if buffer.is_null() { return 1; // Error } - let data_start = addr_of!(__start___llvm_prf_data) as usize; - let data_end = addr_of!(__stop___llvm_prf_data) as usize; - let cnts_start = addr_of!(__start___llvm_prf_cnts) as usize; - let cnts_end = addr_of!(__stop___llvm_prf_cnts) as usize; - let names_start = addr_of!(__start___llvm_prf_names) as usize; - let names_end = addr_of!(__stop___llvm_prf_names) as usize; - let vnds_start = addr_of!(__start___llvm_prf_vnds) as usize; - let vnds_end = addr_of!(__stop___llvm_prf_vnds) as usize; - - let data_size = section_size(data_start, data_end); - let cnts_size = section_size(cnts_start, cnts_end); - let names_size = section_size(names_start, names_end); - let vnds_size = section_size(vnds_start, vnds_end); - let version = INSTR_PROF_RAW_VERSION; - let ctr_size = counter_entry_size(version); - - let pad_before_counters = align_up(data_size, 8) - data_size; - let pad_after_counters = 0usize; - let pad_after_bitmap = 0usize; - let pad_after_names = align_up(names_size, 8) - names_size; - - let expected_size = __llvm_profile_get_size_for_buffer() as usize; + let layout = Layout::collect(); + let expected_size = layout.total_size(); let out = unsafe { core::slice::from_raw_parts_mut(buffer, expected_size) }; - let header = RawHeader { - magic: INSTR_PROF_RAW_MAGIC_64, - version, - binary_ids_size: 0, - num_data: (data_size / 64) as u64, - padding_bytes_before_counters: pad_before_counters as u64, - num_counters: (cnts_size.div_ceil(ctr_size)) as u64, - padding_bytes_after_counters: pad_after_counters as u64, - num_bitmap_bytes: 0, - padding_bytes_after_bitmap_bytes: pad_after_bitmap as u64, - names_size: names_size as u64, - counters_delta: cnts_start.wrapping_sub(data_start) as u64, - bitmap_delta: 0, - names_delta: names_start as u64, - num_vtables: 0, - vnames_size: 0, - value_kind_last: VALUE_KIND_LAST, - }; + let header = layout.header(); let mut offset = 0usize; - macro_rules! write_u64 { - ($val:expr) => {{ - if offset + 8 > out.len() { - return 1; - } - let bytes = ($val).to_le_bytes(); - out[offset..offset + 8].copy_from_slice(&bytes); - offset += 8; - }}; - } - - write_u64!(header.magic); - write_u64!(header.version); - write_u64!(header.binary_ids_size); - write_u64!(header.num_data); - write_u64!(header.padding_bytes_before_counters); - write_u64!(header.num_counters); - write_u64!(header.padding_bytes_after_counters); - write_u64!(header.num_bitmap_bytes); - write_u64!(header.padding_bytes_after_bitmap_bytes); - write_u64!(header.names_size); - write_u64!(header.counters_delta); - write_u64!(header.bitmap_delta); - write_u64!(header.names_delta); - write_u64!(header.num_vtables); - write_u64!(header.vnames_size); - write_u64!(header.value_kind_last); - - if offset + data_size > out.len() { - return 1; + for value in [ + header.magic, + header.version, + header.binary_ids_size, + header.num_data, + header.padding_bytes_before_counters, + header.num_counters, + header.padding_bytes_after_counters, + header.num_bitmap_bytes, + header.padding_bytes_after_bitmap_bytes, + header.names_size, + header.counters_delta, + header.bitmap_delta, + header.names_delta, + header.num_vtables, + header.vnames_size, + header.value_kind_last, + ] { + if write_u64_le(out, &mut offset, value).is_err() { + return 1; + } } - unsafe { - core::ptr::copy_nonoverlapping( - data_start as *const u8, - out.as_mut_ptr().add(offset), - data_size, - ) - }; - offset += data_size; - if offset + pad_before_counters > out.len() { + if copy_section(out, &mut offset, layout.data).is_err() { return 1; } - out[offset..offset + pad_before_counters].fill(0); - offset += pad_before_counters; - - if offset + cnts_size > out.len() { + if write_zeros(out, &mut offset, layout.pad_before_counters).is_err() { return 1; } - unsafe { - core::ptr::copy_nonoverlapping( - cnts_start as *const u8, - out.as_mut_ptr().add(offset), - cnts_size, - ) - }; - offset += cnts_size; - - if offset + names_size > out.len() { + if copy_section(out, &mut offset, layout.counters).is_err() { return 1; } - unsafe { - core::ptr::copy_nonoverlapping( - names_start as *const u8, - out.as_mut_ptr().add(offset), - names_size, - ) - }; - offset += names_size; - - if offset + pad_after_names > out.len() { + if copy_section(out, &mut offset, layout.names).is_err() { + return 1; + } + if write_zeros(out, &mut offset, layout.pad_after_names).is_err() { return 1; } - out[offset..offset + pad_after_names].fill(0); - offset += pad_after_names; // Value profiling nodes are emitted in memory sections, but the raw format // stores serialized value records. Keep it empty when there is no value data. - if vnds_size != 0 { + if layout.vnds.len() != 0 { // vnds has data, but we do not serialize it directly into raw payload. } diff --git a/examples/axtest-runner/src/main.rs b/examples/axtest-runner/src/main.rs index 15cfcd7223..981b3a38a6 100644 --- a/examples/axtest-runner/src/main.rs +++ b/examples/axtest-runner/src/main.rs @@ -6,20 +6,18 @@ use axlog::ax_println; extern crate alloc; extern crate axstd; +#[cfg(feature = "axtest_cov")] mod coverage_runtime; + +// TODO: Use `minicov = "0.3"`` to export. #[cfg(feature = "axtest_cov")] fn dump_coverage_profraw() { use crate::alloc::string::ToString; ax_println!("[COVERAGE] Starting coverage dump..."); - unsafe extern "C" { - fn __llvm_profile_get_size_for_buffer() -> u64; - fn __llvm_profile_write_buffer(buffer: *mut u8) -> i32; - } - const COV_PATH: &str = "/axtest_cov.profraw"; - let size = unsafe { __llvm_profile_get_size_for_buffer() as usize }; + let size = crate::coverage_runtime::llvm_profile_get_size_for_buffer(); ax_println!("[COVERAGE] Size for buffer: {}", size); if size == 0 { ax_println!("coverage buffer is empty"); @@ -27,7 +25,7 @@ fn dump_coverage_profraw() { } let mut buffer = alloc::vec![0u8; size]; - let ret = unsafe { __llvm_profile_write_buffer(buffer.as_mut_ptr()) }; + let ret = crate::coverage_runtime::llvm_profile_write_buffer(buffer.as_mut_ptr()); ax_println!("[COVERAGE] Write buffer returned: {}", ret); if ret != 0 { ax_println!("failed to write coverage buffer, code: {}", ret); diff --git a/scripts/make/build.mk b/scripts/make/build.mk index e139e28ace..96a4e06275 100644 --- a/scripts/make/build.mk +++ b/scripts/make/build.mk @@ -36,6 +36,8 @@ else ifneq ($(filter $(or $(MAKECMDGOALS), $(.DEFAULT_GOAL)), all build run just endif $(if $(V), $(info RUSTFLAGS: "$(RUSTFLAGS)")) export RUSTFLAGS + export CFLAGS_x86_64_unknown_none + export CFLAGS_riscv64gc_unknown_none_elf endif _cargo_build: oldconfig diff --git a/scripts/make/cargo.mk b/scripts/make/cargo.mk index 6766927d2f..bf56d20b98 100644 --- a/scripts/make/cargo.mk +++ b/scripts/make/cargo.mk @@ -24,6 +24,10 @@ ifeq ($(AX_TEST),y) # LLVM source-based coverage instrumentation for axtest runs. APP_FEATURES += axtest_cov RUSTFLAGS += -C instrument-coverage -Zno-profiler-runtime + ifeq ($(ARCH),x86_64) + # minicov's C runtime needs a large code model for high-half kernel addresses. + CFLAGS_x86_64_unknown_none += -mcmodel=large + endif endif endif # Recompute APP_FEAT after modifying APP_FEATURES From 75db04eb0c5dd01d774bf20312e8569097389d82 Mon Sep 17 00:00:00 2001 From: Debin Date: Wed, 1 Apr 2026 14:51:25 +0800 Subject: [PATCH 3/3] feat: add axtest module and integrate with task management, including tests for scheduling and state switching --- Cargo.lock | 1 + modules/axhal/build.rs | 120 ++++++++++++++++++++++++++++++ modules/axhal/linker.lds.S | 64 +--------------- modules/axtask/Cargo.toml | 4 + modules/axtask/src/axtests.rs | 133 ++++++++++++++++++++++++++++++++++ modules/axtask/src/lib.rs | 1 + 6 files changed, 261 insertions(+), 62 deletions(-) create mode 100644 modules/axtask/src/axtests.rs diff --git a/Cargo.lock b/Cargo.lock index 2858a11b25..8ee947209b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -829,6 +829,7 @@ dependencies = [ "axhal", "axsched", "axtask", + "axtest", "cfg-if", "cpumask", "crate_interface 0.1.4", diff --git a/modules/axhal/build.rs b/modules/axhal/build.rs index be7002f358..8974175e76 100644 --- a/modules/axhal/build.rs +++ b/modules/axhal/build.rs @@ -1,6 +1,112 @@ use std::io::Result; use std::path::Path; +const AXTEST_SECTIONS: &str = r#" + .axtest_desc : ALIGN(8) { + __axtest_suites_start = .; + KEEP(*(.axtest_desc)) + __axtest_suites_end = .; + } + + .axtest_mod_hooks : ALIGN(8) { + __axtest_mod_hooks_start = .; + KEEP(*(.axtest_mod_hooks)) + __axtest_mod_hooks_end = .; + } +"#; + +const LLVM_PROFILE_SECTIONS: &str = r#" + /* LLVM profiling sections */ + __llvm_prf_data : ALIGN(8) { + PROVIDE(__start___llvm_prf_data = .); + __llvm_prf_data_start = .; + KEEP(*(__llvm_prf_data)) + PROVIDE(__stop___llvm_prf_data = .); + __llvm_prf_data_end = .; + } + + __llvm_prf_vnds : ALIGN(8) { + PROVIDE(__start___llvm_prf_vnds = .); + __llvm_prf_vnds_start = .; + KEEP(*(__llvm_prf_vnds)) + PROVIDE(__stop___llvm_prf_vnds = .); + __llvm_prf_vnds_end = .; + } + + __llvm_prf_vns : ALIGN(8) { + PROVIDE(__start___llvm_prf_vns = .); + KEEP(*(__llvm_prf_vns)) + PROVIDE(__stop___llvm_prf_vns = .); + } + + __llvm_prf_vtab : ALIGN(8) { + PROVIDE(__start___llvm_prf_vtab = .); + KEEP(*(__llvm_prf_vtab)) + PROVIDE(__stop___llvm_prf_vtab = .); + } + + __llvm_prf_names : ALIGN(8) { + PROVIDE(__start___llvm_prf_names = .); + __llvm_prf_names_start = .; + KEEP(*(__llvm_prf_names)) + PROVIDE(__stop___llvm_prf_names = .); + __llvm_prf_names_end = .; + } + + __llvm_prf_bits : ALIGN(8) { + PROVIDE(__start___llvm_prf_bits = .); + KEEP(*(__llvm_prf_bits)) + PROVIDE(__stop___llvm_prf_bits = .); + } + + __llvm_prf_cnts : ALIGN(8) { + PROVIDE(__start___llvm_prf_cnts = .); + __llvm_prf_cnts_start = .; + KEEP(*(__llvm_prf_cnts)) + PROVIDE(__stop___llvm_prf_cnts = .); + __llvm_prf_cnts_end = .; + } + + __llvm_orderfile : ALIGN(8) { + PROVIDE(__start___llvm_orderfile = .); + KEEP(*(__llvm_orderfile)) + PROVIDE(__stop___llvm_orderfile = .); + } +"#; + +fn env_flag(name: &str) -> bool { + std::env::var(name) + .map(|v| matches!(v.as_str(), "1" | "y" | "Y" | "yes" | "YES" | "true" | "TRUE")) + .unwrap_or(false) +} + +fn has_rustflag_token(token: &str) -> bool { + let encoded = std::env::var("CARGO_ENCODED_RUSTFLAGS").unwrap_or_default(); + encoded.split('\u{1f}').any(|f| f == token) +} + +fn has_cfg_axtest() -> bool { + if has_rustflag_token("--cfg=axtest") { + return true; + } + let encoded = std::env::var("CARGO_ENCODED_RUSTFLAGS").unwrap_or_default(); + let mut prev_cfg = false; + for flag in encoded.split('\u{1f}') { + if prev_cfg && flag == "axtest" { + return true; + } + prev_cfg = flag == "--cfg"; + } + false +} + +fn has_instrument_coverage() -> bool { + let encoded = std::env::var("CARGO_ENCODED_RUSTFLAGS").unwrap_or_default(); + encoded + .split('\u{1f}') + .any(|f| f == "instrument-coverage" || f == "-Cinstrument-coverage") +} + fn main() { let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); let platform = axconfig::PLATFORM; @@ -25,6 +131,20 @@ fn gen_linker_script(arch: &str, platform: &str) -> Result<()> { &format!("{:#x}", axconfig::plat::KERNEL_BASE_VADDR), ); let ld_content = ld_content.replace("%CPU_NUM%", &format!("{}", axconfig::plat::MAX_CPU_NUM)); + let ax_test_enabled = env_flag("AX_TEST") || has_cfg_axtest(); + let ax_test_cov_enabled = env_flag("AX_TEST_COV") || has_instrument_coverage(); + let ld_content = ld_content.replace( + "%AXTEST_SECTIONS%", + if ax_test_enabled { AXTEST_SECTIONS } else { "" }, + ); + let ld_content = ld_content.replace( + "%LLVM_PROFILE_SECTIONS%", + if ax_test_cov_enabled { + LLVM_PROFILE_SECTIONS + } else { + "" + }, + ); // target///build/axhal-xxxx/out let out_dir = std::env::var("OUT_DIR").unwrap(); diff --git a/modules/axhal/linker.lds.S b/modules/axhal/linker.lds.S index 44539819e8..8de1c0009d 100644 --- a/modules/axhal/linker.lds.S +++ b/modules/axhal/linker.lds.S @@ -97,69 +97,9 @@ SECTIONS { linkme_SYSCALL : { *(linkme_SYSCALL) } linkm2_SYSCALL : { *(linkm2_SYSCALL) } axns_resource : { *(axns_resource) } - - .axtest : ALIGN(8) { - __axtest_suites_start = .; - KEEP(*(.axtest)) - __axtest_suites_end = .; - } - - /* LLVM profiling sections */ - __llvm_prf_data : ALIGN(8) { - PROVIDE(__start___llvm_prf_data = .); - __llvm_prf_data_start = .; - KEEP(*(__llvm_prf_data)) - PROVIDE(__stop___llvm_prf_data = .); - __llvm_prf_data_end = .; - } - - __llvm_prf_vnds : ALIGN(8) { - PROVIDE(__start___llvm_prf_vnds = .); - __llvm_prf_vnds_start = .; - KEEP(*(__llvm_prf_vnds)) - PROVIDE(__stop___llvm_prf_vnds = .); - __llvm_prf_vnds_end = .; - } + %AXTEST_SECTIONS% - __llvm_prf_vns : ALIGN(8) { - PROVIDE(__start___llvm_prf_vns = .); - KEEP(*(__llvm_prf_vns)) - PROVIDE(__stop___llvm_prf_vns = .); - } - - __llvm_prf_vtab : ALIGN(8) { - PROVIDE(__start___llvm_prf_vtab = .); - KEEP(*(__llvm_prf_vtab)) - PROVIDE(__stop___llvm_prf_vtab = .); - } - - __llvm_prf_names : ALIGN(8) { - PROVIDE(__start___llvm_prf_names = .); - __llvm_prf_names_start = .; - KEEP(*(__llvm_prf_names)) - PROVIDE(__stop___llvm_prf_names = .); - __llvm_prf_names_end = .; - } - - __llvm_prf_bits : ALIGN(8) { - PROVIDE(__start___llvm_prf_bits = .); - KEEP(*(__llvm_prf_bits)) - PROVIDE(__stop___llvm_prf_bits = .); - } - - __llvm_prf_cnts : ALIGN(8) { - PROVIDE(__start___llvm_prf_cnts = .); - __llvm_prf_cnts_start = .; - KEEP(*(__llvm_prf_cnts)) - PROVIDE(__stop___llvm_prf_cnts = .); - __llvm_prf_cnts_end = .; - } - - __llvm_orderfile : ALIGN(8) { - PROVIDE(__start___llvm_orderfile = .); - KEEP(*(__llvm_orderfile)) - PROVIDE(__stop___llvm_orderfile = .); - } + %LLVM_PROFILE_SECTIONS% } INSERT AFTER .tbss; diff --git a/modules/axtask/Cargo.toml b/modules/axtask/Cargo.toml index a68cc3a169..ad122ca2f6 100644 --- a/modules/axtask/Cargo.toml +++ b/modules/axtask/Cargo.toml @@ -48,8 +48,12 @@ kernel_guard = { version = "0.1", optional = true } crate_interface = { version = "0.1", optional = true } cpumask = { version = "0.1", optional = true } axsched = { version = "0.3", optional = true } +axtest = { workspace = true } [dev-dependencies] rand = "0.9" axhal = { workspace = true, features = ["fp-simd"] } axtask = { workspace = true, features = ["test", "multitask"] } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(axtest)'] } \ No newline at end of file diff --git a/modules/axtask/src/axtests.rs b/modules/axtask/src/axtests.rs new file mode 100644 index 0000000000..e35166f7b0 --- /dev/null +++ b/modules/axtask/src/axtests.rs @@ -0,0 +1,133 @@ + +use alloc::{format, vec::Vec}; +use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + +use axtest::{ax_assert, ax_assert_eq, def_test}; +use kspin::SpinNoIrq; + +use crate::{WaitQueue, api as axtask, current}; + +static SERIAL: SpinNoIrq<()> = SpinNoIrq::new(()); + +#[def_test] +fn test_sched_fifo() { + let _lock = SERIAL.lock(); + + const NUM_TASKS: usize = 10; + static FINISHED_TASKS: AtomicUsize = AtomicUsize::new(0); + FINISHED_TASKS.store(0, Ordering::Release); + + for i in 0..NUM_TASKS { + axtask::spawn_raw( + move || { + let _ = i; + axtask::yield_now(); + FINISHED_TASKS.fetch_add(1, Ordering::Release); + }, + format!("T{}", i), + 0x1000, + ); + } + + while FINISHED_TASKS.load(Ordering::Acquire) < NUM_TASKS { + axtask::yield_now(); + } +} + +#[def_test] +fn test_fp_state_switch() { + let _lock = SERIAL.lock(); + + const NUM_TASKS: usize = 5; + const FLOATS: [f64; NUM_TASKS] = [ + 3.141592653589793, + 2.718281828459045, + -1.4142135623730951, + 0.0, + 0.618033988749895, + ]; + static FINISHED_TASKS: AtomicUsize = AtomicUsize::new(0); + static HAS_ERROR: AtomicBool = AtomicBool::new(false); + FINISHED_TASKS.store(0, Ordering::Release); + HAS_ERROR.store(false, Ordering::Release); + + for (i, float) in FLOATS.iter().copied().enumerate() { + axtask::spawn(move || { + let mut value = float + i as f64; + axtask::yield_now(); + value -= i as f64; + + if (value - float).abs() >= 1e-9 { + HAS_ERROR.store(true, Ordering::Release); + } + FINISHED_TASKS.fetch_add(1, Ordering::Release); + }); + } + + while FINISHED_TASKS.load(Ordering::Acquire) < NUM_TASKS { + axtask::yield_now(); + } + ax_assert!(!HAS_ERROR.load(Ordering::Acquire)); +} + +#[def_test] +fn test_wait_queue() { + let _lock = SERIAL.lock(); + + const NUM_TASKS: usize = 10; + + static WQ1: WaitQueue = WaitQueue::new(); + static WQ2: WaitQueue = WaitQueue::new(); + static COUNTER: AtomicUsize = AtomicUsize::new(0); + static HAS_ERROR: AtomicBool = AtomicBool::new(false); + COUNTER.store(0, Ordering::Release); + HAS_ERROR.store(false, Ordering::Release); + + for _ in 0..NUM_TASKS { + axtask::spawn(move || { + COUNTER.fetch_add(1, Ordering::Release); + WQ1.notify_one(true); + WQ2.wait(); + + if current().in_wait_queue() { + HAS_ERROR.store(true, Ordering::Release); + } + + COUNTER.fetch_sub(1, Ordering::Release); + WQ1.notify_one(true); + }); + } + + WQ1.wait_until(|| COUNTER.load(Ordering::Acquire) == NUM_TASKS); + ax_assert_eq!(COUNTER.load(Ordering::Acquire), NUM_TASKS); + ax_assert!(!current().in_wait_queue()); + WQ2.notify_all(true); + + WQ1.wait_until(|| COUNTER.load(Ordering::Acquire) == 0); + ax_assert_eq!(COUNTER.load(Ordering::Acquire), 0); + ax_assert!(!current().in_wait_queue()); + ax_assert!(!HAS_ERROR.load(Ordering::Acquire)); +} + +#[def_test] +fn test_task_join() { + let _lock = SERIAL.lock(); + + const NUM_TASKS: usize = 10; + let mut tasks = Vec::with_capacity(NUM_TASKS); + + for i in 0..NUM_TASKS { + tasks.push(axtask::spawn_raw( + move || { + axtask::yield_now(); + axtask::exit(i as _); + }, + format!("T{}", i), + 0x1000, + )); + } + + for (i, task) in tasks.iter().enumerate() { + ax_assert_eq!(task.join(), Some(i as _)); + } +} diff --git a/modules/axtask/src/lib.rs b/modules/axtask/src/lib.rs index e22f816ae7..b8d7b238ea 100644 --- a/modules/axtask/src/lib.rs +++ b/modules/axtask/src/lib.rs @@ -45,6 +45,7 @@ cfg_if::cfg_if! { mod task_ext; mod api; mod wait_queue; + mod axtests; #[cfg(feature = "irq")] mod timers;