1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #ifndef RISCV64_TO_X86_64_BERBERIS_INTRINSICS_MACRO_ASSEMBLER_ARITH_IMPL_H_
18 #define RISCV64_TO_X86_64_BERBERIS_INTRINSICS_MACRO_ASSEMBLER_ARITH_IMPL_H_
19 
20 #include <climits>
21 #include <type_traits>
22 
23 #include "berberis/base/bit_util.h"
24 #include "berberis/intrinsics/macro_assembler.h"
25 
26 namespace berberis {
27 
28 // Divisor comes in "src", dividend comes in gpr_a, result is returned in gpr_a.
29 // gpr_d and FLAGS are clobbered by that macroinstruction.
30 template <typename Assembler>
31 template <typename IntType>
MacroDiv(Register src)32 void MacroAssembler<Assembler>::MacroDiv(Register src) {
33   Label* zero = MakeLabel();
34   Label* done = MakeLabel();
35   Test<IntType>(src, src);
36   Jcc(Condition::kZero, *zero);
37 
38   if constexpr (std::is_signed_v<IntType>) {
39     Label* do_idiv = MakeLabel();
40     // If min int32_t/int64_t is divided by -1 then in risc-v the result is
41     // the dividend, but x86 will raise an exception. Handle this case separately.
42     Cmp<IntType>(src, int8_t{-1});
43     Jcc(Condition::kNotEqual, *do_idiv);
44 
45     if constexpr (std::is_same_v<IntType, int64_t>) {
46       Cmp<IntType>(gpr_a,
47                    {.disp = constants_pool::kVectorConst<std::numeric_limits<IntType>::min()>});
48     } else {
49       Cmp<IntType>(gpr_a, std::numeric_limits<IntType>::min());
50     }
51     Jcc(Condition::kEqual, *done);
52 
53     Bind(do_idiv);
54     // If we are dealing with 8-bit signed case then we need to sign-extend %al into %ax.
55     if constexpr (std::is_same_v<IntType, int8_t>) {
56       Cbw();
57     // We need to sign-extend gpr_a into gpr_d to ensure 32bit/64-bit/128-bit dividend is correct.
58     } else if constexpr (std::is_same_v<IntType, int16_t>) {
59       Cwd();
60     } else if constexpr (std::is_same_v<IntType, int32_t>) {
61       Cdq();
62     } else if constexpr (std::is_same_v<IntType, int64_t>) {
63       Cqo();
64     } else {
65       static_assert(kDependentTypeFalse<IntType>, "Unsupported format");
66     }
67   } else if constexpr (std::is_same_v<IntType, uint8_t>) {
68     // For 8bit unsigned case we need “xor %ah, %ah” instruction, but our assembler doesn't support
69     // %ah register. Use .byte to emit the required machine code.
70     TwoByte(uint16_t{0xe430});
71   } else {
72     // We need to zero-extend eax into dx/edx/rdx to ensure 32-bit/64-bit/128-bit dividend is
73     // correct.
74     Xor<uint32_t>(gpr_d, gpr_d);
75   }
76 
77   Div<IntType>(src);
78   Jmp(*done);
79 
80   Bind(zero);
81   Mov<IntType>(gpr_a, int64_t{-1});
82 
83   Bind(done);
84 }
85 
86 // Divisor comes in "src", dividend comes in gpr_a, remainder is returned in gpr_d.
87 // gpr_a and FLAGS are clobbered by that macroinstruction.
88 template <typename Assembler>
89 template <typename IntType>
MacroRem(Register src)90 void MacroAssembler<Assembler>::MacroRem(Register src) {
91   Label* zero = MakeLabel();
92   Label* overflow = MakeLabel();
93   Label* done = MakeLabel();
94   Test<IntType>(src, src);
95   Jcc(Condition::kZero, *zero);
96 
97   if constexpr (std::is_signed_v<IntType>) {
98     Label* do_idiv = MakeLabel();
99     // If min int32_t/int64_t is divided by -1 then in risc-v the result is
100     // the dividend, but x86 will raise an exception. Handle this case separately.
101     Cmp<IntType>(src, int8_t{-1});
102     Jcc(Condition::kNotEqual, *do_idiv);
103 
104     if constexpr (std::is_same_v<IntType, int64_t>) {
105       Cmp<IntType>(gpr_a,
106                    {.disp = constants_pool::kVectorConst<std::numeric_limits<IntType>::min()>});
107     } else {
108       Cmp<IntType>(gpr_a, std::numeric_limits<IntType>::min());
109     }
110     Jcc(Condition::kEqual, *overflow);
111 
112     Bind(do_idiv);
113     // If we are dealing with 8-bit signed case then we need to sign-extend %al into %ax.
114     if constexpr (std::is_same_v<IntType, int8_t>) {
115       Cbw();
116       // We need to sign-extend gpr_a into gpr_d to ensure 32bit/64-bit/128-bit dividend is correct.
117     } else if constexpr (std::is_same_v<IntType, int16_t>) {
118       Cwd();
119     } else if constexpr (std::is_same_v<IntType, int32_t>) {
120       Cdq();
121     } else if constexpr (std::is_same_v<IntType, int64_t>) {
122       Cqo();
123     } else {
124       static_assert(kDependentTypeFalse<IntType>, "Unsupported format");
125     }
126   } else if constexpr (std::is_same_v<IntType, uint8_t>) {
127     // For 8bit unsigned case we need “xor %ah, %ah” instruction, but our assembler doesn't support
128     // %ah register. Use .byte to emit the required machine code.
129     TwoByte(uint16_t{0xe430});
130   } else {
131     // We need to zero-extend eax into dx/edx/rdx to ensure 32-bit/64-bit/128-bit dividend is
132     // correct.
133     Xor<uint64_t>(gpr_d, gpr_d);
134   }
135 
136   Div<IntType>(src);
137   if constexpr (std::is_same_v<IntType, uint8_t> || std::is_same_v<IntType, int8_t>) {
138     // For 8bit case the result is in %ah, but our assembler doesn't support
139     // %ah register. move %ah to %al
140     TwoByte(uint16_t{0xe086});
141   }
142   Jmp(*done);
143 
144   Bind(zero);
145   if constexpr (std::is_same_v<IntType, uint8_t> || std::is_same_v<IntType, int8_t>) {
146     Mov<int8_t>(gpr_a, src);
147   } else {
148     Mov<IntType>(gpr_d, src);
149   }
150   Jmp(*done);
151 
152   Bind(overflow);
153   if constexpr (std::is_same_v<IntType, uint8_t> || std::is_same_v<IntType, int8_t>) {
154     Xor<int8_t>(gpr_a, gpr_a);
155   } else {
156     Xor<IntType>(gpr_d, gpr_d);
157   }
158   Bind(done);
159 }
160 }  // namespace berberis
161 
162 #endif  // RISCV64_TO_X86_64_BERBERIS_INTRINSICS_MACRO_ASSEMBLER_ARITH_IMPL_H_
163