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

Use a C-safe return type for __rust_[ui]128_* overflowing intrinsics #134338

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
17 changes: 11 additions & 6 deletions compiler/rustc_codegen_cranelift/Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,9 @@ dependencies = [

[[package]]
name = "log"
version = "0.4.22"
version = "0.4.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
checksum = "3d6ea2a48c204030ee31a7d7fc72c93294c92fe87ecb1789881c9543516e1a0d"

[[package]]
name = "mach2"
Expand Down Expand Up @@ -302,9 +302,9 @@ dependencies = [

[[package]]
name = "proc-macro2"
version = "1.0.92"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
Expand Down Expand Up @@ -402,9 +402,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"

[[package]]
name = "syn"
version = "2.0.95"
version = "2.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a"
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
dependencies = [
"proc-macro2",
"quote",
Expand Down Expand Up @@ -516,3 +516,8 @@ name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

[[patch.unused]]
name = "compiler_builtins"
version = "0.1.999"
source = "git+https://github.com/tgross35/compiler-builtins.git?branch=NOMERGE-overflowing-c-safe-ret-version#fbdf922da9710c01698d7b42f7e40dcb1ac66d2d"
3 changes: 3 additions & 0 deletions compiler/rustc_codegen_cranelift/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ libloading = { version = "0.8.0", optional = true }
smallvec = "1.8.1"

[patch.crates-io]
# todo: remove patch
compiler_builtins = { git = "https://github.com/tgross35/compiler-builtins.git", package = "compiler_builtins", branch = "NOMERGE-overflowing-c-safe-ret-version" }

# Uncomment to use an unreleased version of cranelift
#cranelift-codegen = { git = "https://github.com/bytecodealliance/wasmtime.git", branch = "release-28.0.0", version = "0.115.0" }
#cranelift-frontend = { git = "https://github.com/bytecodealliance/wasmtime.git", branch = "release-28.0.0", version = "0.115.0" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ index 7165c3e48af..968552ad435 100644

[dependencies]
core = { path = "../core" }
-compiler_builtins = { version = "=0.1.140", features = ['rustc-dep-of-std'] }
+compiler_builtins = { version = "=0.1.140", features = ['rustc-dep-of-std', 'no-f16-f128'] }
-compiler_builtins = { version = "0.1.140", features = ['rustc-dep-of-std'] }
+compiler_builtins = { version = "0.1.140", features = ['rustc-dep-of-std', 'no-f16-f128'] }

[dev-dependencies]
rand = { version = "0.8.5", default-features = false, features = ["alloc"] }
Expand Down
18 changes: 10 additions & 8 deletions compiler/rustc_codegen_cranelift/src/codegen_i128.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,20 +76,22 @@ pub(crate) fn maybe_codegen_mul_checked<'tcx>(
}

let is_signed = type_sign(lhs.layout().ty);

let out_ty = Ty::new_tup(fx.tcx, &[lhs.layout().ty, fx.tcx.types.bool]);
let out_place = CPlace::new_stack_slot(fx, fx.layout_of(out_ty));
let oflow_out_place = CPlace::new_stack_slot(fx, fx.layout_of(fx.tcx.types.i32));
let param_types = vec![
AbiParam::special(fx.pointer_type, ArgumentPurpose::StructReturn),
AbiParam::new(types::I128),
AbiParam::new(types::I128),
AbiParam::special(fx.pointer_type, ArgumentPurpose::Normal),
];
let args = [out_place.to_ptr().get_addr(fx), lhs.load_scalar(fx), rhs.load_scalar(fx)];
fx.lib_call(
let args = [lhs.load_scalar(fx), rhs.load_scalar(fx), oflow_out_place.to_ptr().get_addr(fx)];
let ret = fx.lib_call(
if is_signed { "__rust_i128_mulo" } else { "__rust_u128_mulo" },
param_types,
vec![],
vec![AbiParam::new(types::I128)],
&args,
);
Some(out_place.to_cvalue(fx))
let mul = ret[0];
let oflow = oflow_out_place.to_cvalue(fx).load_scalar(fx);
let oflow = clif_intcast(fx, oflow, types::I8, false);
let layout = fx.layout_of(Ty::new_tup(fx.tcx, &[lhs.layout().ty, fx.tcx.types.bool]));
Some(CValue::by_val_pair(mul, oflow, layout))
}
2 changes: 1 addition & 1 deletion compiler/rustc_codegen_cranelift/src/compiler_builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ builtin_functions! {
fn __divti3(n: i128, d: i128) -> i128;
fn __umodti3(n: u128, d: u128) -> u128;
fn __modti3(n: i128, d: i128) -> i128;
fn __rust_u128_mulo(a: u128, b: u128) -> (u128, bool);
fn __rust_u128_mulo(a: u128, b: u128, oflow: &mut i32) -> u128;

// floats
fn __floattisf(i: i128) -> f32;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ test = { path = "./sysroot_src/library/test" }
proc_macro = { path = "./sysroot_src/library/proc_macro" }

[patch.crates-io]
# todo: remove patch
compiler_builtins = { git = "https://github.com/tgross35/compiler-builtins.git", package = "compiler_builtins", branch = "NOMERGE-overflowing-c-safe-ret-version" }
rustc-std-workspace-core = { path = "./sysroot_src/library/rustc-std-workspace-core" }
rustc-std-workspace-alloc = { path = "./sysroot_src/library/rustc-std-workspace-alloc" }
rustc-std-workspace-std = { path = "./sysroot_src/library/rustc-std-workspace-std" }
Expand Down
121 changes: 58 additions & 63 deletions compiler/rustc_codegen_gcc/src/int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Contributor

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 in src/intrinsic/mod.rs.

AFAIK, the only related tests that run in this CI are those lines.

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 taking a look. Doesn't this block currently sometimes use __mulosi4 without an early turn? After the change to compiler_builtins, __rust_*128_o should be consistent with the __mulo*i4 functions.

AFAIK, the only related tests that run in this CI are those lines.

Is there any kind of codegen/asm test that should be added here? Or is the existing test sufficient once CI gets that far.

Copy link
Contributor

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 to compiler_builtins, __rust_*128_o should be consistent with the __mulo*i4 functions.

Indeed, it looks like there might be a bug in here.

Is there any kind of codegen/asm test that should be added here? Or is the existing test sufficient once CI gets that far.

What do you mean? There's more tests that are ran in the repo of rustc_codegen_gcc itself.

Copy link
Contributor Author

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.

I changed this so mulo and the __rust functions should now be taking the same codepath, still have to update the rest.

Is there any kind of codegen/asm test that should be added here? Or is the existing test sufficient once CI gets that far.

What do you mean? There's more tests that are ran in the repo of rustc_codegen_gcc itself.

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).

Copy link
Contributor

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.

Copy link
Contributor Author

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.

I was expecting that there would be UI tests for these, so that every backend could benefit from them.

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.

}
_ => 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);
Expand All @@ -364,80 +354,85 @@ 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 oflow_type = self.i32_type;
let oflow_param_type = oflow_type.make_pointer();
let res_type = a_type;

