1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 *
4 * Copyright 2019 Mozilla Foundation
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19 #include "wasm/WasmGC.h"
20 #include "wasm/WasmInstance.h"
21 #include "jit/MacroAssembler-inl.h"
22
23 using mozilla::DebugOnly;
24
25 using namespace js;
26 using namespace js::jit;
27 using namespace js::wasm;
28
ConvertStackMapBoolVectorToStackMap(const StackMapBoolVector & vec,bool hasRefs)29 wasm::StackMap* wasm::ConvertStackMapBoolVectorToStackMap(
30 const StackMapBoolVector& vec, bool hasRefs) {
31 wasm::StackMap* stackMap = wasm::StackMap::create(vec.length());
32 if (!stackMap) {
33 return nullptr;
34 }
35
36 bool hasRefsObserved = false;
37 size_t i = 0;
38 for (bool b : vec) {
39 if (b) {
40 stackMap->setBit(i);
41 hasRefsObserved = true;
42 }
43 i++;
44 }
45 MOZ_RELEASE_ASSERT(hasRefs == hasRefsObserved);
46
47 return stackMap;
48 }
49
50 // Generate a stackmap for a function's stack-overflow-at-entry trap, with
51 // the structure:
52 //
53 // <reg dump area>
54 // | ++ <space reserved before trap, if any>
55 // | ++ <space for Frame>
56 // | ++ <inbound arg area>
57 // | |
58 // Lowest Addr Highest Addr
59 //
60 // The caller owns the resulting stackmap. This assumes a grow-down stack.
61 //
62 // For non-debug builds, if the stackmap would contain no pointers, no
63 // stackmap is created, and nullptr is returned. For a debug build, a
64 // stackmap is always created and returned.
65 //
66 // The "space reserved before trap" is the space reserved by
67 // MacroAssembler::wasmReserveStackChecked, in the case where the frame is
68 // "small", as determined by that function.
CreateStackMapForFunctionEntryTrap(const wasm::ArgTypeVector & argTypes,const MachineState & trapExitLayout,size_t trapExitLayoutWords,size_t nBytesReservedBeforeTrap,size_t nInboundStackArgBytes,wasm::StackMap ** result)69 bool wasm::CreateStackMapForFunctionEntryTrap(
70 const wasm::ArgTypeVector& argTypes, const MachineState& trapExitLayout,
71 size_t trapExitLayoutWords, size_t nBytesReservedBeforeTrap,
72 size_t nInboundStackArgBytes, wasm::StackMap** result) {
73 // Ensure this is defined on all return paths.
74 *result = nullptr;
75
76 // The size of the wasm::Frame itself.
77 const size_t nFrameBytes = sizeof(wasm::Frame);
78
79 // The size of the register dump (trap) area.
80 const size_t trapExitLayoutBytes = trapExitLayoutWords * sizeof(void*);
81
82 // This is the total number of bytes covered by the map.
83 const DebugOnly<size_t> nTotalBytes = trapExitLayoutBytes +
84 nBytesReservedBeforeTrap + nFrameBytes +
85 nInboundStackArgBytes;
86
87 // Create the stackmap initially in this vector. Since most frames will
88 // contain 128 or fewer words, heap allocation is avoided in the majority of
89 // cases. vec[0] is for the lowest address in the map, vec[N-1] is for the
90 // highest address in the map.
91 StackMapBoolVector vec;
92
93 // Keep track of whether we've actually seen any refs.
94 bool hasRefs = false;
95
96 // REG DUMP AREA
97 wasm::ExitStubMapVector trapExitExtras;
98 if (!GenerateStackmapEntriesForTrapExit(
99 argTypes, trapExitLayout, trapExitLayoutWords, &trapExitExtras)) {
100 return false;
101 }
102 MOZ_ASSERT(trapExitExtras.length() == trapExitLayoutWords);
103
104 if (!vec.appendN(false, trapExitLayoutWords)) {
105 return false;
106 }
107 for (size_t i = 0; i < trapExitLayoutWords; i++) {
108 vec[i] = trapExitExtras[i];
109 hasRefs |= vec[i];
110 }
111
112 // SPACE RESERVED BEFORE TRAP
113 MOZ_ASSERT(nBytesReservedBeforeTrap % sizeof(void*) == 0);
114 if (!vec.appendN(false, nBytesReservedBeforeTrap / sizeof(void*))) {
115 return false;
116 }
117
118 // SPACE FOR FRAME
119 if (!vec.appendN(false, nFrameBytes / sizeof(void*))) {
120 return false;
121 }
122
123 // INBOUND ARG AREA
124 MOZ_ASSERT(nInboundStackArgBytes % sizeof(void*) == 0);
125 const size_t numStackArgWords = nInboundStackArgBytes / sizeof(void*);
126
127 const size_t wordsSoFar = vec.length();
128 if (!vec.appendN(false, numStackArgWords)) {
129 return false;
130 }
131
132 for (WasmABIArgIter i(argTypes); !i.done(); i++) {
133 ABIArg argLoc = *i;
134 if (argLoc.kind() == ABIArg::Stack &&
135 argTypes[i.index()] == MIRType::RefOrNull) {
136 uint32_t offset = argLoc.offsetFromArgBase();
137 MOZ_ASSERT(offset < nInboundStackArgBytes);
138 MOZ_ASSERT(offset % sizeof(void*) == 0);
139 vec[wordsSoFar + offset / sizeof(void*)] = true;
140 hasRefs = true;
141 }
142 }
143
144 #ifndef DEBUG
145 // We saw no references, and this is a non-debug build, so don't bother
146 // building the stackmap.
147 if (!hasRefs) {
148 return true;
149 }
150 #endif
151
152 // Convert vec into a wasm::StackMap.
153 MOZ_ASSERT(vec.length() * sizeof(void*) == nTotalBytes);
154 wasm::StackMap* stackMap = ConvertStackMapBoolVectorToStackMap(vec, hasRefs);
155 if (!stackMap) {
156 return false;
157 }
158 stackMap->setExitStubWords(trapExitLayoutWords);
159
160 stackMap->setFrameOffsetFromTop(nFrameBytes / sizeof(void*) +
161 numStackArgWords);
162 #ifdef DEBUG
163 for (uint32_t i = 0; i < nFrameBytes / sizeof(void*); i++) {
164 MOZ_ASSERT(stackMap->getBit(stackMap->numMappedWords -
165 stackMap->frameOffsetFromTop + i) == 0);
166 }
167 #endif
168
169 *result = stackMap;
170 return true;
171 }
172
GenerateStackmapEntriesForTrapExit(const ArgTypeVector & args,const MachineState & trapExitLayout,const size_t trapExitLayoutNumWords,ExitStubMapVector * extras)173 bool wasm::GenerateStackmapEntriesForTrapExit(
174 const ArgTypeVector& args, const MachineState& trapExitLayout,
175 const size_t trapExitLayoutNumWords, ExitStubMapVector* extras) {
176 MOZ_ASSERT(extras->empty());
177
178 // If this doesn't hold, we can't distinguish saved and not-saved
179 // registers in the MachineState. See MachineState::MachineState().
180 MOZ_ASSERT(trapExitLayoutNumWords < 0x100);
181
182 if (!extras->appendN(false, trapExitLayoutNumWords)) {
183 return false;
184 }
185
186 for (WasmABIArgIter i(args); !i.done(); i++) {
187 if (!i->argInRegister() || i.mirType() != MIRType::RefOrNull) {
188 continue;
189 }
190
191 size_t offsetFromTop =
192 reinterpret_cast<size_t>(trapExitLayout.address(i->gpr()));
193
194 // If this doesn't hold, the associated register wasn't saved by
195 // the trap exit stub. Better to crash now than much later, in
196 // some obscure place, and possibly with security consequences.
197 MOZ_RELEASE_ASSERT(offsetFromTop < trapExitLayoutNumWords);
198
199 // offsetFromTop is an offset in words down from the highest
200 // address in the exit stub save area. Switch it around to be an
201 // offset up from the bottom of the (integer register) save area.
202 size_t offsetFromBottom = trapExitLayoutNumWords - 1 - offsetFromTop;
203
204 (*extras)[offsetFromBottom] = true;
205 }
206
207 return true;
208 }
209
EmitWasmPreBarrierGuard(MacroAssembler & masm,Register tls,Register scratch,Register valueAddr,Label * skipBarrier)210 void wasm::EmitWasmPreBarrierGuard(MacroAssembler& masm, Register tls,
211 Register scratch, Register valueAddr,
212 Label* skipBarrier) {
213 // If no incremental GC has started, we don't need the barrier.
214 masm.loadPtr(
215 Address(tls, offsetof(TlsData, addressOfNeedsIncrementalBarrier)),
216 scratch);
217 masm.branchTest32(Assembler::Zero, Address(scratch, 0), Imm32(0x1),
218 skipBarrier);
219
220 // If the previous value is null, we don't need the barrier.
221 masm.loadPtr(Address(valueAddr, 0), scratch);
222 masm.branchTestPtr(Assembler::Zero, scratch, scratch, skipBarrier);
223 }
224
EmitWasmPreBarrierCall(MacroAssembler & masm,Register tls,Register scratch,Register valueAddr)225 void wasm::EmitWasmPreBarrierCall(MacroAssembler& masm, Register tls,
226 Register scratch, Register valueAddr) {
227 MOZ_ASSERT(valueAddr == PreBarrierReg);
228
229 masm.loadPtr(Address(tls, offsetof(TlsData, instance)), scratch);
230 masm.loadPtr(Address(scratch, Instance::offsetOfPreBarrierCode()), scratch);
231 #if defined(DEBUG) && defined(JS_CODEGEN_ARM64)
232 // The prebarrier assumes that x28 == sp.
233 Label ok;
234 masm.Cmp(sp, vixl::Operand(x28));
235 masm.B(&ok, Assembler::Equal);
236 masm.breakpoint();
237 masm.bind(&ok);
238 #endif
239 masm.call(scratch);
240 }
241
EmitWasmPostBarrierGuard(MacroAssembler & masm,const Maybe<Register> & object,Register otherScratch,Register setValue,Label * skipBarrier)242 void wasm::EmitWasmPostBarrierGuard(MacroAssembler& masm,
243 const Maybe<Register>& object,
244 Register otherScratch, Register setValue,
245 Label* skipBarrier) {
246 // If the pointer being stored is null, no barrier.
247 masm.branchTestPtr(Assembler::Zero, setValue, setValue, skipBarrier);
248
249 // If there is a containing object and it is in the nursery, no barrier.
250 if (object) {
251 masm.branchPtrInNurseryChunk(Assembler::Equal, *object, otherScratch,
252 skipBarrier);
253 }
254
255 // If the pointer being stored is to a tenured object, no barrier.
256 masm.branchPtrInNurseryChunk(Assembler::NotEqual, setValue, otherScratch,
257 skipBarrier);
258 }
259
260 #ifdef DEBUG
IsValidStackMapKey(bool debugEnabled,const uint8_t * nextPC)261 bool wasm::IsValidStackMapKey(bool debugEnabled, const uint8_t* nextPC) {
262 # if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
263 const uint8_t* insn = nextPC;
264 return (insn[-2] == 0x0F && insn[-1] == 0x0B) || // ud2
265 (insn[-2] == 0xFF && (insn[-1] & 0xF8) == 0xD0) || // call *%r_
266 insn[-5] == 0xE8 || // call simm32
267 (debugEnabled && insn[-5] == 0x0F && insn[-4] == 0x1F &&
268 insn[-3] == 0x44 && insn[-2] == 0x00 &&
269 insn[-1] == 0x00); // nop_five
270
271 # elif defined(JS_CODEGEN_ARM)
272 const uint32_t* insn = (const uint32_t*)nextPC;
273 return ((uintptr_t(insn) & 3) == 0) && // must be ARM, not Thumb
274 (insn[-1] == 0xe7f000f0 || // udf
275 (insn[-1] & 0xfffffff0) == 0xe12fff30 || // blx reg (ARM, enc A1)
276 (insn[-1] & 0xff000000) == 0xeb000000 || // bl simm24 (ARM, enc A1)
277 (debugEnabled && insn[-1] == 0xe320f000)); // "as_nop"
278
279 # elif defined(JS_CODEGEN_ARM64)
280 const uint32_t hltInsn = 0xd4a00000;
281 const uint32_t* insn = (const uint32_t*)nextPC;
282 return ((uintptr_t(insn) & 3) == 0) &&
283 (insn[-1] == hltInsn || // hlt
284 (insn[-1] & 0xfffffc1f) == 0xd63f0000 || // blr reg
285 (insn[-1] & 0xfc000000) == 0x94000000 || // bl simm26
286 (debugEnabled && insn[-1] == 0xd503201f)); // nop
287
288 # elif defined(JS_CODEGEN_MIPS64)
289 // TODO (bug 1699696): Implement this. As for the platforms above, we need to
290 // enumerate all code sequences that can precede the stackmap location.
291 return true;
292 # elif defined(JS_CODEGEN_LOONG64)
293 // TODO(loong64): Implement IsValidStackMapKey.
294 return true;
295 # else
296 MOZ_CRASH("IsValidStackMapKey: requires implementation on this platform");
297 # endif
298 }
299 #endif
300