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