/ externals / biscuit / src / assembler_util.hpp
assembler_util.hpp
  1  #pragma once
  2  
  3  #include <biscuit/assert.hpp>
  4  #include <biscuit/code_buffer.hpp>
  5  #include <biscuit/registers.hpp>
  6  
  7  #include <cstddef>
  8  #include <cstdint>
  9  
 10  // Generic internal utility header for various helper functions related
 11  // to encoding instructions.
 12  
 13  namespace biscuit {
 14  // Determines if a value lies within the range of a 6-bit immediate.
 15  [[nodiscard]] constexpr bool IsValidSigned6BitImm(ptrdiff_t value) {
 16      return value >= -32 && value <= 31;
 17  }
 18  
 19  // S-type and I-type immediates are 12 bits in size
 20  [[nodiscard]] constexpr bool IsValidSigned12BitImm(ptrdiff_t value) {
 21      return value >= -2048 && value <= 2047;
 22  }
 23  
 24  // B-type immediates only provide -4KiB to +4KiB range branches.
 25  [[nodiscard]] constexpr bool IsValidBTypeImm(ptrdiff_t value) {
 26      return value >= -4096 && value <= 4095;
 27  }
 28  
 29  // J-type immediates only provide -1MiB to +1MiB range branches.
 30  [[nodiscard]] constexpr bool IsValidJTypeImm(ptrdiff_t value) {
 31      return value >= -0x80000 && value <= 0x7FFFF;
 32  }
 33  
 34  // CB-type immediates only provide -256B to +256B range branches.
 35  [[nodiscard]] constexpr bool IsValidCBTypeImm(ptrdiff_t value) {
 36      return value >= -256 && value <= 255;
 37  }
 38  
 39  // CJ-type immediates only provide -2KiB to +2KiB range branches.
 40  [[nodiscard]] constexpr bool IsValidCJTypeImm(ptrdiff_t value) {
 41      return IsValidSigned12BitImm(value);
 42  }
 43  
 44  // Determines whether or not the register fits in 3-bit compressed encoding.
 45  [[nodiscard]] constexpr bool IsValid3BitCompressedReg(Register reg) {
 46      const auto index = reg.Index();
 47      return index >= 8 && index <= 15;
 48  }
 49  
 50  // Determines whether or not the given shift amount is valid for a compressed shift instruction
 51  [[nodiscard]] constexpr bool IsValidCompressedShiftAmount(uint32_t shift) {
 52      return shift > 0 && shift <= 64;
 53  }
 54  
 55  // Turns a compressed register into its encoding.
 56  [[nodiscard]] constexpr uint32_t CompressedRegTo3BitEncoding(Register reg) {
 57      return reg.Index() - 8;
 58  }
 59  
 60  // Transforms a regular value into an immediate encoded in a B-type instruction.
 61  [[nodiscard]] constexpr uint32_t TransformToBTypeImm(uint32_t imm) {
 62      // clang-format off
 63      return ((imm & 0x07E0) << 20) |
 64             ((imm & 0x1000) << 19) |
 65             ((imm & 0x001E) << 7) |
 66             ((imm & 0x0800) >> 4);
 67      // clang-format on
 68  }
 69  
 70  // Transforms a regular value into an immediate encoded in a J-type instruction.
 71  [[nodiscard]] constexpr uint32_t TransformToJTypeImm(uint32_t imm) {
 72      // clang-format off
 73      return ((imm & 0x0FF000) >> 0) |
 74             ((imm & 0x000800) << 9) |
 75             ((imm & 0x0007FE) << 20) |
 76             ((imm & 0x100000) << 11);
 77      // clang-format on
 78  }
 79  
 80  // Transforms a regular value into an immediate encoded in a CB-type instruction.
 81  [[nodiscard]] constexpr uint32_t TransformToCBTypeImm(uint32_t imm) {
 82      // clang-format off
 83      return ((imm & 0x0C0) >> 1) |
 84             ((imm & 0x006) << 2) |
 85             ((imm & 0x020) >> 3) |
 86             ((imm & 0x018) << 7) |
 87             ((imm & 0x100) << 4);
 88      // clang-format on
 89  }
 90  
 91  // Transforms a regular value into an immediate encoded in a CJ-type instruction.
 92  [[nodiscard]] constexpr uint32_t TransformToCJTypeImm(uint32_t imm) {
 93      // clang-format off
 94      return ((imm & 0x800) << 1) |
 95             ((imm & 0x010) << 7) |
 96             ((imm & 0x300) << 1) |
 97             ((imm & 0x400) >> 2) |
 98             ((imm & 0x040) << 1) |
 99             ((imm & 0x080) >> 1) |
100             ((imm & 0x00E) << 4) |
101             ((imm & 0x020) >> 3);
102      // clang-format on
103  }
104  
105  // Emits a B type RISC-V instruction. These consist of:
106  // imm[12|10:5] | rs2 | rs1 | funct3 | imm[4:1] | imm[11] | opcode
107  inline void EmitBType(CodeBuffer& buffer, uint32_t imm, GPR rs2, GPR rs1,
108                        uint32_t funct3, uint32_t opcode) {
109      imm &= 0x1FFE;
110  
111      buffer.Emit32(TransformToBTypeImm(imm) | (rs2.Index() << 20) | (rs1.Index() << 15) |
112                    ((funct3 & 0b111) << 12) | (opcode & 0x7F));
113  }
114  
115  // Emits a I type RISC-V instruction. These consist of:
116  // imm[11:0] | rs1 | funct3 | rd | opcode
117  inline void EmitIType(CodeBuffer& buffer, uint32_t imm, Register rs1, uint32_t funct3,
118                        Register rd, uint32_t opcode) {
119      imm &= 0xFFF;
120  
121      buffer.Emit32((imm << 20) | (rs1.Index() << 15) | ((funct3 & 0b111) << 12) |
122                    (rd.Index() << 7) | (opcode & 0x7F));
123  }
124  
125  // Emits a J type RISC-V instruction. These consist of:
126  // imm[20|10:1|11|19:12] | rd | opcode
127  inline void EmitJType(CodeBuffer& buffer, uint32_t imm, GPR rd, uint32_t opcode) {
128      imm &= 0x1FFFFE;
129  
130      buffer.Emit32(TransformToJTypeImm(imm) | rd.Index() << 7 | (opcode & 0x7F));
131  }
132  
133  // Emits a R type RISC instruction. These consist of:
134  // funct7 | rs2 | rs1 | funct3 | rd | opcode
135  inline void EmitRType(CodeBuffer& buffer, uint32_t funct7, Register rs2, Register rs1,
136                        uint32_t funct3, Register rd, uint32_t opcode) {
137      // clang-format off
138      const auto value = ((funct7 & 0xFF) << 25) |
139                         (rs2.Index() << 20) |
140                         (rs1.Index() << 15) |
141                         ((funct3 & 0b111) << 12) |
142                         (rd.Index() << 7) |
143                         (opcode & 0x7F);
144      // clang-format off
145  
146      buffer.Emit32(value);
147  }
148  
149  // Emits a R type RISC instruction. These consist of:
150  // funct7 | rs2 | rs1 | funct3 | rd | opcode
151  inline void EmitRType(CodeBuffer& buffer, uint32_t funct7, FPR rs2, FPR rs1, RMode funct3,
152                        FPR rd, uint32_t opcode) {
153      EmitRType(buffer, funct7, rs2, rs1, static_cast<uint32_t>(funct3), rd, opcode);
154  }
155  
156  // Emits a R4 type RISC instruction. These consist of:
157  // rs3 | funct2 | rs2 | rs1 | funct3 | rd | opcode
158  inline void EmitR4Type(CodeBuffer& buffer, FPR rs3, uint32_t funct2, FPR rs2, FPR rs1,
159                         RMode funct3, FPR rd, uint32_t opcode) {
160      const auto reg_bits = (rs3.Index() << 27) | (rs2.Index() << 20) | (rs1.Index() << 15) | (rd.Index() << 7);
161      const auto funct_bits = ((funct2 & 0b11) << 25) | (static_cast<uint32_t>(funct3) << 12);
162      buffer.Emit32(reg_bits | funct_bits | (opcode & 0x7F));
163  }
164  
165  // Emits a S type RISC-V instruction. These consist of:
166  // imm[11:5] | rs2 | rs1 | funct3 | imm[4:0] | opcode
167  inline void EmitSType(CodeBuffer& buffer, uint32_t imm, Register rs2, GPR rs1,
168                        uint32_t funct3, uint32_t opcode) {
169      imm &= 0xFFF;
170  
171      // clang-format off
172      const auto new_imm = ((imm & 0x01F) << 7) |
173                           ((imm & 0xFE0) << 20);
174      // clang-format on
175  
176      buffer.Emit32(new_imm | (rs2.Index() << 20) | (rs1.Index() << 15) |
177                    ((funct3 & 0b111) << 12) | (opcode & 0x7F));
178  }
179  
180  // Emits a U type RISC-V instruction. These consist of:
181  // imm[31:12] | rd | opcode
182  inline void EmitUType(CodeBuffer& buffer, uint32_t imm, GPR rd, uint32_t opcode) {
183      buffer.Emit32((imm & 0x000FFFFF) << 12 | rd.Index() << 7 | (opcode & 0x7F));
184  }
185  
186  // Emits an atomic instruction.
187  inline void EmitAtomic(CodeBuffer& buffer, uint32_t funct5, Ordering ordering, GPR rs2, GPR rs1,
188                         uint32_t funct3, GPR rd, uint32_t opcode) noexcept {
189      const auto funct7 = (funct5 << 2) | static_cast<uint32_t>(ordering);
190      EmitRType(buffer, funct7, rs2, rs1, funct3, rd, opcode);
191  }
192  
193  // Emits a fence instruction
194  inline void EmitFENCE(CodeBuffer& buffer, uint32_t fm, FenceOrder pred, FenceOrder succ,
195                        GPR rs, uint32_t funct3, GPR rd, uint32_t opcode) noexcept {
196      // clang-format off
197      buffer.Emit32(((fm & 0b1111) << 28) |
198                    (static_cast<uint32_t>(pred) << 24) |
199                    (static_cast<uint32_t>(succ) << 20) |
200                    (rs.Index() << 15) |
201                    ((funct3 & 0b111) << 12) |
202                    (rd.Index() << 7) |
203                    (opcode & 0x7F));
204      // clang-format on
205  }
206  
207  // Internal helpers for siloing away particular comparisons for behavior.
208  constexpr bool IsRV32(ArchFeature feature) {
209      return feature == ArchFeature::RV32;
210  }
211  constexpr bool IsRV64(ArchFeature feature) {
212      return feature == ArchFeature::RV64;
213  }
214  constexpr bool IsRV128(ArchFeature feature) {
215      return feature == ArchFeature::RV128;
216  }
217  constexpr bool IsRV32OrRV64(ArchFeature feature) {
218      return IsRV32(feature) || IsRV64(feature);
219  }
220  constexpr bool IsRV64OrRV128(ArchFeature feature) {
221      return IsRV64(feature) || IsRV128(feature);
222  }
223  
224  } // namespace biscuit