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 namespace js {
24 namespace wasm {
25
ConvertStackMapBoolVectorToStackMap(const StackMapBoolVector & vec,bool hasRefs)26 wasm::StackMap* ConvertStackMapBoolVectorToStackMap(
27 const StackMapBoolVector& vec, bool hasRefs) {
28 wasm::StackMap* stackMap = wasm::StackMap::create(vec.length());
29 if (!stackMap) {
30 return nullptr;
31 }
32
33 bool hasRefsObserved = false;
34 size_t i = 0;
35 for (bool b : vec) {
36 if (b) {
37 stackMap->setBit(i);
38 hasRefsObserved = true;
39 }
40 i++;
41 }
42 MOZ_RELEASE_ASSERT(hasRefs == hasRefsObserved);
43
44 return stackMap;
45 }
46
47 // Generate a stackmap for a function's stack-overflow-at-entry trap, with
48 // the structure:
49 //
50 // <reg dump area>
51 // | ++ <space reserved before trap, if any>
52 // | ++ <space for Frame>
53 // | ++ <inbound arg area>
54 // | |
55 // Lowest Addr Highest Addr
56 //
57 // The caller owns the resulting stackmap. This assumes a grow-down stack.
58 //
59 // For non-debug builds, if the stackmap would contain no pointers, no
60 // stackmap is created, and nullptr is returned. For a debug build, a
61 // stackmap is always created and returned.
62 //
63 // The "space reserved before trap" is the space reserved by
64 // MacroAssembler::wasmReserveStackChecked, in the case where the frame is
65 // "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)66 bool CreateStackMapForFunctionEntryTrap(const wasm::ArgTypeVector& argTypes,
67 const MachineState& trapExitLayout,
68 size_t trapExitLayoutWords,
69 size_t nBytesReservedBeforeTrap,
70 size_t nInboundStackArgBytes,
71 wasm::StackMap** result) {
72 // Ensure this is defined on all return paths.
73 *result = nullptr;
74
75 // The size of the wasm::Frame itself.
76 const size_t nFrameBytes = sizeof(wasm::Frame);
77
78 // The size of the register dump (trap) area.
79 const size_t trapExitLayoutBytes = trapExitLayoutWords * sizeof(void*);
80
81 // This is the total number of bytes covered by the map.
82 const DebugOnly<size_t> nTotalBytes = trapExitLayoutBytes +
83 nBytesReservedBeforeTrap + nFrameBytes +
84 nInboundStackArgBytes;
85
86 // Create the stackmap initially in this vector. Since most frames will
87 // contain 128 or fewer words, heap allocation is avoided in the majority of
88 // cases. vec[0] is for the lowest address in the map, vec[N-1] is for the
89 // highest address in the map.
90 StackMapBoolVector vec;
91
92 // Keep track of whether we've actually seen any refs.
93 bool hasRefs = false;
94
95 // REG DUMP AREA
96 wasm::ExitStubMapVector trapExitExtras;
97 if (!GenerateStackmapEntriesForTrapExit(
98 argTypes, trapExitLayout, trapExitLayoutWords, &trapExitExtras)) {
99 return false;
100 }
101 MOZ_ASSERT(trapExitExtras.length() == trapExitLayoutWords);
102
103 if (!vec.appendN(false, trapExitLayoutWords)) {
104 return false;
105 }
106 for (size_t i = 0; i < trapExitLayoutWords; i++) {
107 vec[i] = trapExitExtras[i];
108 hasRefs |= vec[i];
109 }
110
111 // SPACE RESERVED BEFORE TRAP
112 MOZ_ASSERT(nBytesReservedBeforeTrap % sizeof(void*) == 0);
113 if (!vec.appendN(false, nBytesReservedBeforeTrap / sizeof(void*))) {
114 return false;
115 }
116
117 // SPACE FOR FRAME
118 if (!vec.appendN(false, nFrameBytes / sizeof(void*))) {
119 return false;
120 }
121
122 // INBOUND ARG AREA
123 MOZ_ASSERT(nInboundStackArgBytes % sizeof(void*) == 0);
124 const size_t numStackArgWords = nInboundStackArgBytes / sizeof(void*);
125
126 const size_t wordsSoFar = vec.length();
127 if (!vec.appendN(false, numStackArgWords)) {
128 return false;
129 }
130
131 for (ABIArgIter i(argTypes); !i.done(); i++) {
132 ABIArg argLoc = *i;
133 if (argLoc.kind() == ABIArg::Stack &&
134 argTypes[i.index()] == MIRType::RefOrNull) {
135 uint32_t offset = argLoc.offsetFromArgBase();
136 MOZ_ASSERT(offset < nInboundStackArgBytes);
137 MOZ_ASSERT(offset % sizeof(void*) == 0);
138 vec[wordsSoFar + offset / sizeof(void*)] = true;
139 hasRefs = true;
140 }
141 }
142
143 #ifndef DEBUG
144 // We saw no references, and this is a non-debug build, so don't bother
145 // building the stackmap.
146 if (!hasRefs) {
147 return true;
148 }
149 #endif
150
151 // Convert vec into a wasm::StackMap.
152 MOZ_ASSERT(vec.length() * sizeof(void*) == nTotalBytes);
153 wasm::StackMap* stackMap = ConvertStackMapBoolVectorToStackMap(vec, hasRefs);
154 if (!stackMap) {
155 return false;
156 }
157 stackMap->setExitStubWords(trapExitLayoutWords);
158
159 stackMap->setFrameOffsetFromTop(nFrameBytes / sizeof(void*) +
160 numStackArgWords);
161 #ifdef DEBUG
162 for (uint32_t i = 0; i < nFrameBytes / sizeof(void*); i++) {
163 MOZ_ASSERT(stackMap->getBit(stackMap->numMappedWords -
164 stackMap->frameOffsetFromTop + i) == 0);
165 }
166 #endif
167
168 *result = stackMap;
169 return true;
170 }
171
GenerateStackmapEntriesForTrapExit(const ArgTypeVector & args,const MachineState & trapExitLayout,const size_t trapExitLayoutNumWords,ExitStubMapVector * extras)172 bool GenerateStackmapEntriesForTrapExit(const ArgTypeVector& args,
173 const MachineState& trapExitLayout,
174 const size_t trapExitLayoutNumWords,
175 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 (ABIArgIter 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 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 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 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 } // namespace wasm
261 } // namespace js
262