1 /*
2  * Copyright (C) 2024 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 // Assembler to produce RV64 instructions (no ABI version). Somewhat influenced by V8 assembler.
18 
19 #ifndef BERBERIS_ASSEMBLER_RV64_H_
20 #define BERBERIS_ASSEMBLER_RV64_H_
21 
22 #include <bit>          // std::countr_zero
23 #include <type_traits>  // std::is_same
24 
25 #include "berberis/assembler/riscv.h"
26 
27 namespace berberis::rv64 {
28 
29 class Assembler : public riscv::Assembler<Assembler> {
30  public:
31   using BaseAssembler = riscv::Assembler<Assembler>;
32   using FinalAssembler = Assembler;
33 
Assembler(MachineCode * code)34   explicit Assembler(MachineCode* code) : BaseAssembler(code) {}
35 
36   using ShiftImmediate = BaseAssembler::Shift64Immediate;
37 
38   // Don't use templates here to enable implicit conversions.
39 #define BERBERIS_DEFINE_MAKE_SHIFT_IMMEDIATE(IntType)                                \
40   static constexpr std::optional<ShiftImmediate> MakeShiftImmediate(IntType value) { \
41     return BaseAssembler::MakeShift64Immediate(value);                               \
42   }
43   BERBERIS_DEFINE_MAKE_SHIFT_IMMEDIATE(int8_t)
44   BERBERIS_DEFINE_MAKE_SHIFT_IMMEDIATE(uint8_t)
45   BERBERIS_DEFINE_MAKE_SHIFT_IMMEDIATE(int16_t)
46   BERBERIS_DEFINE_MAKE_SHIFT_IMMEDIATE(uint16_t)
47   BERBERIS_DEFINE_MAKE_SHIFT_IMMEDIATE(int32_t)
48   BERBERIS_DEFINE_MAKE_SHIFT_IMMEDIATE(uint32_t)
49   BERBERIS_DEFINE_MAKE_SHIFT_IMMEDIATE(int64_t)
50   BERBERIS_DEFINE_MAKE_SHIFT_IMMEDIATE(uint64_t)
51 #undef BERBERIS_DEFINE_MAKE_SHIFT_IMMEDIATE
52 
53   friend BaseAssembler;
54 
55 // Instructions.
56 #include "berberis/assembler/gen_assembler_rv64-inl.h"  // NOLINT generated file!
57 
58  private:
59   Assembler() = delete;
60   Assembler(const Assembler&) = delete;
61   Assembler(Assembler&&) = delete;
62   void operator=(const Assembler&) = delete;
63   void operator=(Assembler&&) = delete;
64   void Li32(Register dest, int32_t imm32);
65 };
66 
Ld(Register arg0,const Label & label)67 inline void Assembler::Ld(Register arg0, const Label& label) {
68   jumps_.push_back(Jump{&label, pc(), false});
69   // First issue auipc to load top 20 bits of difference between pc and target address
70   EmitUTypeInstruction<uint32_t{0x0000'0017}>(arg0, UImmediate{0});
71   // The low 12 bite of difference will be encoded in the Ld instruction
72   EmitITypeInstruction<uint32_t{0x0000'3003}>(arg0, Operand<Register, IImmediate>{.base = arg0});
73 }
74 
75 // It's needed to unhide 32bit immediate version.
Li32(Register dest,int32_t imm32)76 inline void Assembler::Li32(Register dest, int32_t imm32) {
77   BaseAssembler::Li(dest, imm32);
78 };
79 
Li(Register dest,int64_t imm64)80 inline void Assembler::Li(Register dest, int64_t imm64) {
81   int32_t imm32 = static_cast<int32_t>(imm64);
82   if (static_cast<int64_t>(imm32) == imm64) {
83     Li32(dest, imm32);
84   } else {
85     // Perform calculations on unsigned type to avoid undefined behavior.
86     uint64_t uimm = static_cast<uint64_t>(imm64);
87     if (imm64 & 0xfff) {
88       // Since bottom 12bits are loaded via a 12-bit signed immediate, we need to transfer the sign
89       // bit to the top part.
90       int64_t top = (uimm + ((uimm & (1ULL << 11)) << 1)) & 0xffff'ffff'ffff'f000;
91       // Sign extends the bottom 12 bits.
92       struct {
93         int64_t data : 12;
94       } bottom = {imm64};
95       Li(dest, top);
96       Addi(dest, dest, static_cast<IImmediate>(bottom.data));
97     } else {
98       uint8_t zeros = std::countr_zero(uimm);
99       Li(dest, imm64 >> zeros);
100       Slli(dest, dest, static_cast<Shift64Immediate>(zeros));
101     }
102   }
103 }
104 
105 inline void Assembler::Lwu(Register arg0, const Label& label) {
106   jumps_.push_back(Jump{&label, pc(), false});
107   // First issue auipc to load top 20 bits of difference between pc and target address
108   EmitUTypeInstruction<uint32_t{0x0000'0017}>(arg0, UImmediate{0});
109   // The low 12 bite of difference will be encoded in the Lwu instruction
110   EmitITypeInstruction<uint32_t{0x0000'6003}>(arg0, Operand<Register, IImmediate>{.base = arg0});
111 }
112 
113 inline void Assembler::Sd(Register arg0, const Label& label, Register arg2) {
114   jumps_.push_back(Jump{&label, pc(), false});
115   // First issue auipc to load top 20 bits of difference between pc and target address
116   EmitUTypeInstruction<uint32_t{0x0000'0017}>(arg2, UImmediate{0});
117   // The low 12 bite of difference will be encoded in the Sd instruction
118   EmitSTypeInstruction<uint32_t{0x0000'3023}>(arg0, Operand<Register, SImmediate>{.base = arg2});
119 }
120 
121 inline void Assembler::SextW(Register arg0, Register arg1) {
122   Addiw(arg0, arg1, 0);
123 }
124 
125 inline void Assembler::ZextW(Register arg0, Register arg1) {
126   AddUW(arg0, arg1, zero);
127 }
128 
129 inline void Assembler::Negw(Register arg0, Register arg1) {
130   Subw(arg0, zero, arg1);
131 }
132 
133 }  // namespace berberis::rv64
134 
135 #endif  // BERBERIS_ASSEMBLER_RV64_H_
136