Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions compiler/rustc_codegen_gcc/src/asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,9 @@ fn reg_class_to_gcc(reg_class: InlineAsmRegClass) -> &'static str {
unreachable!("clobber-only")
}
InlineAsmRegClass::Sparc(SparcInlineAsmRegClass::reg) => "r",
InlineAsmRegClass::Sparc(SparcInlineAsmRegClass::freg) => "f",
InlineAsmRegClass::Sparc(SparcInlineAsmRegClass::dreg) => "e",
InlineAsmRegClass::Sparc(SparcInlineAsmRegClass::qreg) => "e",
InlineAsmRegClass::Sparc(SparcInlineAsmRegClass::yreg) => unreachable!("clobber-only"),
InlineAsmRegClass::Err => unreachable!(),
}
Expand Down Expand Up @@ -896,6 +899,9 @@ fn dummy_output_type<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, reg: InlineAsmRegCl
unreachable!("clobber-only")
}
InlineAsmRegClass::Sparc(SparcInlineAsmRegClass::reg) => cx.type_i32(),
InlineAsmRegClass::Sparc(SparcInlineAsmRegClass::freg) => cx.type_f32(),
InlineAsmRegClass::Sparc(SparcInlineAsmRegClass::dreg) => cx.type_f64(),
InlineAsmRegClass::Sparc(SparcInlineAsmRegClass::qreg) => cx.type_f128(),
InlineAsmRegClass::Sparc(SparcInlineAsmRegClass::yreg) => unreachable!("clobber-only"),
InlineAsmRegClass::Msp430(Msp430InlineAsmRegClass::reg) => cx.type_i16(),
InlineAsmRegClass::M68k(M68kInlineAsmRegClass::reg) => cx.type_i32(),
Expand Down
15 changes: 15 additions & 0 deletions compiler/rustc_codegen_llvm/src/asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,16 @@ fn reg_to_llvm(reg: InlineAsmRegOrRegClass, layout: Option<&TyAndLayout<'_>>) ->
} else if reg == InlineAsmReg::Arm(ArmInlineAsmReg::r14) {
// LLVM doesn't recognize r14
"{lr}".to_string()
} else if let InlineAsmReg::Sparc(reg) = reg
&& let Some(num) = reg.dreg_number()
{
// LLVM numbers d registers sequentially (d0 => d0, d2 => d1, d4 => d2 etc.)
format!("{{d{}}}", num / 2)
} else if let InlineAsmReg::Sparc(reg) = reg
&& let Some(num) = reg.qreg_number()
{
// LLVM numbers q registers sequentially (q0 => q0, q4 => q1, q8 => q2 etc.)
format!("{{q{}}}", num / 4)
} else {
format!("{{{}}}", reg.name())
}
Expand Down Expand Up @@ -819,6 +829,8 @@ fn reg_to_llvm(reg: InlineAsmRegOrRegClass, layout: Option<&TyAndLayout<'_>>) ->
unreachable!("clobber-only")
}
Sparc(SparcInlineAsmRegClass::reg) => "r",
Sparc(SparcInlineAsmRegClass::freg) => "f",
Sparc(SparcInlineAsmRegClass::dreg | SparcInlineAsmRegClass::qreg) => "e",
Sparc(SparcInlineAsmRegClass::yreg) => unreachable!("clobber-only"),
Msp430(Msp430InlineAsmRegClass::reg) => "r",
M68k(M68kInlineAsmRegClass::reg) => "r",
Expand Down Expand Up @@ -1042,6 +1054,9 @@ fn dummy_output_type<'ll>(cx: &CodegenCx<'ll, '_>, reg: InlineAsmRegClass) -> &'
unreachable!("clobber-only")
}
Sparc(SparcInlineAsmRegClass::reg) => cx.type_i32(),
Sparc(SparcInlineAsmRegClass::freg) => cx.type_f32(),
Sparc(SparcInlineAsmRegClass::dreg) => cx.type_f64(),
Sparc(SparcInlineAsmRegClass::qreg) => cx.type_f128(),
Sparc(SparcInlineAsmRegClass::yreg) => unreachable!("clobber-only"),
Msp430(Msp430InlineAsmRegClass::reg) => cx.type_i16(),
M68k(M68kInlineAsmRegClass::reg) => cx.type_i32(),
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2306,6 +2306,7 @@ symbols! {
usize_legacy_mod,
v1,
v8plus,
v9,
va_arg,
va_arg_safe,
va_copy,
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_target/src/asm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ impl InlineAsmReg {
Self::LoongArch(r) => r.overlapping_regs(|r| cb(Self::LoongArch(r))),
Self::Mips(_) => cb(self),
Self::S390x(r) => r.overlapping_regs(|r| cb(Self::S390x(r))),
Self::Sparc(_) => cb(self),
Self::Sparc(r) => r.overlapping_regs(|r| cb(Self::Sparc(r))),
Self::Xtensa(_) => cb(self),
Self::Bpf(r) => r.overlapping_regs(|r| cb(Self::Bpf(r))),
Self::Avr(r) => r.overlapping_regs(|r| cb(Self::Avr(r))),
Expand Down
203 changes: 202 additions & 1 deletion compiler/rustc_target/src/asm/sparc.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
use std::fmt;

use rustc_data_structures::fx::FxIndexSet;
use rustc_span::Symbol;
use rustc_span::{Symbol, sym};

use super::{InlineAsmArch, InlineAsmType, ModifierInfo};
use crate::spec::{RelocModel, Target};

def_reg_class! {
Sparc SparcInlineAsmRegClass {
reg,
freg,
dreg,
qreg,
yreg,
}
}
Expand Down Expand Up @@ -51,6 +54,9 @@ impl SparcInlineAsmRegClass {
types! { _: I8, I16, I32, I64; }
}
}
Self::freg => types! { _: F32; },
Self::dreg => types! { _: F64; },
Self::qreg => types! { _: F128; },
Self::yreg => &[],
}
}
Expand All @@ -75,6 +81,23 @@ fn reserved_g5(
}
}

