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