1//=- WebAssemblyMCCodeEmitter.cpp - Convert WebAssembly code to machine code -//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8///
9/// \file
10/// This file implements the WebAssemblyMCCodeEmitter class.
11///
12//===----------------------------------------------------------------------===//
13
14#include "MCTargetDesc/WebAssemblyFixupKinds.h"
15#include "MCTargetDesc/WebAssemblyMCTargetDesc.h"
16#include "llvm/ADT/Statistic.h"
17#include "llvm/MC/MCCodeEmitter.h"
18#include "llvm/MC/MCContext.h"
19#include "llvm/MC/MCFixup.h"
20#include "llvm/MC/MCInst.h"
21#include "llvm/MC/MCInstrInfo.h"
22#include "llvm/MC/MCSubtargetInfo.h"
23#include "llvm/MC/MCSymbol.h"
24#include "llvm/Support/Debug.h"
25#include "llvm/Support/EndianStream.h"
26#include "llvm/Support/LEB128.h"
27#include "llvm/Support/SMLoc.h"
28#include "llvm/Support/raw_ostream.h"
29
30using namespace llvm;
31
32#define DEBUG_TYPE "mccodeemitter"
33
34STATISTIC(MCNumEmitted, "Number of MC instructions emitted.");
35STATISTIC(MCNumFixups, "Number of MC fixups created.");
36
37namespace {
38class WebAssemblyMCCodeEmitter final : public MCCodeEmitter {
39 const MCInstrInfo &MCII;
40 MCContext &Ctx;
41 // Implementation generated by tablegen.
42 uint64_t getBinaryCodeForInstr(const MCInst &MI,
43 SmallVectorImpl<MCFixup> &Fixups,
44 const MCSubtargetInfo &STI) const;
45
46 void encodeInstruction(const MCInst &MI, SmallVectorImpl<char> &CB,
47 SmallVectorImpl<MCFixup> &Fixups,
48 const MCSubtargetInfo &STI) const override;
49
50 void encodeP2AlignAndMemOrder(const MCInst &MI, unsigned P2AlignIdx,
51 const MCInstrDesc &Desc,
52 const MCSubtargetInfo &STI, raw_ostream &OS,
53 SmallVectorImpl<MCFixup> &Fixups,
54 uint64_t Start) const;
55
56 uint8_t getEncodedMemOrder(uint8_t Order, unsigned Opcode) const;
57
58public:
59 WebAssemblyMCCodeEmitter(const MCInstrInfo &MCII, MCContext &Ctx)
60 : MCII(MCII), Ctx{Ctx} {}
61};
62} // end anonymous namespace
63
64MCCodeEmitter *llvm::createWebAssemblyMCCodeEmitter(const MCInstrInfo &MCII,
65 MCContext &Ctx) {
66 return new WebAssemblyMCCodeEmitter(MCII, Ctx);
67}
68
69uint8_t WebAssemblyMCCodeEmitter::getEncodedMemOrder(uint8_t Order,
70 unsigned Opcode) const {
71 if (Order == wasm::WASM_MEM_ORDER_ACQ_REL) {
72 StringRef Name = MCII.getName(Opcode);
73 if (Name.contains(Other: "RMW") || Name.contains(Other: "CMPXCHG"))
74 return wasm::WASM_MEM_ORDER_RMW_ACQ_REL;
75 }
76 return Order;
77}
78
79void WebAssemblyMCCodeEmitter::encodeP2AlignAndMemOrder(
80 const MCInst &MI, unsigned P2AlignIdx, const MCInstrDesc &Desc,
81 const MCSubtargetInfo &STI, raw_ostream &OS,
82 SmallVectorImpl<MCFixup> &Fixups, uint64_t Start) const {
83 uint64_t P2Align = MI.getOperand(i: P2AlignIdx).getImm();
84 uint8_t Order = wasm::WASM_MEM_ORDER_SEQ_CST;
85
86 // Atomic instructions always have an ordering, but if it's SEQ_CST then we
87 // don't use the relaxed-atomics encoding (even if relaxed-atomics is
88 // enabled) because the original encoding is smaller.
89 if (P2AlignIdx > 0 && Desc.operands()[P2AlignIdx - 1].OperandType ==
90 WebAssembly::OPERAND_MEMORDER) {
91 Order = MI.getOperand(i: P2AlignIdx - 1).getImm();
92 if (Order != wasm::WASM_MEM_ORDER_SEQ_CST) {
93 assert(STI.getFeatureBits()[WebAssembly::FeatureRelaxedAtomics] &&
94 "Non-default atomic ordering but feature not enabled");
95 P2Align |= wasm::WASM_MEMARG_HAS_MEM_ORDER;
96 }
97 }
98
99 encodeULEB128(Value: P2Align, OS);
100
101 // Memory index will go here once we support multi-memory.
102
103 if (P2Align & wasm::WASM_MEMARG_HAS_MEM_ORDER) {
104 support::endian::write<uint8_t>(os&: OS,
105 value: getEncodedMemOrder(Order, Opcode: MI.getOpcode()),
106 endian: llvm::endianness::little);
107 }
108}
109
110void WebAssemblyMCCodeEmitter::encodeInstruction(
111 const MCInst &MI, SmallVectorImpl<char> &CB,
112 SmallVectorImpl<MCFixup> &Fixups, const MCSubtargetInfo &STI) const {
113 raw_svector_ostream OS(CB);
114 uint64_t Start = OS.tell();
115
116 uint64_t Binary = getBinaryCodeForInstr(MI, Fixups, STI);
117 if (Binary < (1 << 8)) {
118 OS << uint8_t(Binary);
119 } else if (Binary < (1 << 16)) {
120 OS << uint8_t(Binary >> 8);
121 encodeULEB128(Value: uint8_t(Binary), OS);
122 } else if (Binary < (1 << 24)) {
123 OS << uint8_t(Binary >> 16);
124 encodeULEB128(Value: uint16_t(Binary), OS);
125 } else {
126 llvm_unreachable("Very large (prefix + 3 byte) opcodes not supported");
127 }
128
129 // For br_table instructions, encode the size of the table. In the MCInst,
130 // there's an index operand (if not a stack instruction), one operand for
131 // each table entry, and the default operand.
132 unsigned Opcode = MI.getOpcode();
133 if (Opcode == WebAssembly::BR_TABLE_I32_S ||
134 Opcode == WebAssembly::BR_TABLE_I64_S)
135 encodeULEB128(Value: MI.getNumOperands() - 1, OS);
136 if (Opcode == WebAssembly::BR_TABLE_I32 ||
137 Opcode == WebAssembly::BR_TABLE_I64)
138 encodeULEB128(Value: MI.getNumOperands() - 2, OS);
139
140 const MCInstrDesc &Desc = MCII.get(Opcode);
141 for (unsigned I = 0, E = MI.getNumOperands(); I < E; ++I) {
142 const MCOperand &MO = MI.getOperand(i: I);
143 if (MO.isReg()) {
144 /* nothing to encode */
145
146 } else if (MO.isImm()) {
147 if (I < Desc.getNumOperands()) {
148 const MCOperandInfo &Info = Desc.operands()[I];
149 LLVM_DEBUG(dbgs() << "Encoding immediate: type="
150 << int(Info.OperandType) << "\n");
151 switch (Info.OperandType) {
152 case WebAssembly::OPERAND_I32IMM:
153 encodeSLEB128(Value: int32_t(MO.getImm()), OS);
154 break;
155 case WebAssembly::OPERAND_P2ALIGN:
156 encodeP2AlignAndMemOrder(MI, P2AlignIdx: I, Desc, STI, OS, Fixups, Start);
157 break;
158 case WebAssembly::OPERAND_OFFSET32:
159 encodeULEB128(Value: uint32_t(MO.getImm()), OS);
160 break;
161 case WebAssembly::OPERAND_I64IMM:
162 encodeSLEB128(Value: MO.getImm(), OS);
163 break;
164 case WebAssembly::OPERAND_SIGNATURE:
165 case WebAssembly::OPERAND_VEC_I8IMM:
166 support::endian::write<uint8_t>(os&: OS, value: MO.getImm(),
167 endian: llvm::endianness::little);
168 break;
169 case WebAssembly::OPERAND_MEMORDER: {
170 // If there is a p2align operand (everything but fence) it is encoded
171 // together with the mem ordering (in the next iteration).
172 if (I + 1 < Desc.getNumOperands() &&
173 Desc.operands()[I + 1].OperandType ==
174 WebAssembly::OPERAND_P2ALIGN)
175 break;
176 uint8_t Val = getEncodedMemOrder(Order: MO.getImm(), Opcode);
177 if (STI.getFeatureBits()[WebAssembly::FeatureRelaxedAtomics]) {
178 support::endian::write<uint8_t>(os&: OS, value: Val, endian: llvm::endianness::little);
179 } else {
180 assert(Opcode == WebAssembly::ATOMIC_FENCE_S);
181 support::endian::write<uint8_t>(os&: OS, value: 0, endian: llvm::endianness::little);
182 }
183 break;
184 }
185 case WebAssembly::OPERAND_VEC_I16IMM:
186 support::endian::write<uint16_t>(os&: OS, value: MO.getImm(),
187 endian: llvm::endianness::little);
188 break;
189 case WebAssembly::OPERAND_VEC_I32IMM:
190 support::endian::write<uint32_t>(os&: OS, value: MO.getImm(),
191 endian: llvm::endianness::little);
192 break;
193 case WebAssembly::OPERAND_VEC_I64IMM:
194 support::endian::write<uint64_t>(os&: OS, value: MO.getImm(),
195 endian: llvm::endianness::little);
196 break;
197 case WebAssembly::OPERAND_GLOBAL:
198 Ctx.reportError(
199 L: SMLoc(),
200 Msg: Twine("Wasm globals should only be accessed symbolically!"));
201 break;
202 default:
203 encodeULEB128(Value: uint64_t(MO.getImm()), OS);
204 }
205 } else {
206 // Variadic immediate operands are br_table's destination operands or
207 // try_table's operands (# of catch clauses, catch sub-opcodes, or catch
208 // clause destinations)
209 assert(WebAssembly::isBrTable(Opcode) ||
210 Opcode == WebAssembly::TRY_TABLE_S);
211 encodeULEB128(Value: uint32_t(MO.getImm()), OS);
212 }
213
214 } else if (MO.isSFPImm()) {
215 uint32_t F = MO.getSFPImm();
216 support::endian::write<uint32_t>(os&: OS, value: F, endian: llvm::endianness::little);
217 } else if (MO.isDFPImm()) {
218 uint64_t D = MO.getDFPImm();
219 support::endian::write<uint64_t>(os&: OS, value: D, endian: llvm::endianness::little);
220 } else if (MO.isExpr()) {
221 llvm::MCFixupKind FixupKind;
222 size_t PaddedSize = 5;
223 if (I < Desc.getNumOperands()) {
224 const MCOperandInfo &Info = Desc.operands()[I];
225 switch (Info.OperandType) {
226 case WebAssembly::OPERAND_I32IMM:
227 FixupKind = WebAssembly::fixup_sleb128_i32;
228 break;
229 case WebAssembly::OPERAND_I64IMM:
230 FixupKind = WebAssembly::fixup_sleb128_i64;
231 PaddedSize = 10;
232 break;
233 case WebAssembly::OPERAND_FUNCTION32:
234 case WebAssembly::OPERAND_TABLE:
235 case WebAssembly::OPERAND_OFFSET32:
236 case WebAssembly::OPERAND_SIGNATURE:
237 case WebAssembly::OPERAND_TYPEINDEX:
238 case WebAssembly::OPERAND_GLOBAL:
239 case WebAssembly::OPERAND_TAG:
240 FixupKind = WebAssembly::fixup_uleb128_i32;
241 break;
242 case WebAssembly::OPERAND_OFFSET64:
243 FixupKind = WebAssembly::fixup_uleb128_i64;
244 PaddedSize = 10;
245 break;
246 default:
247 llvm_unreachable("unexpected symbolic operand kind");
248 }
249 } else {
250 // Variadic expr operands are try_table's catch/catch_ref clauses' tags.
251 assert(Opcode == WebAssembly::TRY_TABLE_S);
252 FixupKind = WebAssembly::fixup_uleb128_i32;
253 }
254 Fixups.push_back(
255 Elt: MCFixup::create(Offset: OS.tell() - Start, Value: MO.getExpr(), Kind: FixupKind));
256 ++MCNumFixups;
257 encodeULEB128(Value: 0, OS, PadTo: PaddedSize);
258 } else {
259 llvm_unreachable("unexpected operand kind");
260 }
261 }
262
263 ++MCNumEmitted; // Keep track of the # of mi's emitted.
264}
265
266#include "WebAssemblyGenMCCodeEmitter.inc"
267