fn v9_only(
_arch: InlineAsmArch,
_reloc_model: RelocModel,
target_features: &FxIndexSet<Symbol>,
_target: &Target,
_is_clobber: bool,
) -> Result<(), &'static str> {
// FIXME: This is the what GCC/LLVM currently use to limit access to upper-half registers, but
// it's unclear whether this is the correct behaviour. See the discussion around
// https://github.com/rust-lang/rust/pull/160949#discussion_r3806194355.
if !target_features.contains(&sym::v9) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume that even with explicit SPARC ABI modeling (#160562) we'll want to check the target feature here, not the ABI?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. v9 can currently be enabled when v8plus is disabled.

Err("floating point registers in the upper half can only be used on SPARCv9")
} else {
Ok(())
}
}

def_regs! {
Sparc SparcInlineAsmReg SparcInlineAsmRegClass {
// FIXME:
Expand Down Expand Up @@ -107,6 +130,86 @@ def_regs! {
r27: reg = ["r27", "i3"], // % reserved_i3
r28: reg = ["r28", "i4"], // % reserved_i4
r29: reg = ["r29", "i5"], // % reserved_i5
f0: freg = ["f0"],
f1: freg = ["f1"],
f2: freg = ["f2"],
f3: freg = ["f3"],
f4: freg = ["f4"],
f5: freg = ["f5"],
f6: freg = ["f6"],
f7: freg = ["f7"],
f8: freg = ["f8"],
f9: freg = ["f9"],
f10: freg = ["f10"],
f11: freg = ["f11"],
f12: freg = ["f12"],
f13: freg = ["f13"],
f14: freg = ["f14"],
f15: freg = ["f15"],
f16: freg = ["f16"],
f17: freg = ["f17"],
f18: freg = ["f18"],
f19: freg = ["f19"],
f20: freg = ["f20"],
f21: freg = ["f21"],
f22: freg = ["f22"],
f23: freg = ["f23"],
f24: freg = ["f24"],
f25: freg = ["f25"],
f26: freg = ["f26"],
f27: freg = ["f27"],
f28: freg = ["f28"],
f29: freg = ["f29"],
f30: freg = ["f30"],
f31: freg = ["f31"],
d0: dreg = ["d0"],
d2: dreg = ["d2"],
d4: dreg = ["d4"],
d6: dreg = ["d6"],
d8: dreg = ["d8"],
d10: dreg = ["d10"],
d12: dreg = ["d12"],
d14: dreg = ["d14"],
d16: dreg = ["d16"],
d18: dreg = ["d18"],
d20: dreg = ["d20"],
d22: dreg = ["d22"],
d24: dreg = ["d24"],
d26: dreg = ["d26"],
d28: dreg = ["d28"],
d30: dreg = ["d30"],
d32: dreg = ["d32"] % v9_only,

@taiki-e taiki-e Aug 14, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, on a 32-bit SPARC, to use registers that are not available in v8 but are available in v9, the v8plus is usually required.
(IIRC, LLVM has not yet implemented the register-related features of v8plus, so maybe we need to look into GCC's behavior.)

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Rust v8plus target (sparc-unknown-linux-gnu) currently enables the v9 target feature as v8plus requires a v9 CPU, so checking if the v9 feature is enabled appears to be correct AFAICT (this is shown in the sparc-types.rs test).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue I'm concerned about is that when compiling sparc-unknown-none-elf (which does not use the v8plus ABI) with the -C target-cpu=v9, it is able to access registers that are unavailable in v8.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's some relevant discussion in #t-compiler/major changes > Expose `target_abi = "v8plus"` on sparc-… compiler-team#1028 @ 💬. My current understanding of v8plus is that it is just the name of the standard way to use v9 CPU features in 32-bit SPARC applications in a way that is backwards compatible; is this correct, or is there another method of running 32-bit SPARC applications with v9 CPU features? LLVM will happily allocate the upper-half floating point registers with just the v9 feature enabled (of course, that doesn't mean LLVM is correct). If v8plus is just "32-bit SPARC + v9" then it seems like we should just automatically enable it when the v9 CPU feature is enabled on 32-bit SPARC. Alternatively, given the differing ELF architectures, we may decide to just ban enabling v9/v8plus on targets that don't have them enabled by default.

In summary, I'm not sure it makes sense to enable v9 on 32-bit SPARC without enabling v8plus, so I think the solution here might be improving the SPARC target feature handling rather than changing the register feature requirements.

cc SPARC target feature tracking issue: #132783

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having given this some thought, I think the main thing I'm unsure about here is: what's the ABI of enabling the v9 target feature without v8plus on 32-bit SPARC? LLVM seems to think v9 alone gives access to the extra registers, but the only specification I can find is for V8+. Are you saying that without v8plus LLVM shouldn't be able to use the new registers, but can use the new instructions?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @koachan who does sparc things in LLVM

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LLVM seems to think v9 alone gives access to the extra registers

GCC seems to think alike in this aspect, at least? -mcpu=v9 alone gives the extra FP registers already; OTOH to use the extended-width integer registers you do need -mv8plus in, see https://godbolt.org/z/ndGEfz64E

With LLVM itself, if memory serves me right, then aside from the extra FP registers and setting a couple ELF flags, the v8plus feature flag is largely unimplemented at the moment...
My personal opinion is that if LLVM were to implement anything further then it's best to try to match GCC's behavior, but I'm not entirely sure.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left it checking v9 given that that matches current GCC and LLVM and both SPARC target features and SPARC inline ASM are currently unstable, and I've left a couple of FIXMEs so to ensure that we make a deliberate decision on this before stabilisation.

d34: dreg = ["d34"] % v9_only,
d36: dreg = ["d36"] % v9_only,
d38: dreg = ["d38"] % v9_only,
d40: dreg = ["d40"] % v9_only,
d42: dreg = ["d42"] % v9_only,
d44: dreg = ["d44"] % v9_only,
d46: dreg = ["d46"] % v9_only,
d48: dreg = ["d48"] % v9_only,
d50: dreg = ["d50"] % v9_only,
d52: dreg = ["d52"] % v9_only,
d54: dreg = ["d54"] % v9_only,
d56: dreg = ["d56"] % v9_only,
d58: dreg = ["d58"] % v9_only,
d60: dreg = ["d60"] % v9_only,
d62: dreg = ["d62"] % v9_only,
q0: qreg = ["q0"],
q4: qreg = ["q4"],
q8: qreg = ["q8"],
q12: qreg = ["q12"],
q16: qreg = ["q16"],
q20: qreg = ["q20"],
q24: qreg = ["q24"],
q28: qreg = ["q28"],
q32: qreg = ["q32"] % v9_only,
q36: qreg = ["q36"] % v9_only,
q40: qreg = ["q40"] % v9_only,
q44: qreg = ["q44"] % v9_only,
q48: qreg = ["q48"] % v9_only,
q52: qreg = ["q52"] % v9_only,
q56: qreg = ["q56"] % v9_only,
q60: qreg = ["q60"] % v9_only,
y: yreg = ["y"],
#error = ["r0", "g0"] =>
"g0 is always zero and cannot be used as an operand for inline asm",
Expand Down Expand Up @@ -135,4 +238,102 @@ impl SparcInlineAsmReg {
) -> fmt::Result {
write!(out, "%{}", self.name())
}

pub fn overlapping_regs(self, mut cb: impl FnMut(SparcInlineAsmReg)) {
cb(self);

macro_rules! reg_conflicts {
(
$(
$q:ident : $d0:ident $d1:ident : $f0:ident $f1:ident $f2:ident $f3:ident
),*;
$(
$q_high:ident : $d0_high:ident $d1_high:ident
),*;
) => {
match self {
$(
Self::$q => {
cb(Self::$d0);
cb(Self::$d1);
cb(Self::$f0);
cb(Self::$f1);
cb(Self::$f2);
cb(Self::$f3);
}
Self::$d0 => {
cb(Self::$q);
cb(Self::$f0);
cb(Self::$f1);
}
Self::$d1 => {
cb(Self::$q);
cb(Self::$f2);
cb(Self::$f3);
}
Self::$f0 | Self::$f1 => {
cb(Self::$q);
cb(Self::$d0);
}
Self::$f2 | Self::$f3 => {
cb(Self::$q);
cb(Self::$d1);
}
)*
$(
Self::$q_high => {
cb(Self::$d0_high);
cb(Self::$d1_high);
}
Self::$d0_high | Self::$d1_high => {
cb(Self::$q_high);
}
)*
_ => {},
}
};
}

// SPARC's floating-point register file is interesting in that it can be
// viewed as 16 128-bit registers, 32 64-bit registers or 32 32-bit
// registers. Because these views overlap, the registers of different
// widths will conflict (e.g. d0 overlaps with f0 and f1, and q1
// overlaps with d2 and d3).
//
// See section 3.1.2 of The SPARC Architecture Manual: Version 9 for details.
reg_conflicts! {
q0 : d0 d2 : f0 f1 f2 f3,
q4 : d4 d6 : f4 f5 f6 f7,
q8 : d8 d10 : f8 f9 f10 f11,
q12 : d12 d14 : f12 f13 f14 f15,
q16 : d16 d18 : f16 f17 f18 f19,
q20 : d20 d22 : f20 f21 f22 f23,
q24 : d24 d26 : f24 f25 f26 f27,
q28 : d28 d30 : f28 f29 f30 f31;
q32 : d32 d34,
q36 : d36 d38,
q40 : d40 d42,
q44 : d44 d46,
q48 : d48 d50,
q52 : d52 d54,
q56 : d56 d58,
q60 : d60 d62;
}
}

pub fn dreg_number(self) -> Option<u32> {
if self >= Self::d0 && self <= Self::d62 {
Some((self as u32 - Self::d0 as u32) * 2)
} else {
None
}
}

pub fn qreg_number(self) -> Option<u32> {
if self >= Self::q0 && self <= Self::q60 {
Some((self as u32 - Self::q0 as u32) * 4)
} else {
None
}
}
}
2 changes: 2 additions & 0 deletions compiler/rustc_target/src/target_features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,8 @@ const SPARC_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
// tidy-alphabetical-start
("leoncasa", Unstable(sym::sparc_target_feature), &[]),
("v8plus", Unstable(sym::sparc_target_feature), &[]),
// FIXME: It's unclear what this feature means when `v8plus` is disabled on 32-bit SPARC. See
// the discussion around https://github.com/rust-lang/rust/pull/160949#discussion_r3806194355.
("v9", Unstable(sym::sparc_target_feature), &[]),
// tidy-alphabetical-end
];
Expand Down
Loading
Loading