diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml index e613abbe..109a8536 100644 --- a/.github/workflows/changelog.yaml +++ b/.github/workflows/changelog.yaml @@ -27,6 +27,8 @@ jobs: - 'riscv-rt/**' riscv-semihosting: - 'riscv-semihosting/**' + riscv-target-parser: + - 'riscv-target-parser/**' - name: Check for CHANGELOG.md (riscv) if: steps.changes.outputs.riscv == 'true' @@ -43,7 +45,15 @@ jobs: changeLogPath: ./riscv-pac/CHANGELOG.md skipLabels: 'skip changelog' missingUpdateErrorMessage: 'Please add a changelog entry in the riscv-pac/CHANGELOG.md file.' - + + - name: Check for CHANGELOG.md (riscv-peripheral) + if: steps.changes.outputs.riscv-peripheral == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: ./riscv-peripheral/CHANGELOG.md + skipLabels: 'skip changelog' + missingUpdateErrorMessage: 'Please add a changelog entry in the riscv-peripheral/CHANGELOG.md file.' + - name: Check for CHANGELOG.md (riscv-rt) if: steps.changes.outputs.riscv-rt == 'true' uses: dangoslen/changelog-enforcer@v3 @@ -60,10 +70,10 @@ jobs: skipLabels: 'skip changelog' missingUpdateErrorMessage: 'Please add a changelog entry in the riscv-semihosting/CHANGELOG.md file.' - - name: Check for CHANGELOG.md (riscv-peripheral) - if: steps.changes.outputs.riscv-peripheral == 'true' + - name: Check for CHANGELOG.md (riscv-target-parser) + if: steps.changes.outputs.riscv-target-parser == 'true' uses: dangoslen/changelog-enforcer@v3 with: - changeLogPath: ./riscv-peripheral/CHANGELOG.md + changeLogPath: ./riscv-target-parser/CHANGELOG.md skipLabels: 'skip changelog' - missingUpdateErrorMessage: 'Please add a changelog entry in the riscv-peripheral/CHANGELOG.md file.' + missingUpdateErrorMessage: 'Please add a changelog entry in the riscv-target-parser/CHANGELOG.md file.' diff --git a/.github/workflows/riscv-rt.yaml b/.github/workflows/riscv-rt.yaml index a1727162..cf7c52c1 100644 --- a/.github/workflows/riscv-rt.yaml +++ b/.github/workflows/riscv-rt.yaml @@ -14,8 +14,10 @@ jobs: toolchain: [ stable, nightly, 1.61.0 ] target: - riscv32i-unknown-none-elf + - riscv32im-unknown-none-elf - riscv32imc-unknown-none-elf - riscv32imac-unknown-none-elf + - riscv32imafc-unknown-none-elf - riscv64imac-unknown-none-elf - riscv64gc-unknown-none-elf example: @@ -25,6 +27,11 @@ jobs: # Nightly is only for reference and allowed to fail - toolchain: nightly experimental: true + exclude: + - toolchain: 1.61.0 + target: riscv32im-unknown-none-elf + - toolchain: 1.61.0 + target: riscv32imafc-unknown-none-elf runs-on: ubuntu-latest continue-on-error: ${{ matrix.experimental || false }} steps: diff --git a/.github/workflows/riscv-target-parser.yaml b/.github/workflows/riscv-target-parser.yaml new file mode 100644 index 00000000..bd04a190 --- /dev/null +++ b/.github/workflows/riscv-target-parser.yaml @@ -0,0 +1,37 @@ +on: + push: + branches: [ master ] + pull_request: + merge_group: + +name: Run tests (riscv-target-parser) + +jobs: + run-tests: + strategy: + matrix: + os: [ macos-latest, ubuntu-latest, windows-latest ] + toolchain: [ stable, nightly, 1.61.0 ] + include: + # Nightly is only for reference and allowed to fail + - rust: nightly + experimental: true + runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.experimental || false }} + steps: + - uses: actions/checkout@v4 + - name: Update Rust toolchain + run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} + - name: Build + run: cargo build --package riscv-target-parser + - name: Run tests + run: cargo test --package riscv-target-parser + + # Job to check that all the builds succeeded + tests-check: + needs: + - run-tests + runs-on: ubuntu-latest + if: always() + steps: + - run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' diff --git a/Cargo.toml b/Cargo.toml index ba28eae6..a10c4830 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,6 @@ members = [ "riscv-peripheral", "riscv-rt", "riscv-semihosting", + "riscv-target-parser", "tests", ] diff --git a/README.md b/README.md index 5bfbda80..3314bbee 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ This repository contains various crates useful for writing Rust programs on RISC * [`riscv-peripheral`]: Interfaces for standard RISC-V peripherals * [`riscv-rt`]: Startup code and interrupt handling * [`riscv-semihosting`]: Semihosting for RISC-V processors +* [`riscv-target-parser`]: Utility crate for parsing RISC-V targets in build scripts This project is developed and maintained by the [RISC-V team][team]. @@ -27,5 +28,6 @@ to intervene to uphold that code of conduct. [`riscv-peripheral`]: https://crates.io/crates/riscv-peripheral [`riscv-rt`]: https://crates.io/crates/riscv-rt [`riscv-semihosting`]: https://crates.io/crates/riscv-semihosting +[`riscv-target-parser`]: https://crates.io/crates/riscv-target-parser [team]: https://github.com/rust-embedded/wg#the-risc-v-team [CoC]: CODE_OF_CONDUCT.md diff --git a/riscv-rt/CHANGELOG.md b/riscv-rt/CHANGELOG.md index 6ab2cb08..068822f1 100644 --- a/riscv-rt/CHANGELOG.md +++ b/riscv-rt/CHANGELOG.md @@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed +- Limit rustc cfg flags to `riscvi`, `riscvm`, `riscvf`, and `riscvd`. +- Temporary use of `RISCV_RT_LLVM_ARCH_PATCH` environment variable to include the + temporary patch required for avoid LLVM spurious errors. +- `riscv-rt` now use the `RISCV_RT_BASE_ISA` environment variable to configure the behavior + of `riscv-rt-macros` depending on aspects of the base ISA (e.g., RV32I or RV32E). +- Use `riscv-target-parser` in build script to identify target-specific configurations. +- Add documentation to trap frame fields. +- Avoid using `t3`+ in startup assembly to ensure compatibility with RVE. - `link.x.in`: remove references to `eh_frame`. - Rename start/end section symbols to align with `cortex-m-rt`: - `_stext`: it remains, as linker files can modify it. diff --git a/riscv-rt/Cargo.toml b/riscv-rt/Cargo.toml index d5973f33..991d3a17 100644 --- a/riscv-rt/Cargo.toml +++ b/riscv-rt/Cargo.toml @@ -19,10 +19,13 @@ targets = [ "riscv64imac-unknown-none-elf", "riscv64gc-unknown-none-elf", ] +[build-dependencies] +riscv-target-parser = { path = "../riscv-target-parser", version = "0.1.0" } + [dependencies] riscv = { path = "../riscv", version = "0.12.0" } riscv-pac = { path = "../riscv-pac", version = "0.2.0" } -riscv-rt-macros = { path = "macros", version = "0.2.2" } +riscv-rt-macros = { path = "macros", version = "0.3.0" } [dev-dependencies] panic-halt = "1.0.0" diff --git a/riscv-rt/build.rs b/riscv-rt/build.rs index 1d787a98..b8c2a7a8 100644 --- a/riscv-rt/build.rs +++ b/riscv-rt/build.rs @@ -1,6 +1,10 @@ // NOTE: Adapted from cortex-m/build.rs -use std::{collections::HashSet, env, fs, io, path::PathBuf}; +use riscv_target_parser::RiscvTarget; +use std::{env, fs, io, path::PathBuf}; + +// List of all possible RISC-V configurations to check for in risv-rt +const RISCV_CFG: [&str; 4] = ["riscvi", "riscvm", "riscvf", "riscvd"]; fn add_linker_script(arch_width: u32) -> io::Result<()> { // Read the file to a string and replace all occurrences of ${ARCH_WIDTH} with the arch width @@ -17,96 +21,39 @@ fn add_linker_script(arch_width: u32) -> io::Result<()> { Ok(()) } -/// Parse the target RISC-V architecture and returns its bit width and the extension set -fn parse_target(target: &str, cargo_flags: &str) -> (u32, HashSet) { - // isolate bit width and extensions from the rest of the target information - let arch = target - .trim_start_matches("riscv") - .split('-') - .next() - .unwrap(); - - let bits = arch - .chars() - .take_while(|c| c.is_ascii_digit()) - .collect::() - .parse::() - .unwrap(); - - let mut extensions: HashSet = arch.chars().skip_while(|c| c.is_ascii_digit()).collect(); - // expand the 'g' shorthand extension - if extensions.contains(&'g') { - extensions.insert('i'); - extensions.insert('m'); - extensions.insert('a'); - extensions.insert('f'); - extensions.insert('d'); - } - - let cargo_flags = cargo_flags - .split(0x1fu8 as char) - .filter(|arg| !arg.is_empty()); - - cargo_flags - .filter(|k| k.starts_with("target-feature=")) - .flat_map(|str| { - let flags = str.split('=').collect::>()[1]; - flags.split(',') - }) - .for_each(|feature| { - let chars = feature.chars().collect::>(); - match chars[0] { - '+' => { - extensions.insert(chars[1]); - } - '-' => { - extensions.remove(&chars[1]); - } - _ => { - panic!("Unsupported target feature operation"); - } - } - }); - - (bits, extensions) -} - fn main() { - println!("cargo:rustc-check-cfg=cfg(riscv)"); - println!("cargo:rustc-check-cfg=cfg(riscv32)"); - println!("cargo:rustc-check-cfg=cfg(riscv64)"); - for ext in ['i', 'e', 'm', 'a', 'f', 'd', 'g', 'c'] { - println!("cargo:rustc-check-cfg=cfg(riscv{})", ext); + // Required until target_feature risc-v is stable and in-use (rust 1.75) + for ext in RISCV_CFG.iter() { + println!("cargo:rustc-check-cfg=cfg({ext})"); } let target = env::var("TARGET").unwrap(); let cargo_flags = env::var("CARGO_ENCODED_RUSTFLAGS").unwrap(); - let _name = env::var("CARGO_PKG_NAME").unwrap(); - // set configuration flags depending on the target - if target.starts_with("riscv") { - println!("cargo:rustc-cfg=riscv"); - // This is required until target_arch & target_feature risc-v work is - // stable and in-use (rust 1.75.0) - let (bits, extensions) = parse_target(&target, &cargo_flags); - - // generate the linker script and expose the ISA width - let arch_width = match bits { - 32 => { - println!("cargo:rustc-cfg=riscv32"); - 4 - } - 64 => { - println!("cargo:rustc-cfg=riscv64"); - 8 + if let Ok(target) = RiscvTarget::build(&target, &cargo_flags) { + let width = target.width(); + + // set environmet variable RISCV_RT_BASE_ISA to the base ISA of the target. + println!( + "cargo:rustc-env=RISCV_RT_BASE_ISA={}", + target.llvm_base_isa() + ); + // set environment variable RISCV_RT_LLVM_ARCH_PATCH to patch LLVM bug. + // (this env variable is temporary and will be removed after LLVM being fixed) + println!( + "cargo:rustc-env=RISCV_RT_LLVM_ARCH_PATCH={}", + target.llvm_arch_patch() + ); + // make sure that these env variables are not changed without notice. + println!("cargo:rerun-if-env-changed=RISCV_RT_BASE_ISA"); + println!("cargo:rerun-if-env-changed=RISCV_RT_LLVM_ARCH_PATCH"); + + for flag in target.rustc_flags() { + // Required until target_feature risc-v is stable and in-use + if RISCV_CFG.contains(&flag.as_str()) { + println!("cargo:rustc-cfg={flag}"); } - _ => panic!("Unsupported bit width"), - }; - add_linker_script(arch_width).unwrap(); - - // expose the ISA extensions - for ext in &extensions { - println!("cargo:rustc-cfg=riscv{}", ext); } + add_linker_script(width.into()).unwrap(); } } diff --git a/riscv-rt/macros/Cargo.toml b/riscv-rt/macros/Cargo.toml index f856c7b8..e067dbd3 100644 --- a/riscv-rt/macros/Cargo.toml +++ b/riscv-rt/macros/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["riscv", "runtime", "startup"] license = "MIT OR Apache-2.0" name = "riscv-rt-macros" repository = "https://github.com/rust-embedded/riscv" -version = "0.2.2" +version = "0.3.0" edition = "2021" [lib] diff --git a/riscv-rt/macros/src/lib.rs b/riscv-rt/macros/src/lib.rs index 3cd75e1a..07b4e023 100644 --- a/riscv-rt/macros/src/lib.rs +++ b/riscv-rt/macros/src/lib.rs @@ -1,11 +1,6 @@ #![deny(warnings)] -extern crate core; -extern crate proc_macro; -extern crate proc_macro2; -extern crate quote; -extern crate syn; - +use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; use syn::{ @@ -16,8 +11,6 @@ use syn::{ FnArg, ItemFn, LitInt, LitStr, PatType, Path, ReturnType, Token, Type, Visibility, }; -use proc_macro::TokenStream; - /// Attribute to declare the entry point of the program /// /// **IMPORTANT**: This attribute must appear exactly *once* in the dependency graph. Also, if you @@ -359,56 +352,82 @@ pub fn loop_global_asm(input: TokenStream) -> TokenStream { #[derive(Clone, Copy, Debug)] enum RiscvArch { - Rv32, - Rv64, + Rv32I, + Rv32E, + Rv64I, + Rv64E, +} + +impl Parse for RiscvArch { + fn parse(input: parse::ParseStream) -> syn::Result { + let ident: syn::Ident = input.parse()?; + match ident.to_string().as_str() { + "rv32i" => Ok(Self::Rv32I), + "rv32e" => Ok(Self::Rv32E), + "rv64i" => Ok(Self::Rv64I), + "rv64e" => Ok(Self::Rv64E), + _ => Err(syn::Error::new(ident.span(), "Invalid RISC-V architecture")), + } + } } impl RiscvArch { - fn width(&self) -> usize { + fn try_from_env() -> Option { + let arch = std::env::var("RISCV_RT_BASE_ISA").ok()?; + match arch.as_str() { + "rv32i" => Some(Self::Rv32I), + "rv32e" => Some(Self::Rv32E), + "rv64i" => Some(Self::Rv64I), + "rv64e" => Some(Self::Rv64E), + _ => None, + } + } + + const fn width(&self) -> usize { match self { - Self::Rv32 => 4, - Self::Rv64 => 8, + Self::Rv32I | Self::Rv32E => 4, + Self::Rv64I | Self::Rv64E => 8, } } - fn store(&self) -> &str { + const fn store(&self) -> &str { match self { - Self::Rv32 => "sw", - Self::Rv64 => "sd", + Self::Rv32I | Self::Rv32E => "sw", + Self::Rv64I | Self::Rv64E => "sd", } } - fn load(&self) -> &str { + const fn load(&self) -> &str { match self { - Self::Rv32 => "lw", - Self::Rv64 => "ld", + Self::Rv32I | Self::Rv32E => "lw", + Self::Rv64I | Self::Rv64E => "ld", + } + } + + fn trap_frame(&self) -> Vec<&str> { + match self { + Self::Rv32I | Self::Rv64I => vec![ + "ra", "t0", "t1", "t2", "t3", "t4", "t5", "t6", "a0", "a1", "a2", "a3", "a4", "a5", + "a6", "a7", + ], + Self::Rv32E | Self::Rv64E => { + vec!["ra", "t0", "t1", "t2", "a0", "a1", "a2", "a3", "a4", "a5"] + } } } -} -/// Size of the trap frame (in number of registers) -const TRAP_SIZE: usize = 16; - -#[rustfmt::skip] -/// List of the register names to be stored in the trap frame -const TRAP_FRAME: [&str; TRAP_SIZE] = [ - "ra", - "t0", - "t1", - "t2", - "t3", - "t4", - "t5", - "t6", - "a0", - "a1", - "a2", - "a3", - "a4", - "a5", - "a6", - "a7", -]; + /// Standard RISC-V ABI requires the stack to be 16-byte aligned. + /// However, in LLVM, for RV32E and RV64E, the stack must be 4-byte aligned + /// to be compatible with the implementation of ilp32e in GCC + /// + /// Related: https://llvm.org/docs/RISCVUsage.html + const fn byte_alignment(&self) -> usize { + match self { + Self::Rv32E | Self::Rv64E => 4, + _ => 16, + } + } +} /// Generate the assembly instructions to store the trap frame. /// @@ -421,13 +440,13 @@ const TRAP_FRAME: [&str; TRAP_SIZE] = [ fn store_trap bool>(arch: RiscvArch, mut filter: T) -> String { let width = arch.width(); let store = arch.store(); - TRAP_FRAME + arch.trap_frame() .iter() .enumerate() - .filter(|(_, ®)| filter(reg)) + .filter(|(_, ®)| !reg.starts_with('_') && filter(reg)) .map(|(i, reg)| format!("{store} {reg}, {i}*{width}(sp)")) .collect::>() - .join("\n") + .join("\n ") } /// Generate the assembly instructions to load the trap frame. @@ -435,43 +454,40 @@ fn store_trap bool>(arch: RiscvArch, mut filter: T) -> String fn load_trap(arch: RiscvArch) -> String { let width = arch.width(); let load = arch.load(); - TRAP_FRAME + arch.trap_frame() .iter() .enumerate() + .filter(|(_, ®)| !reg.starts_with('_')) .map(|(i, reg)| format!("{load} {reg}, {i}*{width}(sp)")) .collect::>() - .join("\n") + .join("\n ") } -/// Generates weak `_start_trap` function in assembly for RISCV-32 targets. -/// -/// This implementation stores all registers in the trap frame and calls `_start_trap_rust`. -/// The trap frame is allocated on the stack and deallocated after the call. +/// Temporary patch macro to deal with LLVM bug #[proc_macro] -pub fn weak_start_trap_riscv32(_input: TokenStream) -> TokenStream { - weak_start_trap(RiscvArch::Rv32) +pub fn llvm_arch_patch(_input: TokenStream) -> TokenStream { + let q = if let Ok(arch) = std::env::var("RISCV_RT_LLVM_ARCH_PATCH") { + let patch = format!(".attribute arch,\"{arch}\""); + quote! { core::arch::global_asm!{#patch} } + } else { + quote!(compile_error!("RISCV_RT_LLVM_ARCH_PATCH is not set")) + }; + q.into() } -/// Generates weak `_start_trap` function in assembly for RISCV-64 targets. +/// Generates weak `_start_trap` function in assembly. /// /// This implementation stores all registers in the trap frame and calls `_start_trap_rust`. /// The trap frame is allocated on the stack and deallocated after the call. #[proc_macro] -pub fn weak_start_trap_riscv64(_input: TokenStream) -> TokenStream { - weak_start_trap(RiscvArch::Rv64) -} +pub fn weak_start_trap(_input: TokenStream) -> TokenStream { + let arch = RiscvArch::try_from_env().unwrap(); -/// Generates weak `_start_trap` function in assembly. -/// -/// This implementation stores all registers in the trap frame and calls `_start_trap_rust`. -/// The trap frame is allocated on the stack and deallocated after the call. -/// -/// The `arch` parameter is used to determine the width of the registers. -/// The macro also ensures that the trap frame size is 16-byte aligned. -fn weak_start_trap(arch: RiscvArch) -> TokenStream { let width = arch.width(); + let trap_size = arch.trap_frame().len(); + let byte_alignment = arch.byte_alignment(); // ensure we do not break that sp is 16-byte aligned - if (TRAP_SIZE * width) % 16 != 0 { + if (trap_size * width) % byte_alignment != 0 { return parse::Error::new(Span::call_site(), "Trap frame size must be 16-byte aligned") .to_compile_error() .into(); @@ -491,12 +507,12 @@ core::arch::global_asm!( .align {width} .weak _start_trap _start_trap: - addi sp, sp, - {TRAP_SIZE} * {width} + addi sp, sp, - {trap_size} * {width} {store} add a0, sp, zero jal ra, _start_trap_rust {load} - addi sp, sp, {TRAP_SIZE} * {width} + addi sp, sp, {trap_size} * {width} {ret} ");"# ) @@ -504,27 +520,16 @@ _start_trap: .unwrap() } -/// Generates vectored interrupt trap functions in assembly for RISCV-32 targets. -#[cfg(feature = "v-trap")] -#[proc_macro] -pub fn vectored_interrupt_trap_riscv32(_input: TokenStream) -> TokenStream { - vectored_interrupt_trap(RiscvArch::Rv32) -} - -/// Generates vectored interrupt trap functions in assembly for RISCV-64 targets. #[cfg(feature = "v-trap")] #[proc_macro] -pub fn vectored_interrupt_trap_riscv64(_input: TokenStream) -> TokenStream { - vectored_interrupt_trap(RiscvArch::Rv64) -} - -#[cfg(feature = "v-trap")] /// Generates global '_start_DefaultHandler_trap' and '_continue_interrupt_trap' functions in assembly. /// The '_start_DefaultHandler_trap' function stores the trap frame partially (only register a0) and /// jumps to the interrupt handler. The '_continue_interrupt_trap' function stores the trap frame /// partially (all registers except a0), jumps to the interrupt handler, and restores the trap frame. -fn vectored_interrupt_trap(arch: RiscvArch) -> TokenStream { +pub fn vectored_interrupt_trap(_input: TokenStream) -> TokenStream { + let arch = RiscvArch::try_from_env().unwrap(); let width = arch.width(); + let trap_size = arch.trap_frame().len(); let store_start = store_trap(arch, |reg| reg == "a0"); let store_continue = store_trap(arch, |reg| reg != "a0"); let load = load_trap(arch); @@ -542,7 +547,7 @@ core::arch::global_asm!( .align 4 .global _start_DefaultHandler_trap _start_DefaultHandler_trap: - addi sp, sp, -{TRAP_SIZE} * {width} // allocate space for trap frame + addi sp, sp, -{trap_size} * {width} // allocate space for trap frame {store_start} // store trap partially (only register a0) la a0, DefaultHandler // load interrupt handler address into a0 @@ -552,7 +557,7 @@ _continue_interrupt_trap: {store_continue} // store trap partially (all registers except a0) jalr ra, a0, 0 // jump to corresponding interrupt handler (address stored in a0) {load} // restore trap frame - addi sp, sp, {TRAP_SIZE} * {width} // deallocate space for trap frame + addi sp, sp, {trap_size} * {width} // deallocate space for trap frame {ret} // return from interrupt ");"# ); @@ -664,39 +669,11 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { /// loop{}; /// } /// ``` -pub fn core_interrupt_riscv32(args: TokenStream, input: TokenStream) -> TokenStream { - let arch = match () { - #[cfg(feature = "v-trap")] - () => Some(RiscvArch::Rv32), - #[cfg(not(feature = "v-trap"))] - () => None, - }; - trap(args, input, RiscvPacItem::CoreInterrupt, arch) -} - -#[proc_macro_attribute] -/// Attribute to declare a core interrupt handler. -/// -/// The function must have the signature `[unsafe] fn() [-> !]`. -/// -/// The argument of the macro must be a path to a variant of an enum that implements the `riscv_rt::CoreInterruptNumber` trait. -/// -/// If the `v-trap` feature is enabled, this macro generates the corresponding interrupt trap handler in assembly. -/// -/// # Example -/// -/// ``` ignore,no_run -/// #[riscv_rt::core_interrupt(riscv::interrupt::Interrupt::SupervisorSoft)] -/// fn supervisor_soft() -> ! { -/// loop{}; -/// } -/// ``` -pub fn core_interrupt_riscv64(args: TokenStream, input: TokenStream) -> TokenStream { - let arch = match () { - #[cfg(feature = "v-trap")] - () => Some(RiscvArch::Rv64), - #[cfg(not(feature = "v-trap"))] - () => None, +pub fn core_interrupt(args: TokenStream, input: TokenStream) -> TokenStream { + let arch = if cfg!(feature = "v-trap") { + RiscvArch::try_from_env() + } else { + None }; trap(args, input, RiscvPacItem::CoreInterrupt, arch) } @@ -757,7 +734,6 @@ fn trap( Some(arch) => { let trap = start_interrupt_trap(int_ident, arch); quote! { - #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] #trap } } @@ -784,6 +760,7 @@ fn trap( fn start_interrupt_trap(ident: &syn::Ident, arch: RiscvArch) -> proc_macro2::TokenStream { let interrupt = ident.to_string(); let width = arch.width(); + let trap_size = arch.trap_frame().len(); let store = store_trap(arch, |r| r == "a0"); let instructions = format!( @@ -793,7 +770,7 @@ core::arch::global_asm!( .align 2 .global _start_{interrupt}_trap _start_{interrupt}_trap: - addi sp, sp, -{TRAP_SIZE} * {width} // allocate space for trap frame + addi sp, sp, -{trap_size} * {width} // allocate space for trap frame {store} // store trap partially (only register a0) la a0, {interrupt} // load interrupt handler address into a0 j _continue_interrupt_trap // jump to common part of interrupt trap diff --git a/riscv-rt/src/asm.rs b/riscv-rt/src/asm.rs index cf09782e..098d75c7 100644 --- a/riscv-rt/src/asm.rs +++ b/riscv-rt/src/asm.rs @@ -29,15 +29,7 @@ macro_rules! cfg_global_asm { // - https://github.com/rust-embedded/riscv/issues/175 // - https://github.com/rust-lang/rust/issues/80608 // - https://github.com/llvm/llvm-project/issues/61991 -cfg_global_asm!( - "// Provisional patch to avoid LLVM spurious errors when compiling in release mode.", - #[cfg(all(riscv32, riscvm))] - ".attribute arch, \"rv32im\"", - #[cfg(all(riscv64, riscvm, not(riscvg)))] - ".attribute arch, \"rv64im\"", - #[cfg(all(riscv64, riscvg))] - ".attribute arch, \"rv64g\"", -); +riscv_rt_macros::llvm_arch_patch!(); // Entry point of all programs (_start). It initializes DWARF call frame information, // the stack pointer, the frame pointer (needed for closures to work in start_rust) @@ -47,10 +39,10 @@ cfg_global_asm!( .global _start _start:", - #[cfg(riscv32)] + #[cfg(target_arch = "riscv32")] "lui ra, %hi(_abs_start) jr %lo(_abs_start)(ra)", - #[cfg(riscv64)] + #[cfg(target_arch = "riscv64")] ".option push .option norelax // to prevent an unsupported R_RISCV_ALIGN relocation from being generated 1: @@ -84,7 +76,9 @@ _abs_start: // ZERO OUT GENERAL-PURPOSE REGISTERS riscv_rt_macros::loop_global_asm!(" li x{}, 0", 1, 10); // a0..a2 (x10..x12) skipped -riscv_rt_macros::loop_global_asm!(" li x{}, 0", 13, 32); +riscv_rt_macros::loop_global_asm!(" li x{}, 0", 13, 16); +#[cfg(riscvi)] +riscv_rt_macros::loop_global_asm!(" li x{}, 0", 16, 32); // INITIALIZE GLOBAL POINTER, STACK POINTER, AND FRAME POINTER cfg_global_asm!( @@ -107,13 +101,12 @@ cfg_global_asm!( #[cfg(riscvm)] "mul t0, t2, t0", #[cfg(not(riscvm))] - "beqz t2, 2f // Jump if single-hart - mv t1, t2 - mv t3, t0 + "beqz t2, 2f // skip if hart ID is 0 + mv t1, t0 1: - add t0, t0, t3 - addi t1, t1, -1 - bnez t1, 1b + add t0, t0, t1 + addi t2, t2, -1 + bnez t2, 1b 2: ", ); cfg_global_asm!( @@ -126,13 +119,13 @@ cfg_global_asm!( // STORE A0..A2 IN THE STACK, AS THEY WILL BE NEEDED LATER BY main cfg_global_asm!( - #[cfg(riscv32)] - "addi sp, sp, -4 * 3 + #[cfg(target_arch = "riscv32")] + "addi sp, sp, -4 * 4 // we must keep stack aligned to 16-bytes sw a0, 4 * 0(sp) sw a1, 4 * 1(sp) sw a2, 4 * 2(sp)", - #[cfg(riscv64)] - "addi sp, sp, -8 * 3 + #[cfg(target_arch = "riscv64")] + "addi sp, sp, -8 * 4 // we must keep stack aligned to 16-bytes sd a0, 8 * 0(sp) sd a1, 8 * 1(sp) sd a2, 8 * 2(sp)", @@ -153,22 +146,22 @@ cfg_global_asm!( "call __pre_init // Copy .data from flash to RAM la t0, __sdata - la t2, __edata + la a0, __edata la t1, __sidata - bgeu t0, t2, 2f + bgeu t0, a0, 2f 1: ", #[cfg(target_arch = "riscv32")] - "lw t3, 0(t1) + "lw t2, 0(t1) addi t1, t1, 4 - sw t3, 0(t0) + sw t2, 0(t0) addi t0, t0, 4 - bltu t0, t2, 1b", + bltu t0, a0, 1b", #[cfg(target_arch = "riscv64")] - "ld t3, 0(t1) + "ld t2, 0(t1) addi t1, t1, 8 - sd t3, 0(t0) + sd t2, 0(t0) addi t0, t0, 8 - bltu t0, t2, 1b", + bltu t0, a0, 1b", " 2: // Zero out .bss la t0, __sbss @@ -203,9 +196,9 @@ cfg_global_asm!( "fscsr x0", ); // ZERO OUT FLOATING POINT REGISTERS -#[cfg(all(riscv32, riscvd))] +#[cfg(all(target_arch = "riscv32", riscvd))] riscv_rt_macros::loop_global_asm!(" fcvt.d.w f{}, x0", 32); -#[cfg(all(riscv64, riscvd))] +#[cfg(all(target_arch = "riscv64", riscvd))] riscv_rt_macros::loop_global_asm!(" fmv.d.x f{}, x0", 32); #[cfg(all(riscvf, not(riscvd)))] riscv_rt_macros::loop_global_asm!(" fmv.w.x f{}, x0", 32); @@ -213,16 +206,16 @@ riscv_rt_macros::loop_global_asm!(" fmv.w.x f{}, x0", 32); // SET UP INTERRUPTS, RESTORE a0..a2, AND JUMP TO MAIN RUST FUNCTION cfg_global_asm!( "call _setup_interrupts", - #[cfg(riscv32)] + #[cfg(target_arch = "riscv32")] "lw a0, 4 * 0(sp) lw a1, 4 * 1(sp) lw a2, 4 * 2(sp) - addi sp, sp, 4 * 3", - #[cfg(riscv64)] + addi sp, sp, 4 * 4", + #[cfg(target_arch = "riscv64")] "ld a0, 8 * 0(sp) ld a1, 8 * 1(sp) ld a2, 8 * 2(sp) - addi sp, sp, 8 * 3", + addi sp, sp, 8 * 4", "jal zero, main .cfi_endproc", ); @@ -277,15 +270,10 @@ _pre_init_trap: j _pre_init_trap", ); -#[cfg(riscv32)] -riscv_rt_macros::weak_start_trap_riscv32!(); -#[cfg(riscv64)] -riscv_rt_macros::weak_start_trap_riscv64!(); +riscv_rt_macros::weak_start_trap!(); -#[cfg(all(riscv32, feature = "v-trap"))] -riscv_rt_macros::vectored_interrupt_trap_riscv32!(); -#[cfg(all(riscv64, feature = "v-trap"))] -riscv_rt_macros::vectored_interrupt_trap_riscv64!(); +#[cfg(feature = "v-trap")] +riscv_rt_macros::vectored_interrupt_trap!(); #[rustfmt::skip] global_asm!( diff --git a/riscv-rt/src/interrupts.rs b/riscv-rt/src/interrupts.rs index 6fe900e5..b7ab89a2 100644 --- a/riscv-rt/src/interrupts.rs +++ b/riscv-rt/src/interrupts.rs @@ -71,7 +71,10 @@ pub unsafe extern "C" fn _dispatch_core_interrupt(code: usize) { } // In vectored mode, we also must provide a vector table -#[cfg(all(riscv, feature = "v-trap"))] +#[cfg(all( + any(target_arch = "riscv32", target_arch = "riscv64"), + feature = "v-trap" +))] core::arch::global_asm!( r#" .section .trap, "ax" .weak _vector_table diff --git a/riscv-rt/src/lib.rs b/riscv-rt/src/lib.rs index fe770d73..b999c2d9 100644 --- a/riscv-rt/src/lib.rs +++ b/riscv-rt/src/lib.rs @@ -532,7 +532,7 @@ #![no_std] #![deny(missing_docs)] -#[cfg(riscv)] +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] mod asm; #[cfg(not(feature = "no-exceptions"))] @@ -547,16 +547,8 @@ use riscv::register::scause as xcause; #[cfg(not(feature = "s-mode"))] use riscv::register::mcause as xcause; -pub use riscv_rt_macros::{entry, exception, external_interrupt, pre_init}; - pub use riscv_pac::*; - -#[cfg(riscv32)] -pub use riscv_rt_macros::core_interrupt_riscv32 as core_interrupt; -#[cfg(riscv64)] -pub use riscv_rt_macros::core_interrupt_riscv64 as core_interrupt; -#[cfg(not(riscv))] -pub use riscv_rt_macros::core_interrupt_riscv64 as core_interrupt; // just for docs, tests, etc. +pub use riscv_rt_macros::{core_interrupt, entry, exception, external_interrupt, pre_init}; /// We export this static with an informative name so that if an application attempts to link /// two copies of riscv-rt together, linking will fail. We also declare a links key in @@ -567,25 +559,46 @@ pub use riscv_rt_macros::core_interrupt_riscv64 as core_interrupt; // just for d pub static __ONCE__: () = (); /// Registers saved in trap handler -#[allow(missing_docs)] #[repr(C)] #[derive(Debug)] pub struct TrapFrame { + /// `x1`: return address, stores the address to return to after a function call or interrupt. pub ra: usize, + /// `x5`: temporary register `t0`, used for intermediate values. pub t0: usize, + /// `x6`: temporary register `t1`, used for intermediate values. pub t1: usize, + /// `x7`: temporary register `t2`, used for intermediate values. pub t2: usize, + /// `x28`: temporary register `t3`, used for intermediate values. + #[cfg(riscvi)] pub t3: usize, + /// `x29`: temporary register `t4`, used for intermediate values. + #[cfg(riscvi)] pub t4: usize, + /// `x30`: temporary register `t5`, used for intermediate values. + #[cfg(riscvi)] pub t5: usize, + /// `x31`: temporary register `t6`, used for intermediate values. + #[cfg(riscvi)] pub t6: usize, + /// `x10`: argument register `a0`. Used to pass the first argument to a function. pub a0: usize, + /// `x11`: argument register `a1`. Used to pass the second argument to a function. pub a1: usize, + /// `x12`: argument register `a2`. Used to pass the third argument to a function. pub a2: usize, + /// `x13`: argument register `a3`. Used to pass the fourth argument to a function. pub a3: usize, + /// `x14`: argument register `a4`. Used to pass the fifth argument to a function. pub a4: usize, + /// `x15`: argument register `a5`. Used to pass the sixth argument to a function. pub a5: usize, + #[cfg(riscvi)] + /// `x16`: argument register `a6`. Used to pass the seventh argument to a function. pub a6: usize, + #[cfg(riscvi)] + /// `x17`: argument register `a7`. Used to pass the eighth argument to a function. pub a7: usize, } @@ -619,7 +632,10 @@ pub struct TrapFrame { /// /// This function must be called only from assembly `_start_trap` function. /// Do **NOT** call this function directly. -#[cfg_attr(riscv, link_section = ".trap.rust")] +#[cfg_attr( + any(target_arch = "riscv32", target_arch = "riscv64"), + link_section = ".trap.rust" +)] #[export_name = "_start_trap_rust"] pub unsafe extern "C" fn start_trap_rust(trap_frame: *const TrapFrame) { extern "C" { diff --git a/riscv-target-parser/CHANGELOG.md b/riscv-target-parser/CHANGELOG.md new file mode 100644 index 00000000..4a26855b --- /dev/null +++ b/riscv-target-parser/CHANGELOG.md @@ -0,0 +1,7 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] + diff --git a/riscv-target-parser/Cargo.toml b/riscv-target-parser/Cargo.toml new file mode 100644 index 00000000..8ecd4a9c --- /dev/null +++ b/riscv-target-parser/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "riscv-target-parser" +version = "0.1.0" +rust-version = "1.61" +repository = "https://github.com/rust-embedded/riscv" +authors = ["The RISC-V Team "] +categories = ["embedded", "no-std"] +description = "Parser for RISC-V target specifications" +documentation = "https://docs.rs/riscv-target-parser" +keywords = ["riscv", "build"] +license = "ISC" +edition = "2021" diff --git a/riscv-target-parser/README.md b/riscv-target-parser/README.md new file mode 100644 index 00000000..0e8a8f04 --- /dev/null +++ b/riscv-target-parser/README.md @@ -0,0 +1,40 @@ +[![crates.io](https://img.shields.io/crates/d/riscv-target-parser.svg)](https://crates.io/crates/riscv-target-parser) +[![crates.io](https://img.shields.io/crates/v/riscv-target-parser.svg)](https://crates.io/crates/riscv-target-parser) + +# `riscv-target-parser` + +> Utility crate for parsing RISC-V targets in build scripts + +This project is developed and maintained by the [RISC-V team][team]. + +## [Documentation](https://docs.rs/crate/riscv-target-parser) + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.61 and up. It *might* +compile with older versions but that may change in any new patch release. + +## License + +Copyright 2024-2025 [RISC-V team][team] + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +## Code of Conduct + +Contribution to this crate is organized under the terms of the [Rust Code of +Conduct][CoC], the maintainer of this crate, the [RISC-V team][team], promises +to intervene to uphold that code of conduct. + +[CoC]: CODE_OF_CONDUCT.md +[team]: https://github.com/rust-embedded/wg#the-risc-v-team diff --git a/riscv-target-parser/src/extension.rs b/riscv-target-parser/src/extension.rs new file mode 100644 index 00000000..16afdfc9 --- /dev/null +++ b/riscv-target-parser/src/extension.rs @@ -0,0 +1,494 @@ +use crate::Error; +use std::collections::HashSet; + +/// RISC-V standard extensions +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Extension { + /// Base Integer Instruction Set + I, + /// Base Integer Instruction Set (embedded, only 16 registers) + E, + /// Integer Multiplication and Division + M, + /// Atomic Instructions + A, + /// Single-Precision Floating-Point + F, + /// Double-Precision Floating-Point + D, + /// Quad-Precision Floating-Point + Q, + /// Compressed Instructions + C, + /// Bit Manipulation + B, + /// Packed-SIMD Instructions + P, + /// Vector Operations + V, + /// Hypervisor + H, + /// Standard Z-type extension + Z(String), + /// Standard S-type extension + S(String), + /// Vendor extension + X(String), +} + +impl Extension { + /// Determines if the extension is a base extension. + pub const fn is_base(&self) -> bool { + matches!(self, Self::I | Self::E) + } +} + +impl std::fmt::Display for Extension { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let repr = match self { + Self::I => "i", + Self::E => "e", + Self::M => "m", + Self::A => "a", + Self::F => "f", + Self::D => "d", + Self::Q => "q", + Self::C => "c", + Self::B => "b", + Self::P => "p", + Self::V => "v", + Self::H => "h", + Self::Z(s) | Self::S(s) | Self::X(s) => s, + }; + write!(f, "{repr}") + } +} + +impl<'a> TryFrom<&'a str> for Extension { + type Error = Error<'a>; + + fn try_from(value: &'a str) -> Result { + match value { + "i" => Ok(Extension::I), + "e" => Ok(Extension::E), + "m" => Ok(Extension::M), + "a" => Ok(Extension::A), + "f" => Ok(Extension::F), + "d" => Ok(Extension::D), + "q" => Ok(Extension::Q), + "c" => Ok(Extension::C), + "b" => Ok(Extension::B), + "p" => Ok(Extension::P), + "v" => Ok(Extension::V), + "h" => Ok(Extension::H), + _ => { + if value.starts_with('Z') { + Ok(Extension::Z(value.to_string())) + } else if value.starts_with('S') { + Ok(Extension::S(value.to_string())) + } else if value.starts_with('X') { + Ok(Extension::X(value.to_string())) + } else { + Err(Self::Error::UnknownExtension(value)) + } + } + } + } +} + +/// Collection of RISC-V extensions. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Extensions { + extensions: HashSet, +} + +impl Extensions { + /// Returns a vector with the list of extensions. Extensions are sorted in canonical order. + /// + /// The canonical order is defined as follows: + /// 1. Base ISA (I or E) + /// 2. Standard non-base extensions (M, A, F, D, Q, C, B, P, V, H) + /// 3. Standard Z-type extensions (e.g., Zicsr) + /// 4. Standard S-type extensions (e.g., Ssccfg) + /// 5. Vendor X-type extensions (e.g., XSifivecdiscarddlone) + /// + /// Z, S, and X-type extensions are sorted by their string representation. + pub fn extensions(&self) -> Vec { + let mut res = self.extensions.iter().cloned().collect::>(); + res.sort(); + res + } + + /// Returns the base extension (I or E) if present. + pub fn base_extension(&self) -> Option { + if self.extensions.contains(&Extension::I) { + Some(Extension::I) + } else if self.extensions.contains(&Extension::E) { + Some(Extension::E) + } else { + None + } + } + + /// Returns `true` if the collection contains the given extension. + pub fn contains(&self, extension: &Extension) -> bool { + self.extensions.contains(extension) + } + + pub fn is_g(&self) -> bool { + self.extensions.contains(&Extension::I) + && self.extensions.contains(&Extension::M) + && self.extensions.contains(&Extension::A) + && self.extensions.contains(&Extension::F) + && self.extensions.contains(&Extension::D) + } + + /// Adds an extension to the collection. Returns `true` if the extension was not present. + pub fn insert(&mut self, extension: Extension) -> bool { + self.extensions.insert(extension) + } + + /// Removes an extension from the collection. Returns `true` if the extension was present. + pub fn remove(&mut self, extension: &Extension) -> bool { + self.extensions.remove(extension) + } +} + +impl<'a> TryFrom<&'a str> for Extensions { + type Error = Error<'a>; + + fn try_from(value: &'a str) -> Result { + let mut value = value; + let mut extensions = HashSet::new(); + + while !value.is_empty() { + let extension = + if value.starts_with("Z") || value.starts_with("S") || value.starts_with("X") { + match value.find('_') { + Some(pos) => { + let (ext, _) = value.split_at(pos); + ext + } + None => value, + } + } else { + &value[0..1] // single character extension + }; + value = value.trim_start_matches(extension).trim_start_matches("_"); + + match Extension::try_from(extension) { + Ok(ext) => { + extensions.insert(ext); + } + Err(Self::Error::UnknownExtension(ext)) => { + if ext == "g" { + // G is a shorthand for IMAFD + extensions.insert(Extension::I); + extensions.insert(Extension::M); + extensions.insert(Extension::A); + extensions.insert(Extension::F); + extensions.insert(Extension::D); + } else { + return Err(Self::Error::UnknownExtension(ext)); + } + } + _ => unreachable!(), + } + } + Ok(Extensions { extensions }) + } +} + +impl std::fmt::Display for Extensions { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut extensions = String::new(); + let mut prev_zsx = false; + for ext in &self.extensions() { + if prev_zsx { + extensions.push('_'); + } + extensions.push_str(ext.to_string().as_str()); + prev_zsx = matches!(ext, Extension::Z(_) | Extension::S(_) | Extension::X(_)); + } + match extensions.strip_prefix("imafd") { + Some(extensions) => write!(f, "g{}", extensions), + None => match extensions.strip_prefix("iemafd") { + Some(extensions) => write!(f, "ge{}", extensions), + None => write!(f, "{}", extensions), + }, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_extension_try_from() { + assert_eq!(Extension::try_from("i"), Ok(Extension::I)); + assert_eq!(Extension::try_from("e"), Ok(Extension::E)); + assert_eq!(Extension::try_from("m"), Ok(Extension::M)); + assert_eq!(Extension::try_from("a"), Ok(Extension::A)); + assert_eq!(Extension::try_from("f"), Ok(Extension::F)); + assert_eq!(Extension::try_from("d"), Ok(Extension::D)); + assert_eq!(Extension::try_from("q"), Ok(Extension::Q)); + assert_eq!(Extension::try_from("c"), Ok(Extension::C)); + assert_eq!(Extension::try_from("b"), Ok(Extension::B)); + assert_eq!(Extension::try_from("p"), Ok(Extension::P)); + assert_eq!(Extension::try_from("v"), Ok(Extension::V)); + assert_eq!(Extension::try_from("h"), Ok(Extension::H)); + assert_eq!( + Extension::try_from("Zicsr"), + Ok(Extension::Z("Zicsr".to_string())) + ); + assert_eq!( + Extension::try_from("Ssccfg"), + Ok(Extension::S("Ssccfg".to_string())) + ); + assert_eq!( + Extension::try_from("XSifivecdiscarddlone"), + Ok(Extension::X("XSifivecdiscarddlone".to_string())) + ); + assert_eq!( + Extension::try_from("unknown"), + Err(Error::UnknownExtension("unknown")) + ); + } + + #[test] + fn test_extension_to_string() { + assert_eq!(Extension::I.to_string(), "i"); + assert_eq!(Extension::E.to_string(), "e"); + assert_eq!(Extension::M.to_string(), "m"); + assert_eq!(Extension::A.to_string(), "a"); + assert_eq!(Extension::F.to_string(), "f"); + assert_eq!(Extension::D.to_string(), "d"); + assert_eq!(Extension::Q.to_string(), "q"); + assert_eq!(Extension::C.to_string(), "c"); + assert_eq!(Extension::B.to_string(), "b"); + assert_eq!(Extension::P.to_string(), "p"); + assert_eq!(Extension::V.to_string(), "v"); + assert_eq!(Extension::H.to_string(), "h"); + assert_eq!(Extension::Z("Zicsr".to_string()).to_string(), "Zicsr"); + assert_eq!(Extension::S("Ssccfg".to_string()).to_string(), "Ssccfg"); + assert_eq!( + Extension::X("XSifivecdiscarddlone".to_string()).to_string(), + "XSifivecdiscarddlone" + ); + } + + #[test] + fn test_extension_cmp() { + let mut extensions = vec![ + Extension::I, + Extension::M, + Extension::A, + Extension::F, + Extension::D, + Extension::Q, + Extension::C, + Extension::B, + Extension::P, + Extension::V, + Extension::H, + Extension::Z("Zicsr".to_string()), + Extension::S("Ssccfg".to_string()), + Extension::X("XSifivecdiscarddlone".to_string()), + ]; + extensions.reverse(); + extensions.sort(); + assert_eq!( + extensions, + vec![ + Extension::I, + Extension::M, + Extension::A, + Extension::F, + Extension::D, + Extension::Q, + Extension::C, + Extension::B, + Extension::P, + Extension::V, + Extension::H, + Extension::Z("Zicsr".to_string()), + Extension::S("Ssccfg".to_string()), + Extension::X("XSifivecdiscarddlone".to_string()), + ] + ); + } + + #[test] + fn test_extensions_try_from() { + let mut try_extensions = Extensions::try_from(""); + assert!(try_extensions.is_ok()); + let mut extensions = try_extensions.unwrap(); + assert!(extensions.extensions().is_empty()); + assert!(extensions.base_extension().is_none()); + + try_extensions = + Extensions::try_from("giemafdqcbpvhXSifivecdiscarddlone_Ssccfg_Zicsr_Zaamo_u"); + assert!(try_extensions.is_err()); + assert_eq!(try_extensions, Err(Error::UnknownExtension("u"))); + + try_extensions = Extensions::try_from("geqcbpvhXSifivecdiscarddlone_Ssccfg_Zicsr_Zaamo_"); + assert!(try_extensions.is_ok()); + extensions = try_extensions.unwrap(); + assert_eq!( + extensions.extensions(), + vec![ + Extension::I, + Extension::E, + Extension::M, + Extension::A, + Extension::F, + Extension::D, + Extension::Q, + Extension::C, + Extension::B, + Extension::P, + Extension::V, + Extension::H, + Extension::Z("Zaamo".to_string()), + Extension::Z("Zicsr".to_string()), + Extension::S("Ssccfg".to_string()), + Extension::X("XSifivecdiscarddlone".to_string()), + ] + ); + assert_eq!(extensions.base_extension(), Some(Extension::I)); + + try_extensions = + Extensions::try_from("iemafdqcbpvhXSifivecdiscarddlone_Ssccfg_Zicsr_Zaamo_"); + assert!(try_extensions.is_ok()); + extensions = try_extensions.unwrap(); + assert_eq!( + extensions.extensions(), + vec![ + Extension::I, + Extension::E, + Extension::M, + Extension::A, + Extension::F, + Extension::D, + Extension::Q, + Extension::C, + Extension::B, + Extension::P, + Extension::V, + Extension::H, + Extension::Z("Zaamo".to_string()), + Extension::Z("Zicsr".to_string()), + Extension::S("Ssccfg".to_string()), + Extension::X("XSifivecdiscarddlone".to_string()), + ] + ); + assert_eq!(extensions.base_extension(), Some(Extension::I)); + + try_extensions = + Extensions::try_from("emafdqcbpvhXSifivecdiscarddlone_Ssccfg_Zicsr_Zaamo_"); + assert!(try_extensions.is_ok()); + extensions = try_extensions.unwrap(); + assert_eq!( + extensions.extensions(), + vec![ + Extension::E, + Extension::M, + Extension::A, + Extension::F, + Extension::D, + Extension::Q, + Extension::C, + Extension::B, + Extension::P, + Extension::V, + Extension::H, + Extension::Z("Zaamo".to_string()), + Extension::Z("Zicsr".to_string()), + Extension::S("Ssccfg".to_string()), + Extension::X("XSifivecdiscarddlone".to_string()), + ] + ); + assert_eq!(extensions.base_extension(), Some(Extension::E)); + } + + #[test] + fn test_extensions_insert_remove() { + let mut extensions = Extensions::try_from("gc").unwrap(); + + assert_eq!(extensions.extensions.len(), 6); + assert!(extensions.contains(&Extension::I)); + assert!(extensions.contains(&Extension::M)); + assert!(extensions.contains(&Extension::A)); + assert!(extensions.contains(&Extension::F)); + assert!(extensions.contains(&Extension::D)); + assert!(extensions.contains(&Extension::C)); + assert!(!extensions.contains(&Extension::E)); + assert!(!extensions.contains(&Extension::Q)); + assert_eq!(extensions.base_extension(), Some(Extension::I)); + + assert!(!extensions.insert(Extension::I)); + assert!(!extensions.remove(&Extension::E)); + assert_eq!(extensions.extensions.len(), 6); + + assert!(extensions.insert(Extension::E)); + assert_eq!(extensions.extensions.len(), 7); + assert!(extensions.contains(&Extension::E)); + assert_eq!(extensions.base_extension(), Some(Extension::I)); + + assert!(extensions.remove(&Extension::I)); + assert_eq!(extensions.extensions.len(), 6); + assert!(!extensions.contains(&Extension::I)); + assert_eq!(extensions.base_extension(), Some(Extension::E)); + + assert!(extensions.remove(&Extension::E)); + assert_eq!(extensions.extensions.len(), 5); + assert!(!extensions.contains(&Extension::E)); + assert_eq!(extensions.base_extension(), None); + } + + #[test] + fn test_extensions_to_string() { + let mut extensions = Extensions::try_from("imafdc").unwrap(); + assert_eq!(extensions.to_string(), "gc"); + + extensions.insert(Extension::try_from("Ssccfg").unwrap()); + assert_eq!(extensions.to_string(), "gcSsccfg"); + + extensions.insert(Extension::try_from("Zicsr").unwrap()); + assert_eq!(extensions.to_string(), "gcZicsr_Ssccfg"); + + extensions.insert(Extension::try_from("Zaamo").unwrap()); + assert_eq!(extensions.to_string(), "gcZaamo_Zicsr_Ssccfg"); + + extensions.insert(Extension::try_from("XSifivecdiscarddlone").unwrap()); + assert_eq!( + extensions.to_string(), + "gcZaamo_Zicsr_Ssccfg_XSifivecdiscarddlone" + ); + + extensions.insert(Extension::try_from("e").unwrap()); + assert_eq!( + extensions.to_string(), + "gecZaamo_Zicsr_Ssccfg_XSifivecdiscarddlone" + ); + + extensions.remove(&Extension::I); + assert_eq!( + extensions.to_string(), + "emafdcZaamo_Zicsr_Ssccfg_XSifivecdiscarddlone" + ); + + extensions.remove(&Extension::E); + assert_eq!( + extensions.to_string(), + "mafdcZaamo_Zicsr_Ssccfg_XSifivecdiscarddlone" + ); + + extensions.insert(Extension::I); + assert_eq!( + extensions.to_string(), + "gcZaamo_Zicsr_Ssccfg_XSifivecdiscarddlone" + ); + } +} diff --git a/riscv-target-parser/src/lib.rs b/riscv-target-parser/src/lib.rs new file mode 100644 index 00000000..6a7ce0b8 --- /dev/null +++ b/riscv-target-parser/src/lib.rs @@ -0,0 +1,254 @@ +pub mod extension; +pub use extension::{Extension, Extensions}; + +/// Error variants for the RISC-V target parser. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Error<'a> { + InvalidTriple(&'a str), + InvalidArch(&'a str), + InvalidWidth(usize), + UnknownExtension(&'a str), + UnknownTargetFeature(&'a str), +} + +/// Helper struct to parse and store a target triple. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TargetTriple<'a> { + arch: &'a str, + vendor: &'a str, + os: &'a str, + bin: Option<&'a str>, +} + +impl<'a> TryFrom<&'a str> for TargetTriple<'a> { + type Error = Error<'a>; + + fn try_from(value: &'a str) -> Result { + let mut parts = value.split('-'); + + let arch = parts.next().ok_or(Error::InvalidTriple(value))?; + let vendor = parts.next().ok_or(Error::InvalidTriple(value))?; + let os = parts.next().ok_or(Error::InvalidTriple(value))?; + let bin = parts.next(); + + Ok(Self { + arch, + vendor, + os, + bin, + }) + } +} + +impl std::fmt::Display for TargetTriple<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}-{}-{}", self.arch, self.vendor, self.os)?; + if let Some(bin) = self.bin { + write!(f, "-{}", bin)?; + } + Ok(()) + } +} + +/// The width of the RISC-V architecture. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Width { + /// 32-bit RISC-V architecture. + W32, + /// 64-bit RISC-V architecture. + W64, + /// 128-bit RISC-V architecture. + W128, +} + +impl std::fmt::Display for Width { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::W32 => write!(f, "32"), + Self::W64 => write!(f, "64"), + Self::W128 => write!(f, "128"), + } + } +} + +macro_rules! impl_try_from_width { + ($($t:ty),*) => { + $( + impl TryFrom<$t> for Width { + type Error = Error<'static>; + fn try_from(bits: $t) -> Result { + match bits { + 32 => Ok(Self::W32), + 64 => Ok(Self::W64), + 128 => Ok(Self::W128), + _ => Err(Self::Error::InvalidWidth(bits as usize)), + } + } + } + impl From for $t { + fn from(width: Width) -> Self { + match width { + Width::W32 => 32, + Width::W64 => 64, + Width::W128 => 128, + } + } + } + )* + }; +} +impl_try_from_width!(u8, u16, u32, u64, u128, usize, i16, i32, i64, i128, isize); + +/// Struct that represents a RISC-V target. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RiscvTarget { + width: Width, + extensions: Extensions, +} + +impl RiscvTarget { + /// Builds a RISC-V target from a target triple and cargo flags. + /// This function is expected to be called from a build script. + /// + /// The target triple is expected to be in the form `riscv{width}{extensions}-vendor-os[-bin]`. + /// If the target triple is invalid, an error is returned. + /// + /// # Example + /// + /// ```no_run + /// + /// // In build.rs + /// let target = std::env::var("TARGET").unwrap(); + /// let cargo_flags = std::env::var("CARGO_ENCODED_RUSTFLAGS").unwrap(); + /// let target = riscv_target_parser::RiscvTarget::build(&target, &cargo_flags).unwrap(); // This will panic if the target is invalid + /// ``` + pub fn build<'a>(target: &'a str, cargo_flags: &'a str) -> Result> { + let triple = TargetTriple::try_from(target)?; + let mut target = Self::try_from(triple)?; + + for target_feature in cargo_flags + .split(0x1fu8 as char) + .filter(|arg| arg.starts_with("target-feature=")) + .flat_map(|arg| { + let arg = arg.trim_start_matches("target-feature="); + arg.split(',') + }) + { + if let Some(feature) = target_feature.strip_prefix('+') { + let extension = Extension::try_from(feature)?; + target.extensions.insert(extension); + } else if let Some(feature) = target_feature.strip_prefix('-') { + let extension = Extension::try_from(feature)?; + target.extensions.remove(&extension); + } else { + return Err(Error::UnknownTargetFeature(target_feature)); + } + } + Ok(target) + } + + /// Returns a list of flags to pass to `rustc` for the given RISC-V target. + /// This function is expected to be called from a build script. + /// + /// # Example + /// + /// ```no_run + /// let target = std::env::var("TARGET").unwrap(); + /// let cargo_flags = std::env::var("CARGO_ENCODED_RUSTFLAGS").unwrap(); + /// let target = riscv_target_parser::RiscvTarget::build(&target, &cargo_flags).unwrap(); + /// for flag in target.rustc_flags() { + /// println!("cargo:rustc-check-cfg=cfg({})", flag); + /// println!("cargo:rustc-cfg={}", flag); + /// } + /// + pub fn rustc_flags(&self) -> Vec { + self.extensions + .extensions() + .iter() + .map(|e| format!("riscv{e}")) + .collect::>() + } + + /// Returns the LLVM base ISA for the given RISC-V target. + pub fn llvm_base_isa(&self) -> String { + match (self.width, self.extensions.base_extension()) { + (Width::W32, Some(Extension::I)) => String::from("rv32i"), + (Width::W32, Some(Extension::E)) => String::from("rv32e"), + (Width::W64, Some(Extension::I)) => String::from("rv64i"), + (Width::W64, Some(Extension::E)) => String::from("rv64e"), + (_, None) => panic!("RISC-V target must have a base extension"), + _ => panic!("LLVM does not support this base ISA"), + } + } + + /// Returns the arch code to patch LLVM spurious errors. + /// + /// # Note + /// + /// This is a provisional patch and is limited to work for the riscv-rt crate only. + /// + /// # Related issues + /// + /// - https://github.com/rust-embedded/riscv/issues/175 + /// - https://github.com/rust-lang/rust/issues/80608 + /// - https://github.com/llvm/llvm-project/issues/61991 + pub fn llvm_arch_patch(&self) -> String { + let mut patch = self.llvm_base_isa(); + if self.extensions.contains(&Extension::M) { + patch.push('m'); + } + if self.extensions.contains(&Extension::F) { + patch.push('f'); + } + if self.extensions.contains(&Extension::D) { + patch.push('d'); + } + patch + } + + /// Returns the width of the RISC-V architecture. + pub fn width(&self) -> Width { + self.width + } + + /// Returns the base extension of the RISC-V architecture (if any). + pub fn base_extension(&self) -> Option { + self.extensions.base_extension() + } +} + +impl<'a> TryFrom> for RiscvTarget { + type Error = Error<'a>; + + fn try_from(triple: TargetTriple<'a>) -> Result { + match triple.arch.strip_prefix("riscv") { + Some(arch) => { + match arch + .find(|c: char| !c.is_ascii_digit()) + .unwrap_or(arch.len()) + { + 0 => Err(Error::InvalidArch(arch)), + digit_end => { + let (width_str, extensions_str) = arch.split_at(digit_end); + let width = width_str.parse::().unwrap().try_into()?; + let extensions = extensions_str.try_into()?; + Ok(Self { width, extensions }) + } + } + } + None => Err(Error::InvalidArch(triple.arch)), + } + } +} + +#[cfg(test)] +mod test { + #[test] + fn test_parse_target() { + let target = "riscv32imac-unknown-none-elf"; + let cargo_flags = "target-feature=+m,-a,+f"; + let target = super::RiscvTarget::build(target, cargo_flags).unwrap(); + let rustc_flags = target.rustc_flags(); + assert_eq!(rustc_flags, vec!["riscvi", "riscvm", "riscvf", "riscvc"]); + } +}