-
Notifications
You must be signed in to change notification settings - Fork 13k
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
Use a C-safe return type for __rust_[ui]128_*
overflowing intrinsics
#134338
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -322,36 +322,26 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { | |
}, | ||
} | ||
} else { | ||
match new_kind { | ||
Int(I128) | Uint(U128) => { | ||
let func_name = match oop { | ||
OverflowOp::Add => match new_kind { | ||
Int(I128) => "__rust_i128_addo", | ||
Uint(U128) => "__rust_u128_addo", | ||
_ => unreachable!(), | ||
}, | ||
OverflowOp::Sub => match new_kind { | ||
Int(I128) => "__rust_i128_subo", | ||
Uint(U128) => "__rust_u128_subo", | ||
_ => unreachable!(), | ||
}, | ||
OverflowOp::Mul => match new_kind { | ||
Int(I128) => "__rust_i128_mulo", // TODO(antoyo): use __muloti4d instead? | ||
Uint(U128) => "__rust_u128_mulo", | ||
_ => unreachable!(), | ||
}, | ||
}; | ||
return self.operation_with_overflow(func_name, lhs, rhs); | ||
} | ||
_ => match oop { | ||
OverflowOp::Mul => match new_kind { | ||
Int(I32) => "__mulosi4", | ||
Int(I64) => "__mulodi4", | ||
_ => unreachable!(), | ||
}, | ||
_ => unimplemented!("overflow operation for {:?}", new_kind), | ||
let (func_name, width) = match oop { | ||
OverflowOp::Add => match new_kind { | ||
Int(I128) => ("__rust_i128_addo", 128), | ||
Uint(U128) => ("__rust_u128_addo", 128), | ||
_ => unreachable!(), | ||
}, | ||
} | ||
OverflowOp::Sub => match new_kind { | ||
Int(I128) => ("__rust_i128_subo", 128), | ||
Uint(U128) => ("__rust_u128_subo", 128), | ||
_ => unreachable!(), | ||
}, | ||
OverflowOp::Mul => match new_kind { | ||
Int(I32) => ("__mulosi4", 32), | ||
Int(I64) => ("__mulodi4", 64), | ||
Int(I128) => ("__rust_i128_mulo", 128), // TODO(antoyo): use __muloti4d instead? | ||
Uint(U128) => ("__rust_u128_mulo", 128), | ||
_ => unreachable!(), | ||
}, | ||
}; | ||
return self.operation_with_overflow(func_name, lhs, rhs, width); | ||
}; | ||
|
||
let intrinsic = self.context.get_builtin_function(name); | ||
|
@@ -364,80 +354,87 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { | |
(res.dereference(self.location).to_rvalue(), overflow) | ||
} | ||
|
||
/// Non-`__builtin_*` overflow operations with a `fn(T, T, &mut i32) -> T` signature. | ||
pub fn operation_with_overflow( | ||
&self, | ||
func_name: &str, | ||
lhs: RValue<'gcc>, | ||
rhs: RValue<'gcc>, | ||
width: u64, | ||
) -> (RValue<'gcc>, RValue<'gcc>) { | ||
let a_type = lhs.get_type(); | ||
let b_type = rhs.get_type(); | ||
debug_assert!(a_type.dyncast_array().is_some()); | ||
debug_assert!(b_type.dyncast_array().is_some()); | ||
let overflow_type = self.i32_type; | ||
let overflow_param_type = overflow_type.make_pointer(); | ||
let res_type = a_type; | ||
|
||
let overflow_value = | ||
self.current_func().new_local(self.location, overflow_type, "overflow"); | ||
let overflow_addr = overflow_value.get_address(self.location); | ||
|
||
let param_a = self.context.new_parameter(self.location, a_type, "a"); | ||
let param_b = self.context.new_parameter(self.location, b_type, "b"); | ||
let result_field = self.context.new_field(self.location, a_type, "result"); | ||
let overflow_field = self.context.new_field(self.location, self.bool_type, "overflow"); | ||
|
||
let ret_ty = Ty::new_tup(self.tcx, &[self.tcx.types.i128, self.tcx.types.bool]); | ||
let param_overflow = | ||
self.context.new_parameter(self.location, overflow_param_type, "overflow"); | ||
|
||
let a_elem_type = a_type.dyncast_array().expect("non-array a value"); | ||
debug_assert!(a_elem_type.is_integral()); | ||
let res_ty = match width { | ||
32 => self.tcx.types.i32, | ||
64 => self.tcx.types.i64, | ||
128 => self.tcx.types.i128, | ||
_ => unreachable!("unexpected integer size"), | ||
}; | ||
Comment on lines
+384
to
+389
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was there a reason to do that instead of reusing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, sorry I forgot that you now have multiple types to handle here and not only 128-bit integers. |
||
let layout = self | ||
.tcx | ||
.layout_of(ty::TypingEnv::fully_monomorphized().as_query_input(ret_ty)) | ||
.layout_of(ty::TypingEnv::fully_monomorphized().as_query_input(res_ty)) | ||
.unwrap(); | ||
|
||
let arg_abi = ArgAbi { layout, mode: PassMode::Direct(ArgAttributes::new()) }; | ||
let mut fn_abi = FnAbi { | ||
args: vec![arg_abi.clone(), arg_abi.clone()].into_boxed_slice(), | ||
args: vec![arg_abi.clone(), arg_abi.clone(), arg_abi.clone()].into_boxed_slice(), | ||
ret: arg_abi, | ||
c_variadic: false, | ||
fixed_count: 2, | ||
fixed_count: 3, | ||
conv: Conv::C, | ||
can_unwind: false, | ||
}; | ||
fn_abi.adjust_for_foreign_abi(self.cx, spec::abi::Abi::C { unwind: false }).unwrap(); | ||
|
||
let indirect = matches!(fn_abi.ret.mode, PassMode::Indirect { .. }); | ||
|
||
let return_type = self | ||
.context | ||
.new_struct_type(self.location, "result_overflow", &[result_field, overflow_field]); | ||
let result = if indirect { | ||
let return_value = | ||
self.current_func().new_local(self.location, return_type.as_type(), "return_value"); | ||
let return_param_type = return_type.as_type().make_pointer(); | ||
let return_param = | ||
self.context.new_parameter(self.location, return_param_type, "return_value"); | ||
let ret_indirect = matches!(fn_abi.ret.mode, PassMode::Indirect { .. }); | ||
|
||
let result = if ret_indirect { | ||
let res_value = self.current_func().new_local(self.location, res_type, "result_value"); | ||
let res_addr = res_value.get_address(self.location); | ||
let res_param_type = res_type.make_pointer(); | ||
let param_res = self.context.new_parameter(self.location, res_param_type, "result"); | ||
|
||
let func = self.context.new_function( | ||
self.location, | ||
FunctionType::Extern, | ||
self.type_void(), | ||
&[return_param, param_a, param_b], | ||
&[param_res, param_a, param_b, param_overflow], | ||
func_name, | ||
false, | ||
); | ||
self.llbb().add_eval( | ||
self.location, | ||
self.context.new_call(self.location, func, &[ | ||
return_value.get_address(self.location), | ||
lhs, | ||
rhs, | ||
]), | ||
); | ||
return_value.to_rvalue() | ||
let _void = | ||
self.context.new_call(self.location, func, &[res_addr, lhs, rhs, overflow_addr]); | ||
res_value.to_rvalue() | ||
} else { | ||
let func = self.context.new_function( | ||
self.location, | ||
FunctionType::Extern, | ||
return_type.as_type(), | ||
&[param_a, param_b], | ||
res_type, | ||
&[param_a, param_b, param_overflow], | ||
func_name, | ||
false, | ||
); | ||
self.context.new_call(self.location, func, &[lhs, rhs]) | ||
self.context.new_call(self.location, func, &[lhs, rhs, overflow_addr]) | ||
}; | ||
let overflow = result.access_field(self.location, overflow_field); | ||
let int_result = result.access_field(self.location, result_field); | ||
(int_result, overflow) | ||
|
||
(result, self.context.new_cast(self.location, overflow_value, self.bool_type).to_rvalue()) | ||
} | ||
|
||
pub fn gcc_icmp( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I can see, this change doesn't look right as this will fall to calling
self.overflow_call()
below which expects that the overflow flag is returned from the function call (like these GCC intrinsics) whereas your change seems to return the integer result and not the overflow flag.My guess is that you'll need to keep this special handling and change the method
operation_with_overflow
accordingly as these intrinsics are also used insrc/intrinsic/mod.rs
.AFAIK, the only related tests that run in this CI are those lines.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for taking a look. Doesn't this block currently sometimes use
__mulosi4
without an early turn? After the change tocompiler_builtins
,__rust_*128_o
should be consistent with the__mulo*i4
functions.Is there any kind of codegen/asm test that should be added here? Or is the existing test sufficient once CI gets that far.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, it looks like there might be a bug in here.
What do you mean? There's more tests that are ran in the repo of rustc_codegen_gcc itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed this so
mulo
and the__rust
functions should now be taking the same codepath, still have to update the rest.I am just wondering whether I should add another test anywhere to verify the new behavior, especially if
__mulosi4
may have been called incorrectly before. And if so, what format/location this should be (for LLVM I'd probably add a codegen test, but I'm not sure what the equivalent would be here).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was expecting that there would be UI tests for these, so that every backend could benefit from them.
Do they have to be codegen tests?
There are integer tests here for the GCC codegen, but they are not ran in the Rust CI, as far as I know, so if it's too much of a pain, we can open an issue to remind me to add tests for this in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at https://rust.godbolt.org/z/eWfPK6rGW it seems like GCC lowers these operations to assembly (LLVM as well), which possibly explains why the test was not failing before even if the
__mulosi4
call had a bug. I am not very familiar with this backend but I take it GCC intercepts the call to__builtin_sub_overflow
and adjusts for the correct size?If it doesn't emit this call then I am not sure what kind of test would work here, but can add something if you have any ideas. I updated the code and it should be ready for review, it builds but have not been able to run tests.
Agreed that we should have something end-to-end here, possibly reusing the test generators from compiler-builtins/libm. I think the status quo assumption is that we don't need to test the lowering too heavily since that is the backend's responsibility, but it would still be good to have something systematic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. The doc mentions:
I reviewed your code: it looks good, thanks!
This test does call
overflowing_mul
, so I'm not sure why it wasn't tested there.Any ideas?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In order to hit the branch where
__mulosi
/__mulodi
gets emitted, theself.is_native_int_type(lhs.get_type())
condition has to fail fori32
ori64
- I'm guessing there probably aren't many platforms where that would happen. The calls to__rust_i128_*
were correct before but it looks likeis_native_int_type
has to be returning true fori128
too, at least on x86, since that gets asm lowering https://rust.godbolt.org/z/jPfMK5vhv. I take it maybe thegcc-13-without-int128
CI jobs in the cg_gcc repo would cover this?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that CI job uses a GCC where 128-bit integers are disabled to test this indeed.
Right, that must be why it wasn't tested. But I think
i64
is supported on all platforms, so I'll open an issue to check that.