Implement saturating_{add, sub} for non-native integer types

Updates their unsigned code paths to use the `Builder::gcc_` methods
that automatically lower non-native integer operations to native ones.

Also updates the signed code path of `saturating_add` to support non-
native integer types. That of `saturating_sub` already supports this,
so no major changes have been made.
This commit is contained in:
yvt 2022-04-01 13:54:51 +09:00
parent 837a4467bc
commit 00677e5159

View File

@ -967,34 +967,55 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> {
} }
fn saturating_add(&mut self, lhs: RValue<'gcc>, rhs: RValue<'gcc>, signed: bool, width: u64) -> RValue<'gcc> { fn saturating_add(&mut self, lhs: RValue<'gcc>, rhs: RValue<'gcc>, signed: bool, width: u64) -> RValue<'gcc> {
let func = self.current_func.borrow().expect("func"); let result_type = lhs.get_type();
if signed { if signed {
// Algorithm from: https://stackoverflow.com/a/56531252/389119 // Based on algorithm from: https://stackoverflow.com/a/56531252/389119
let after_block = func.new_block("after"); let func = self.current_func.borrow().expect("func");
let func_name =
match width {
8 => "__builtin_add_overflow",
16 => "__builtin_add_overflow",
32 => "__builtin_sadd_overflow",
64 => "__builtin_saddll_overflow",
128 => "__builtin_add_overflow",
_ => unreachable!(),
};
let overflow_func = self.context.get_builtin_function(func_name);
let result_type = lhs.get_type();
let res = func.new_local(None, result_type, "saturating_sum"); let res = func.new_local(None, result_type, "saturating_sum");
let overflow = self.overflow_call(overflow_func, &[lhs, rhs, res.get_address(None)], None); let supports_native_type = self.is_native_int_type(result_type);
let overflow =
if supports_native_type {
let func_name =
match width {
8 => "__builtin_add_overflow",
16 => "__builtin_add_overflow",
32 => "__builtin_sadd_overflow",
64 => "__builtin_saddll_overflow",
128 => "__builtin_add_overflow",
_ => unreachable!(),
};
let overflow_func = self.context.get_builtin_function(func_name);
self.overflow_call(overflow_func, &[lhs, rhs, res.get_address(None)], None)
}
else {
let func_name =
match width {
128 => "__rust_i128_addo",
_ => unreachable!(),
};
let param_a = self.context.new_parameter(None, result_type, "a");
let param_b = self.context.new_parameter(None, result_type, "b");
let result_field = self.context.new_field(None, result_type, "result");
let overflow_field = self.context.new_field(None, self.bool_type, "overflow");
let return_type = self.context.new_struct_type(None, "result_overflow", &[result_field, overflow_field]);
let func = self.context.new_function(None, FunctionType::Extern, return_type.as_type(), &[param_a, param_b], func_name, false);
let result = self.context.new_call(None, func, &[lhs, rhs]);
let overflow = result.access_field(None, overflow_field);
let int_result = result.access_field(None, result_field);
self.llbb().add_assignment(None, res, int_result);
overflow
};
let then_block = func.new_block("then"); let then_block = func.new_block("then");
let after_block = func.new_block("after");
let unsigned_type = self.context.new_int_type(width as i32 / 8, false); // Return `result_type`'s maximum or minimum value on overflow
let shifted = self.context.new_cast(None, lhs, unsigned_type) >> self.context.new_rvalue_from_int(unsigned_type, width as i32 - 1); // NOTE: convert the type to unsigned to have an unsigned shift.
let uint_max = self.context.new_unary_op(None, UnaryOp::BitwiseNegate, unsigned_type, let unsigned_type = result_type.to_unsigned(&self.cx);
self.context.new_rvalue_from_int(unsigned_type, 0) let shifted = self.gcc_lshr(self.gcc_int_cast(lhs, unsigned_type), self.gcc_int(unsigned_type, width as i64 - 1));
); let uint_max = self.gcc_not(self.gcc_int(unsigned_type, 0));
let int_max = uint_max >> self.context.new_rvalue_one(unsigned_type); let int_max = self.gcc_lshr(uint_max, self.gcc_int(unsigned_type, 1));
then_block.add_assignment(None, res, self.context.new_cast(None, shifted + int_max, result_type)); then_block.add_assignment(None, res, self.gcc_int_cast(self.gcc_add(shifted, int_max), result_type));
then_block.end_with_jump(None, after_block); then_block.end_with_jump(None, after_block);
self.llbb().end_with_conditional(None, overflow, then_block, after_block); self.llbb().end_with_conditional(None, overflow, then_block, after_block);
@ -1006,20 +1027,20 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> {
res.to_rvalue() res.to_rvalue()
} }
else { else {
assert!(!signed);
// Algorithm from: http://locklessinc.com/articles/sat_arithmetic/ // Algorithm from: http://locklessinc.com/articles/sat_arithmetic/
let res = lhs + rhs; let res = self.gcc_add(lhs, rhs);
let res_type = res.get_type(); let cond = self.gcc_icmp(IntPredicate::IntULT, res, lhs);
let cond = self.context.new_comparison(None, ComparisonOp::LessThan, res, lhs); let value = self.gcc_neg(self.gcc_int_cast(cond, result_type));
let value = self.context.new_unary_op(None, UnaryOp::Minus, res_type, self.context.new_cast(None, cond, res_type)); self.gcc_or(res, value)
res | value
} }
} }
// Algorithm from: https://locklessinc.com/articles/sat_arithmetic/ // Algorithm from: https://locklessinc.com/articles/sat_arithmetic/
fn saturating_sub(&mut self, lhs: RValue<'gcc>, rhs: RValue<'gcc>, signed: bool, width: u64) -> RValue<'gcc> { fn saturating_sub(&mut self, lhs: RValue<'gcc>, rhs: RValue<'gcc>, signed: bool, width: u64) -> RValue<'gcc> {
let result_type = lhs.get_type();
if signed { if signed {
// Also based on algorithm from: https://stackoverflow.com/a/56531252/389119 // Based on algorithm from: https://stackoverflow.com/a/56531252/389119
let result_type = lhs.get_type();
let func = self.current_func.borrow().expect("func"); let func = self.current_func.borrow().expect("func");
let res = func.new_local(None, result_type, "saturating_diff"); let res = func.new_local(None, result_type, "saturating_diff");
let supports_native_type = self.is_native_int_type(result_type); let supports_native_type = self.is_native_int_type(result_type);
@ -1059,6 +1080,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> {
let then_block = func.new_block("then"); let then_block = func.new_block("then");
let after_block = func.new_block("after"); let after_block = func.new_block("after");
// Return `result_type`'s maximum or minimum value on overflow
// NOTE: convert the type to unsigned to have an unsigned shift. // NOTE: convert the type to unsigned to have an unsigned shift.
let unsigned_type = result_type.to_unsigned(&self.cx); let unsigned_type = result_type.to_unsigned(&self.cx);
let shifted = self.gcc_lshr(self.gcc_int_cast(lhs, unsigned_type), self.gcc_int(unsigned_type, width as i64 - 1)); let shifted = self.gcc_lshr(self.gcc_int_cast(lhs, unsigned_type), self.gcc_int(unsigned_type, width as i64 - 1));
@ -1076,11 +1098,11 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> {
res.to_rvalue() res.to_rvalue()
} }
else { else {
let res = lhs - rhs; assert!(!signed);
let comparison = self.context.new_comparison(None, ComparisonOp::LessThanEquals, res, lhs); let res = self.gcc_sub(lhs, rhs);
let comparison = self.context.new_cast(None, comparison, lhs.get_type()); let comparison = self.gcc_icmp(IntPredicate::IntULE, res, lhs);
let unary_op = self.context.new_unary_op(None, UnaryOp::Minus, comparison.get_type(), comparison); let value = self.gcc_neg(self.gcc_int_cast(comparison, result_type));
self.and(res, unary_op) self.gcc_and(res, value)
} }
} }
} }