Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

more complete sparc64 ABI fix for aggregates with floating point members #94216

Merged
merged 3 commits into from
Feb 28, 2022
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
276 changes: 176 additions & 100 deletions compiler/rustc_target/src/abi/call/sparc64.rs
Original file line number Diff line number Diff line change
@@ -1,136 +1,212 @@
// FIXME: This needs an audit for correctness and completeness.

use crate::abi::call::{
ArgAbi, ArgAttribute, ArgAttributes, ArgExtension, CastTarget, FnAbi, Reg, RegKind, Uniform,
ArgAbi, ArgAttribute, ArgAttributes, ArgExtension, CastTarget, FnAbi, Reg, Uniform,
};
use crate::abi::{self, HasDataLayout, Size, TyAbiInterface};
use crate::abi::{self, HasDataLayout, Scalar, Size, TyAbiInterface, TyAndLayout};

#[derive(Clone, Debug)]
pub struct Sdata {
pub prefix: [Option<Reg>; 8],
pub prefix_index: usize,
pub last_offset: Size,
pub has_float: bool,
pub arg_attribute: ArgAttribute,
}

fn is_homogeneous_aggregate<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>) -> Option<Uniform>
fn arg_scalar<C>(cx: &C, scalar: &Scalar, offset: Size, mut data: Sdata) -> Sdata
where
Ty: TyAbiInterface<'a, C> + Copy,
C: HasDataLayout,
{
arg.layout.homogeneous_aggregate(cx).ok().and_then(|ha| ha.unit()).and_then(|unit| {
// Ensure we have at most eight uniquely addressable members.
if arg.layout.size > unit.size.checked_mul(8, cx).unwrap() {
return None;
let dl = cx.data_layout();

if scalar.value != abi::F32 && scalar.value != abi::F64 {
return data;
}

data.has_float = true;

if !data.last_offset.is_aligned(dl.f64_align.abi) && data.last_offset < offset {
if data.prefix_index == data.prefix.len() {
return data;
}
data.prefix[data.prefix_index] = Some(Reg::i32());
data.prefix_index += 1;
data.last_offset = data.last_offset + Reg::i32().size;
}

let valid_unit = match unit.kind {
RegKind::Integer => false,
RegKind::Float => false,
RegKind::Vector => arg.layout.size.bits() == 128,
};
for _ in 0..((offset - data.last_offset).bits() / 64)
.min((data.prefix.len() - data.prefix_index) as u64)
{
data.prefix[data.prefix_index] = Some(Reg::i64());
data.prefix_index += 1;
data.last_offset = data.last_offset + Reg::i64().size;
}

valid_unit.then_some(Uniform { unit, total: arg.layout.size })
})
if data.last_offset < offset {
if data.prefix_index == data.prefix.len() {
return data;
}
data.prefix[data.prefix_index] = Some(Reg::i32());
data.prefix_index += 1;
data.last_offset = data.last_offset + Reg::i32().size;
}

if data.prefix_index == data.prefix.len() {
return data;
}

if scalar.value == abi::F32 {
data.arg_attribute = ArgAttribute::InReg;
data.prefix[data.prefix_index] = Some(Reg::f32());
data.last_offset = offset + Reg::f32().size;
} else {
data.prefix[data.prefix_index] = Some(Reg::f64());
data.last_offset = offset + Reg::f64().size;
}
data.prefix_index += 1;
return data;
}

fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>, in_registers_max: Size)
fn arg_scalar_pair<C>(
cx: &C,
scalar1: &Scalar,
scalar2: &Scalar,
mut offset: Size,
mut data: Sdata,
) -> Sdata
where
Ty: TyAbiInterface<'a, C> + Copy,
C: HasDataLayout,
{
if !arg.layout.is_aggregate() {
arg.extend_integer_width_to(64);
return;
data = arg_scalar(cx, &scalar1, offset, data);
if scalar1.value == abi::F32 {
offset += Reg::f32().size;
} else if scalar2.value == abi::F64 {
offset += Reg::f64().size;
} else if let abi::Int(i, _signed) = scalar1.value {
offset += i.size();
} else if scalar1.value == abi::Pointer {
offset = offset + Reg::i64().size;
}

// This doesn't intentionally handle structures with floats which needs
// special care below.
if let Some(uniform) = is_homogeneous_aggregate(cx, arg) {
arg.cast_to(uniform);
return;
if (offset.raw % 4) != 0 && (scalar2.value == abi::F32 || scalar2.value == abi::F64) {
offset.raw += 4 - (offset.raw % 4);
}
Comment on lines +82 to +94
Copy link
Member

Choose a reason for hiding this comment

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

There's three issues here:

  • Abi::ScalarPair should not be special-cased in call ABIs - it's just Abi::Aggregate with extra Rust-only optimizations tacked on (none of the other issues matter if the parse_structure codepath is taken)
  • this is not how one computes the offset of the second scalar for an Abi::ScalarPair (scalar1.size(cx).align_to(scalar2.align(cx).abi) is more or less the typical formula, anything else risks being wrong for certain scalars)
  • else if scalar2.value == abi::F64 is probably a typo? certainly seems like the intention is to get the size of scalar1 (which is just scalar1.size(cx))

As it stands, I believe this code passes (u128, f64) incorrectly (thanks to alignment, I can't think of anything else that would be mismatched), so it was probably unobserved for that reason.

data = arg_scalar(cx, &scalar2, offset, data);
return data;
}

fn parse_structure<'a, Ty, C>(
cx: &C,
layout: TyAndLayout<'a, Ty>,
mut data: Sdata,
mut offset: Size,
) -> Sdata
where
Ty: TyAbiInterface<'a, C> + Copy,
C: HasDataLayout,
{
if let abi::FieldsShape::Union(_) = layout.fields {
return data;
}

if let abi::FieldsShape::Arbitrary { .. } = arg.layout.fields {
let dl = cx.data_layout();
let size = arg.layout.size;
let mut prefix = [None; 8];
let mut prefix_index = 0;
let mut last_offset = Size::ZERO;
let mut has_float = false;
let mut arg_attribute = ArgAttribute::default();

for i in 0..arg.layout.fields.count() {
let field = arg.layout.field(cx, i);
let offset = arg.layout.fields.offset(i);

if let abi::Abi::Scalar(scalar) = &field.abi {
if scalar.value == abi::F32 || scalar.value == abi::F64 {
has_float = true;

if !last_offset.is_aligned(dl.f64_align.abi) && last_offset < offset {
if prefix_index == prefix.len() {
break;
}
prefix[prefix_index] = Some(Reg::i32());
prefix_index += 1;
last_offset = last_offset + Reg::i32().size;
}

for _ in 0..((offset - last_offset).bits() / 64)
.min((prefix.len() - prefix_index) as u64)
{
prefix[prefix_index] = Some(Reg::i64());
prefix_index += 1;
last_offset = last_offset + Reg::i64().size;
}

if last_offset < offset {
if prefix_index == prefix.len() {
break;
}
prefix[prefix_index] = Some(Reg::i32());
prefix_index += 1;
last_offset = last_offset + Reg::i32().size;
}

if prefix_index == prefix.len() {
break;
}

if scalar.value == abi::F32 {
arg_attribute = ArgAttribute::InReg;
prefix[prefix_index] = Some(Reg::f32());
last_offset = offset + Reg::f32().size;
} else {
prefix[prefix_index] = Some(Reg::f64());
last_offset = offset + Reg::f64().size;
}
prefix_index += 1;
match layout.abi {
abi::Abi::Scalar(scalar) => {
data = arg_scalar(cx, &scalar, offset, data);
}
abi::Abi::Aggregate { .. } => {
for i in 0..layout.fields.count().clone() {
if offset < layout.fields.offset(i) {
offset = layout.fields.offset(i);
}
Comment on lines +118 to 121
Copy link
Member

Choose a reason for hiding this comment

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

This is a silent failure (it will cause data corruption AFAICT). If the fields aren't in ascending order, this is a non-C-friendly type and you have to either ICE (which is arguably unfriendly), or better just fall back to "this cannot be passed in any optimized way".

Sadly SPARC64 seems to be one of the architectures unfortunate enough to use "turn struct into as many immediate arguments as needed", instead of byval, for on-stack passing.

But still, you could use something similar to the Union case handled a bit above.

Actually, wait, this is wronger than I thought, it doesn't add field offsets to the enclosing struct's offset, just replaces it altogether.

There's no way the testcases added in this PR could work, I think it's just hitting all the silent failure modes around here and last_offset and accidentally causing the right output...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for looking at it! I'm sure there are problems with my fix.
I was testing it with: https://github.com/psumbera/rust-sparc64-abi-tests
So maybe you can come with sample code which will not work.
Or If you are willing to help with your expertise it I'm ready to provide SPARC testing.

data = parse_structure(cx, layout.field(cx, i).clone(), data.clone(), offset);
}
}

if has_float && arg.layout.size <= in_registers_max {
let mut rest_size = size - last_offset;

if (rest_size.raw % 8) != 0 && prefix_index < prefix.len() {
prefix[prefix_index] = Some(Reg::i32());
rest_size = rest_size - Reg::i32().size;
_ => {
if let abi::Abi::ScalarPair(scalar1, scalar2) = &layout.abi {
data = arg_scalar_pair(cx, scalar1, scalar2, offset, data);
}

arg.cast_to(CastTarget {
prefix,
rest: Uniform { unit: Reg::i64(), total: rest_size },
attrs: ArgAttributes {
regular: arg_attribute,
arg_ext: ArgExtension::None,
pointee_size: Size::ZERO,
pointee_align: None,
},
});
return;
}
}

return data;
}

fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>, in_registers_max: Size)
where
Ty: TyAbiInterface<'a, C> + Copy,
C: HasDataLayout,
{
if !arg.layout.is_aggregate() {
arg.extend_integer_width_to(64);
return;
}

let total = arg.layout.size;
if total > in_registers_max {
arg.make_indirect();
return;
}

match arg.layout.fields {
abi::FieldsShape::Primitive => unreachable!(),
abi::FieldsShape::Array { .. } => {
// Arrays are passed indirectly
arg.make_indirect();
return;
}
abi::FieldsShape::Union(_) => {
// Unions and are always treated as a series of 64-bit integer chunks
}
abi::FieldsShape::Arbitrary { .. } => {
// Stuctures with floating point numbers need special care.

let mut data = parse_structure(
cx,
arg.layout.clone(),
Sdata {
prefix: [None; 8],
prefix_index: 0,
last_offset: Size::ZERO,
has_float: false,
arg_attribute: ArgAttribute::default(),
},
Size { raw: 0 },
);

if data.has_float {
// Structure { float, int, int } doesn't like to be handled like
// { float, long int }. Other way around it doesn't mind.
if data.last_offset < arg.layout.size
&& (data.last_offset.raw % 8) != 0
&& data.prefix_index < data.prefix.len()
{
data.prefix[data.prefix_index] = Some(Reg::i32());
data.prefix_index += 1;
data.last_offset += Reg::i32().size;
}

let mut rest_size = arg.layout.size - data.last_offset;
if (rest_size.raw % 8) != 0 && data.prefix_index < data.prefix.len() {
data.prefix[data.prefix_index] = Some(Reg::i32());
rest_size = rest_size - Reg::i32().size;
}

arg.cast_to(CastTarget {
prefix: data.prefix,
rest: Uniform { unit: Reg::i64(), total: rest_size },
attrs: ArgAttributes {
regular: data.arg_attribute,
arg_ext: ArgExtension::None,
pointee_size: Size::ZERO,
pointee_align: None,
},
});
return;
}
}
}

arg.cast_to(Uniform { unit: Reg::i64(), total });
}

Expand Down
20 changes: 20 additions & 0 deletions src/test/codegen/sparc-struct-abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,23 @@ pub struct FloatLongFloat {
pub extern "C" fn structfloatlongfloat() -> FloatLongFloat {
FloatLongFloat { f: 0.1, i: 123, g: 3.14 }
}

#[repr(C)]
pub struct FloatFloat {
f: f32,
g: f32,
}

#[repr(C)]
pub struct NestedStructs {
a: FloatFloat,
b: FloatFloat,
}

// CHECK: define inreg { float, float, float, float } @structnestestructs()
// CHECK-NEXT: start:
// CHECK-NEXT: ret { float, float, float, float } { float 0x3FB99999A0000000, float 0x3FF19999A0000000, float 0x40019999A0000000, float 0x400A666660000000 }
#[no_mangle]
pub extern "C" fn structnestestructs() -> NestedStructs {
NestedStructs { a: FloatFloat { f: 0.1, g: 1.1 }, b: FloatFloat { f: 2.2, g: 3.3 } }
}