let oflow_value = self.current_func().new_local(self.location, oflow_type, "overflow");
let oflow_addr = oflow_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_oflow = self.context.new_parameter(self.location, oflow_param_type, "oflow");

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"),
};
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_oflow],
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, oflow_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_oflow],
func_name,
false,
);
self.context.new_call(self.location, func, &[lhs, rhs])
self.context.new_call(self.location, func, &[lhs, rhs, oflow_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, oflow_value, self.bool_type).to_rvalue())
}

pub fn gcc_icmp(
Expand Down
6 changes: 4 additions & 2 deletions compiler/rustc_codegen_gcc/src/intrinsic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -988,7 +988,8 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> {
128 => "__rust_i128_addo",
_ => unreachable!(),
};
let (int_result, overflow) = self.operation_with_overflow(func_name, lhs, rhs);
let (int_result, overflow) =
self.operation_with_overflow(func_name, lhs, rhs, width);
self.llbb().add_assignment(self.location, res, int_result);
overflow
};
Expand Down Expand Up @@ -1058,7 +1059,8 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> {
128 => "__rust_i128_subo",
_ => unreachable!(),
};
let (int_result, overflow) = self.operation_with_overflow(func_name, lhs, rhs);
let (int_result, overflow) =
self.operation_with_overflow(func_name, lhs, rhs, width);
self.llbb().add_assignment(self.location, res, int_result);
overflow
};
Expand Down
5 changes: 2 additions & 3 deletions library/Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,8 @@ dependencies = [

[[package]]
name = "compiler_builtins"
version = "0.1.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df14d41c5d172a886df3753d54238eefb0f61c96cbd8b363c33ccc92c457bee3"
version = "0.1.999"
source = "git+https://github.com/tgross35/compiler-builtins.git?branch=NOMERGE-overflowing-c-safe-ret-version#fbdf922da9710c01698d7b42f7e40dcb1ac66d2d"
dependencies = [
"cc",
"rustc-std-workspace-core",
Expand Down
2 changes: 2 additions & 0 deletions library/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,5 @@ rustc-demangle.debug = 0
rustc-std-workspace-core = { path = 'rustc-std-workspace-core' }
rustc-std-workspace-alloc = { path = 'rustc-std-workspace-alloc' }
rustc-std-workspace-std = { path = 'rustc-std-workspace-std' }
# todo: remove patch
compiler_builtins = { git = "https://github.com/tgross35/compiler-builtins.git", package = "compiler_builtins", branch = "NOMERGE-overflowing-c-safe-ret-version" }
2 changes: 1 addition & 1 deletion library/alloc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ edition = "2021"

[dependencies]
core = { path = "../core" }
compiler_builtins = { version = "=0.1.140", features = ['rustc-dep-of-std'] }
compiler_builtins = { version = "0.1.140", features = ['rustc-dep-of-std'] }

[dev-dependencies]
rand = { version = "0.8.5", default-features = false, features = ["alloc"] }
Expand Down
2 changes: 1 addition & 1 deletion library/std/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ cfg-if = { version = "1.0", features = ['rustc-dep-of-std'] }
panic_unwind = { path = "../panic_unwind", optional = true }
panic_abort = { path = "../panic_abort" }
core = { path = "../core", public = true }
compiler_builtins = { version = "=0.1.140" }
compiler_builtins = { version = "0.1.140" }
unwind = { path = "../unwind" }
hashbrown = { version = "0.15", default-features = false, features = [
'rustc-dep-of-std',
Expand Down
Loading