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