diff --git a/xls/passes/arith_simplification_pass.cc b/xls/passes/arith_simplification_pass.cc index bebcd130bb..17d4f89b37 100644 --- a/xls/passes/arith_simplification_pass.cc +++ b/xls/passes/arith_simplification_pass.cc @@ -171,23 +171,30 @@ std::optional MatchClampUpperLimit(Node* n, } // The values required to emulate division by a specific divisor. -struct UnsignedDivisionConstants { - // dividend and divisor must be < 2^N +struct DivisionConstants { + // For unsigned: dividend and divisor must be < 2^N. + // For signed: |dividend| and |divisor| must be <= 2^(N-1). int64_t N; // multiplies the dividend BigInt m; - // right shift amount before the multiply + // right shift amount before the multiply (unsigned only; 0 for signed) int64_t pre_shift; // right shift amount after the multiply int64_t post_shift; + + // whether the divisor is negative (signed only; false for unsigned) + bool divisor_negative = false; }; -struct UnsignedDivisionByPowerOfTwo { +struct DivisionByPowerOfTwo { // The exponent we are dividing by. int64_t exponent; + + // Whether the divisor is negative (signed only; false for unsigned). + bool divisor_negative = false; }; // Returns (m, post_shift, l). Used in optimizing division by @@ -231,11 +238,30 @@ std::tuple ChooseMultiplier(int64_t N, BigInt divisor, return std::make_tuple(m_high, post_shift, l); } -UnsignedDivisionConstants ComputeUnsignedDivisionConstants(int64_t num_bits, - BigInt divisor) { +DivisionConstants ComputeDivisionConstants(int64_t num_bits, + const Bits& divisor_bits, + bool is_signed) { + if (is_signed) { + BigInt divisor = BigInt::MakeSigned(divisor_bits); + BigInt magnitude_divisor = BigInt::Absolute(divisor); + CHECK(!BigInt::IsPowerOfTwo(magnitude_divisor)) + << "divide by power of two isn't handled by DivisionConstants; other " + "code handles that case."; + auto [m, post_shift, ignored] = + ChooseMultiplier(num_bits, magnitude_divisor, num_bits - 1); + return DivisionConstants{ + .N = num_bits, + .m = std::move(m), + .pre_shift = 0, + .post_shift = post_shift, + .divisor_negative = divisor < BigInt::Zero(), + }; + } + + BigInt divisor = BigInt::MakeUnsigned(divisor_bits); CHECK(!BigInt::IsPowerOfTwo(divisor)) - << "divide by power of two isn't handled by UnsignedDivision; other code " - "handles that case."; + << "divide by power of two isn't handled by DivisionConstants; other " + "code handles that case."; auto [m, post_shift, ignored] = ChooseMultiplier(num_bits, divisor, num_bits); @@ -249,7 +275,13 @@ UnsignedDivisionConstants ComputeUnsignedDivisionConstants(int64_t num_bits, pre_shift = 0; } - return UnsignedDivisionConstants{num_bits, m, pre_shift, post_shift}; + return DivisionConstants{ + .N = num_bits, + .m = std::move(m), + .pre_shift = pre_shift, + .post_shift = post_shift, + .divisor_negative = false, + }; } // Narrows or extends n. @@ -577,22 +609,44 @@ absl::StatusOr GenerateLUT(CompressedLUT lut, Node* index, /*default_value=*/past_the_end); } -namespace unsigned_div { - struct Pow2Lut {}; struct Pow2Shift { Node* shift_amount; int64_t literal_shift; }; +absl::StatusOr MakeDivByZeroResult(Node* original_divide_op) { + FunctionBase* fb = original_divide_op->function_base(); + const SourceInfo& loc = original_divide_op->loc(); + int64_t bit_width = original_divide_op->BitCountOrDie(); + if (original_divide_op->op() == Op::kUDiv || bit_width == 0) { + return fb->MakeNode(loc, Value(Bits::AllOnes(bit_width))); + } + XLS_RET_CHECK_EQ(original_divide_op->op(), Op::kSDiv); + Node* dividend = original_divide_op->operand(0); + XLS_ASSIGN_OR_RETURN( + Node * min_neg_lit, + fb->MakeNode(loc, Value(Bits::MinSigned(bit_width)))); + XLS_ASSIGN_OR_RETURN( + Node * max_pos_lit, + fb->MakeNode(loc, Value(Bits::MaxSigned(bit_width)))); + XLS_ASSIGN_OR_RETURN(Node * msb, + fb->MakeNode(loc, dividend, bit_width - 1, 1)); + return fb->MakeNode( + loc, is_div_by_zero, + absl::Span{shift_result, div_by_zero_res}, std::nullopt, + NodeNameFormat("%s_select_div_by_zero", original_divide_op)); } + + XLS_RET_CHECK_EQ(original_divide_op->op(), Op::kSDiv); + // We're dealing with a signed division; the logic is slightly more complex. + // Cases: + // - Div by zero (K + L > N - 1): return max_signed or min_signed, depending + // on the sign of the dividend. + // - Div by min_signed (K + L = N - 1): return 0, unless dividend is + // min_signed, then 1. + // - Otherwise (K + L < N - 1): we can compute the result as + // (dividend + bias) >> (K + L), + // where the bias should be (1 << (K + L)) - 1 if + // the dividend is negative, or 0 otherwise, + // making sure we round towards zero rather than + // -inf. + int64_t limit = bit_width - 1 - literal_shift; + + XLS_ASSIGN_OR_RETURN( + Node * all_ones_lit, + fb->MakeNode(loc, Value(Bits::AllOnes(bit_width)))); + Node* shifted_ones = all_ones_lit; + if (literal_shift != 0) { + XLS_ASSIGN_OR_RETURN( + shifted_ones, + fb->MakeNode(loc, shifted_ones, literal_shift_lit, Op::kShll)); + } + XLS_ASSIGN_OR_RETURN( + shifted_ones, + fb->MakeNode(loc, shifted_ones, shift_amount, Op::kShll)); + XLS_ASSIGN_OR_RETURN(Node * bias_for_negative_dividend, + fb->MakeNode(loc, shifted_ones, Op::kNot)); + XLS_ASSIGN_OR_RETURN(Node * negative_dividend, + fb->MakeNode(loc, dividend, bit_width - 1, 1)); + XLS_ASSIGN_OR_RETURN( + Node * biased_dividend, + fb->MakeNode(loc, dividend, bias_for_negative_dividend, Op::kAdd)); XLS_ASSIGN_OR_RETURN( - Node * shift_result, + Node * adjusted_dividend, + fb->MakeNode( - original_divide_op->loc(), is_div_by_zero, - absl::Span{shift_result, div_by_zero_res}, std::nullopt, - NodeNameFormat("%s_select_div_by_zero", original_divide_op)); + Node * div_by_min_signed_result, + fb->MakeNode( - is_zero, - std::vector{shrl_result, all_ones_lit}, - std::nullopt) - .status()); - return true; - } - - // We're dealing with a signed division; the logic is slightly more complex. - // Cases: - // - Div by zero (K + L > N - 1): return max_signed or min_signed, depending - // on the sign of the dividend. - // - Div by min_signed (K + L = N - 1): return 0, unless dividend is - // min_signed, then 1. - // - Otherwise (K + L < N - 1): we can compute the result as - // (dividend + bias) >> (K + L), - // where the bias should be (1 << (K + L)) - 1 if - // the dividend is negative, or 0 otherwise, - // making sure we round towards zero rather than - // -inf. - int64_t limit = bit_width - 1 - literal_shift; - - // Always generate the shift result (default case). - XLS_ASSIGN_OR_RETURN( - Node * all_ones_lit, - f->MakeNode(n->loc(), Value(Bits::AllOnes(bit_width)))); - Node* shifted_ones = all_ones_lit; - if (literal_shift > 0) { - CHECK(literal_shift_lit != nullptr); - XLS_ASSIGN_OR_RETURN(shifted_ones, - f->MakeNode(n->loc(), shifted_ones, - literal_shift_lit, Op::kShll)); - } - XLS_ASSIGN_OR_RETURN( - shifted_ones, f->MakeNode(n->loc(), shifted_ones, k, Op::kShll)); - XLS_ASSIGN_OR_RETURN(Node * bias_for_negative_dividend, - f->MakeNode(n->loc(), shifted_ones, Op::kNot)); - XLS_ASSIGN_OR_RETURN( - Node * negative_dividend, - f->MakeNode(n->loc(), dividend, bit_width - 1, 1)); - XLS_ASSIGN_OR_RETURN( - Node * biased_dividend, - f->MakeNode(n->loc(), dividend, bias_for_negative_dividend, - Op::kAdd)); - XLS_ASSIGN_OR_RETURN( - Node * adjusted_dividend, - f->MakeNode(n->loc(), msb, - std::vector{max_pos_lit, min_neg_lit}, - std::nullopt)); - - // Case 1: Divide by min_signed (K + L = N - 1) - XLS_ASSIGN_OR_RETURN( - Node * x_is_min_neg, - f->MakeNode(n->loc(), dividend, min_neg_lit, Op::kEq)); - XLS_ASSIGN_OR_RETURN( - Node * zero_lit, - f->MakeNode(n->loc(), Value(UBits(0, bit_width)))); - XLS_ASSIGN_OR_RETURN( - Node * one_lit, - f->MakeNode(n->loc(), Value(UBits(1, bit_width)))); - XLS_ASSIGN_OR_RETURN( - Node * div_by_min_signed_result, - f->MakeNode( - n->loc(), msb, std::vector{max_pos_lit, min_neg_lit}, - std::nullopt)); - } + XLS_ASSIGN_OR_RETURN(Node * div_by_zero_result, + MakeDivByZeroResult(n)); XLS_ASSIGN_OR_RETURN( replacement, f->MakeNode