WasmBCFrame.cpp (19008B)
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 2016 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/WasmBCFrame.h" 20 21 #include "wasm/WasmBaselineCompile.h" // For BaseLocalIter 22 #include "wasm/WasmBCClass.h" 23 24 #include "jit/MacroAssembler-inl.h" 25 #include "wasm/WasmBCClass-inl.h" 26 #include "wasm/WasmBCCodegen-inl.h" 27 #include "wasm/WasmBCRegDefs-inl.h" 28 #include "wasm/WasmBCRegMgmt-inl.h" 29 #include "wasm/WasmBCStkMgmt-inl.h" 30 31 namespace js { 32 namespace wasm { 33 34 using mozilla::Maybe; 35 using mozilla::Some; 36 37 ////////////////////////////////////////////////////////////////////////////// 38 // 39 // BaseLocalIter methods. 40 41 BaseLocalIter::BaseLocalIter(const ValTypeVector& locals, 42 const ArgTypeVector& args, bool debugEnabled) 43 : locals_(locals), 44 args_(args), 45 argsIter_(args_, ABIKind::Wasm), 46 index_(0), 47 frameSize_(0), 48 nextFrameSize_(debugEnabled ? DebugFrame::offsetOfFrame() : 0), 49 frameOffset_(INT32_MAX), 50 stackResultPointerOffset_(INT32_MAX), 51 mirType_(MIRType::Undefined), 52 done_(false) { 53 MOZ_ASSERT(args.lengthWithoutStackResults() <= locals.length()); 54 settle(); 55 } 56 57 int32_t BaseLocalIter::pushLocal(size_t nbytes) { 58 MOZ_ASSERT(nbytes % 4 == 0 && nbytes <= 16); 59 nextFrameSize_ = AlignBytes(frameSize_, nbytes) + nbytes; 60 return nextFrameSize_; // Locals grow down so capture base address. 61 } 62 63 void BaseLocalIter::settle() { 64 MOZ_ASSERT(!done_); 65 frameSize_ = nextFrameSize_; 66 67 if (!argsIter_.done()) { 68 mirType_ = argsIter_.mirType(); 69 MIRType concreteType = mirType_; 70 switch (mirType_) { 71 case MIRType::StackResults: 72 // The pointer to stack results is handled like any other argument: 73 // either addressed in place if it is passed on the stack, or we spill 74 // it in the frame if it's in a register. 75 MOZ_ASSERT(args_.isSyntheticStackResultPointerArg(index_)); 76 concreteType = MIRType::Pointer; 77 [[fallthrough]]; 78 case MIRType::Int32: 79 case MIRType::Int64: 80 case MIRType::Double: 81 case MIRType::Float32: 82 case MIRType::WasmAnyRef: 83 #ifdef ENABLE_WASM_SIMD 84 case MIRType::Simd128: 85 #endif 86 if (argsIter_->argInRegister()) { 87 frameOffset_ = pushLocal(MIRTypeToSize(concreteType)); 88 } else { 89 frameOffset_ = -(argsIter_->offsetFromArgBase() + sizeof(Frame)); 90 } 91 break; 92 default: 93 MOZ_CRASH("Argument type"); 94 } 95 if (mirType_ == MIRType::StackResults) { 96 stackResultPointerOffset_ = frameOffset(); 97 // Advance past the synthetic stack result pointer argument and fall 98 // through to the next case. 99 argsIter_++; 100 frameSize_ = nextFrameSize_; 101 MOZ_ASSERT(argsIter_.done()); 102 } else { 103 return; 104 } 105 } 106 107 if (index_ < locals_.length()) { 108 switch (locals_[index_].kind()) { 109 case ValType::I32: 110 case ValType::I64: 111 case ValType::F32: 112 case ValType::F64: 113 #ifdef ENABLE_WASM_SIMD 114 case ValType::V128: 115 #endif 116 case ValType::Ref: 117 mirType_ = locals_[index_].toMIRType(); 118 frameOffset_ = pushLocal(MIRTypeToSize(mirType_)); 119 break; 120 default: 121 MOZ_CRASH("Compiler bug: Unexpected local type"); 122 } 123 return; 124 } 125 126 done_ = true; 127 } 128 129 void BaseLocalIter::operator++(int) { 130 MOZ_ASSERT(!done_); 131 index_++; 132 if (!argsIter_.done()) { 133 argsIter_++; 134 } 135 settle(); 136 } 137 138 ////////////////////////////////////////////////////////////////////////////// 139 // 140 // Stack map methods. 141 142 bool BaseCompiler::createStackMap(const char* who) { 143 const ExitStubMapVector noExtras; 144 StackMap* stackMap; 145 return stackMapGenerator_.createStackMap( 146 who, noExtras, HasDebugFrameWithLiveRefs::No, stk_, &stackMap) && 147 (!stackMap || stackMaps_->add(masm.currentOffset(), stackMap)); 148 } 149 150 bool BaseCompiler::createStackMap(const char* who, CodeOffset assemblerOffset) { 151 const ExitStubMapVector noExtras; 152 StackMap* stackMap; 153 return stackMapGenerator_.createStackMap( 154 who, noExtras, HasDebugFrameWithLiveRefs::No, stk_, &stackMap) && 155 (!stackMap || stackMaps_->add(assemblerOffset.offset(), stackMap)); 156 } 157 158 bool BaseCompiler::createStackMap( 159 const char* who, HasDebugFrameWithLiveRefs debugFrameWithLiveRefs) { 160 const ExitStubMapVector noExtras; 161 StackMap* stackMap; 162 return stackMapGenerator_.createStackMap( 163 who, noExtras, debugFrameWithLiveRefs, stk_, &stackMap) && 164 (!stackMap || stackMaps_->add(masm.currentOffset(), stackMap)); 165 } 166 167 bool MachineStackTracker::cloneTo(MachineStackTracker* dst) { 168 MOZ_ASSERT(dst->vec_.empty()); 169 if (!dst->vec_.appendAll(vec_)) { 170 return false; 171 } 172 dst->numPtrs_ = numPtrs_; 173 return true; 174 } 175 176 bool StackMapGenerator::generateStackmapEntriesForTrapExit( 177 const ArgTypeVector& args, ExitStubMapVector* extras) { 178 return GenerateStackmapEntriesForTrapExit(args, trapExitLayout_, 179 trapExitLayoutNumWords_, extras); 180 } 181 182 bool StackMapGenerator::createStackMap( 183 const char* who, const ExitStubMapVector& extras, 184 HasDebugFrameWithLiveRefs debugFrameWithLiveRefs, const StkVector& stk, 185 wasm::StackMap** result) { 186 // Always initialize the result value 187 *result = nullptr; 188 189 size_t countedPointers = machineStackTracker.numPtrs() + memRefsOnStk; 190 #ifndef DEBUG 191 // An important optimization. If there are obviously no pointers, as 192 // we expect in the majority of cases, exit quickly. 193 if (countedPointers == 0 && 194 debugFrameWithLiveRefs == HasDebugFrameWithLiveRefs::No) { 195 // We can skip creating the map if there are no |true| elements in 196 // |extras|. 197 bool extrasHasRef = false; 198 for (bool b : extras) { 199 if (b) { 200 extrasHasRef = true; 201 break; 202 } 203 } 204 if (!extrasHasRef) { 205 return true; 206 } 207 } 208 #else 209 // In the debug case, create the stackmap regardless, and cross-check 210 // the pointer-counting below. We expect the final map to have 211 // |countedPointers| in total. This doesn't include those in the 212 // DebugFrame, but they do not appear in the map's bitmap. Note that 213 // |countedPointers| is debug-only from this point onwards. 214 for (bool b : extras) { 215 countedPointers += (b ? 1 : 0); 216 } 217 #endif 218 219 // Start with the frame-setup map, and add operand-stack information to 220 // that. augmentedMst holds live data only within individual calls to 221 // createStackMap. 222 augmentedMst.clear(); 223 if (!machineStackTracker.cloneTo(&augmentedMst)) { 224 return false; 225 } 226 227 // At this point, augmentedMst only contains entries covering the 228 // incoming argument area (if any) and for the area allocated by this 229 // function's prologue. We now need to calculate how far the machine's 230 // stack pointer is below where it was at the start of the body. But we 231 // must take care not to include any words pushed as arguments to an 232 // upcoming function call, since those words belong to the stackmap of 233 // the callee, not to the stackmap of this function. Any alignment padding 234 // for the args also belongs to the callee. 235 // 236 // The only padding belonging to the stackmap of this function is that 237 // required to align the upcoming frame. This is accounted for where 238 // framePushedExcludingOutboundCallArgs is set, in startCallArgs(), and is 239 // comprised of just one component: 240 // 241 // * call->frameAlignAdjustment 242 Maybe<uint32_t> framePushedExcludingArgs; 243 if (framePushedAtEntryToBody.isNothing()) { 244 // Still in the prologue. framePushedExcludingArgs remains Nothing. 245 MOZ_ASSERT(framePushedExcludingOutboundCallArgs.isNothing()); 246 } else { 247 // In the body. 248 MOZ_ASSERT(masm_.framePushed() >= framePushedAtEntryToBody.value()); 249 if (framePushedExcludingOutboundCallArgs.isSome()) { 250 // In the body, and we've potentially pushed some args onto the stack. 251 // We must ignore them when sizing the stackmap. 252 MOZ_ASSERT(masm_.framePushed() >= 253 framePushedExcludingOutboundCallArgs.value()); 254 MOZ_ASSERT(framePushedExcludingOutboundCallArgs.value() >= 255 framePushedAtEntryToBody.value()); 256 framePushedExcludingArgs = 257 Some(framePushedExcludingOutboundCallArgs.value()); 258 } else { 259 // In the body, but not with call args on the stack. The stackmap 260 // must be sized so as to extend all the way "down" to 261 // masm_.framePushed(). 262 framePushedExcludingArgs = Some(masm_.framePushed()); 263 } 264 } 265 266 if (framePushedExcludingArgs.isSome()) { 267 uint32_t bodyPushedBytes = 268 framePushedExcludingArgs.value() - framePushedAtEntryToBody.value(); 269 MOZ_ASSERT(0 == bodyPushedBytes % sizeof(void*)); 270 if (!augmentedMst.pushNonGCPointers(bodyPushedBytes / sizeof(void*))) { 271 return false; 272 } 273 } 274 275 // Scan the operand stack, marking pointers in the just-added new 276 // section. 277 MOZ_ASSERT_IF(framePushedAtEntryToBody.isNothing(), stk.empty()); 278 MOZ_ASSERT_IF(framePushedExcludingArgs.isNothing(), stk.empty()); 279 280 for (const Stk& v : stk) { 281 #ifndef DEBUG 282 // We don't track roots in registers, per rationale below, so if this 283 // doesn't hold, something is seriously wrong, and we're likely to get a 284 // GC-related crash. 285 MOZ_RELEASE_ASSERT(v.kind() != Stk::RegisterRef); 286 if (v.kind() != Stk::MemRef) { 287 continue; 288 } 289 #else 290 // Take the opportunity to check everything we reasonably can about 291 // operand stack elements. 292 switch (v.kind()) { 293 case Stk::MemI32: 294 case Stk::MemI64: 295 case Stk::MemF32: 296 case Stk::MemF64: 297 case Stk::ConstI32: 298 case Stk::ConstI64: 299 case Stk::ConstF32: 300 case Stk::ConstF64: 301 # ifdef ENABLE_WASM_SIMD 302 case Stk::MemV128: 303 case Stk::ConstV128: 304 # endif 305 // All of these have uninteresting type. 306 continue; 307 case Stk::LocalI32: 308 case Stk::LocalI64: 309 case Stk::LocalF32: 310 case Stk::LocalF64: 311 # ifdef ENABLE_WASM_SIMD 312 case Stk::LocalV128: 313 # endif 314 // These also have uninteresting type. Check that they live in the 315 // section of stack set up by beginFunction(). The unguarded use of 316 // |value()| here is safe due to the assertion above this loop. 317 MOZ_ASSERT(v.offs() <= framePushedAtEntryToBody.value()); 318 continue; 319 case Stk::RegisterI32: 320 case Stk::RegisterI64: 321 case Stk::RegisterF32: 322 case Stk::RegisterF64: 323 # ifdef ENABLE_WASM_SIMD 324 case Stk::RegisterV128: 325 # endif 326 // These also have uninteresting type, but more to the point: all 327 // registers holding live values should have been flushed to the 328 // machine stack immediately prior to the instruction to which this 329 // stackmap pertains. So these can't happen. 330 MOZ_CRASH("createStackMap: operand stack has Register-non-Ref"); 331 case Stk::MemRef: 332 // This is the only case we care about. We'll handle it after the 333 // switch. 334 break; 335 case Stk::LocalRef: 336 // We need the stackmap to mention this pointer, but it should 337 // already be in the machineStackTracker section created by 338 // beginFunction(). 339 MOZ_ASSERT(v.offs() <= framePushedAtEntryToBody.value()); 340 continue; 341 case Stk::ConstRef: 342 // This can currently only be a null pointer. 343 MOZ_ASSERT(v.refval() == 0); 344 continue; 345 case Stk::RegisterRef: 346 // This can't happen, per rationale above. 347 MOZ_CRASH("createStackMap: operand stack contains RegisterRef"); 348 default: 349 MOZ_CRASH("createStackMap: unknown operand stack element"); 350 } 351 #endif 352 // v.offs() holds masm.framePushed() at the point immediately after it 353 // was pushed on the stack. Since it's still on the stack, 354 // masm.framePushed() can't be less. 355 MOZ_ASSERT(v.offs() <= framePushedExcludingArgs.value()); 356 uint32_t offsFromMapLowest = framePushedExcludingArgs.value() - v.offs(); 357 MOZ_ASSERT(0 == offsFromMapLowest % sizeof(void*)); 358 augmentedMst.setGCPointer(offsFromMapLowest / sizeof(void*)); 359 } 360 361 MOZ_ASSERT(numStackArgBytes % sizeof(void*) == 0); 362 const size_t numStackArgWords = numStackArgBytes / sizeof(void*); 363 const size_t numStackArgPaddingBytes = 364 AlignStackArgAreaSize(numStackArgBytes) - numStackArgBytes; 365 const size_t numStackArgPaddingWords = 366 numStackArgPaddingBytes / sizeof(void*); 367 368 // Create the final StackMap. The initial map is zeroed out, so there's 369 // no need to write zero bits in it. 370 const uint32_t extraWords = extras.length(); 371 const uint32_t augmentedMstWords = augmentedMst.length(); 372 const uint32_t numMappedWords = 373 numStackArgPaddingWords + extraWords + augmentedMstWords; 374 StackMap* stackMap = stackMaps_->create(numMappedWords); 375 if (!stackMap) { 376 return false; 377 } 378 379 { 380 // First the exit stub extra words, if any. 381 uint32_t i = 0; 382 for (bool b : extras) { 383 if (b) { 384 stackMap->set(i, StackMap::Kind::AnyRef); 385 } 386 i++; 387 } 388 } 389 { 390 // Followed by the "main" part of the map. 391 // 392 // This is really just a bit-array copy, so it is reasonable to ask 393 // whether the representation of MachineStackTracker could be made more 394 // similar to that of StackMap, so that the copy could be done with 395 // `memcpy`. Unfortunately it's not so simple; see comment on `class 396 // MachineStackTracker` for details. 397 MachineStackTracker::Iter iter(augmentedMst); 398 while (true) { 399 size_t i = iter.get(); 400 if (i == MachineStackTracker::Iter::FINISHED) { 401 break; 402 } 403 stackMap->set(extraWords + i, StackMap::Kind::AnyRef); 404 } 405 } 406 407 stackMap->setExitStubWords(extraWords); 408 409 // Record in the map, how far down from the highest address the Frame* is. 410 // Take the opportunity to check that we haven't marked any part of the 411 // Frame itself as a pointer. 412 stackMap->setFrameOffsetFromTop(numStackArgPaddingWords + numStackArgWords + 413 sizeof(Frame) / sizeof(void*)); 414 #ifdef DEBUG 415 for (uint32_t i = 0; i < sizeof(Frame) / sizeof(void*); i++) { 416 MOZ_ASSERT(stackMap->get(stackMap->header.numMappedWords - 417 stackMap->header.frameOffsetFromTop + i) == 418 StackMap::Kind::POD); 419 } 420 #endif 421 422 // Note the presence of a DebugFrame with live pointers, if any. 423 if (debugFrameWithLiveRefs != HasDebugFrameWithLiveRefs::No) { 424 stackMap->setHasDebugFrameWithLiveRefs(); 425 } 426 427 #ifdef DEBUG 428 { 429 // Crosscheck the map pointer counting. 430 uint32_t nw = stackMap->header.numMappedWords; 431 uint32_t np = 0; 432 for (uint32_t i = 0; i < nw; i++) { 433 if (stackMap->get(i) == StackMap::Kind::AnyRef) { 434 np += 1; 435 } 436 } 437 MOZ_ASSERT(size_t(np) == countedPointers); 438 } 439 #endif 440 441 *result = stackMaps_->finalize(stackMap); 442 return true; 443 } 444 445 ////////////////////////////////////////////////////////////////////////////// 446 // 447 // Stack frame methods. 448 449 void BaseStackFrame::zeroLocals(BaseRegAlloc* ra) { 450 MOZ_ASSERT(varLow_ != UINT32_MAX); 451 452 if (varLow_ == varHigh_) { 453 return; 454 } 455 456 static const uint32_t wordSize = sizeof(void*); 457 458 // The adjustments to 'low' by the size of the item being stored compensates 459 // for the fact that locals offsets are the offsets from Frame to the bytes 460 // directly "above" the locals in the locals area. See comment at Local. 461 462 // On 64-bit systems we may have 32-bit alignment for the local area as it 463 // may be preceded by parameters and prologue/debug data. 464 465 uint32_t low = varLow_; 466 if (low % wordSize) { 467 masm.store32(Imm32(0), Address(sp_, localOffset(low + 4))); 468 low += 4; 469 } 470 MOZ_ASSERT(low % wordSize == 0); 471 472 const uint32_t high = AlignBytes(varHigh_, wordSize); 473 474 // An UNROLL_LIMIT of 16 is chosen so that we only need an 8-bit signed 475 // immediate to represent the offset in the store instructions in the loop 476 // on x64. 477 478 const uint32_t UNROLL_LIMIT = 16; 479 const uint32_t initWords = (high - low) / wordSize; 480 const uint32_t tailWords = initWords % UNROLL_LIMIT; 481 const uint32_t loopHigh = high - (tailWords * wordSize); 482 483 // With only one word to initialize, just store an immediate zero. 484 485 if (initWords == 1) { 486 masm.storePtr(ImmWord(0), Address(sp_, localOffset(low + wordSize))); 487 return; 488 } 489 490 // For other cases, it's best to have a zero in a register. 491 // 492 // One can do more here with SIMD registers (store 16 bytes at a time) or 493 // with instructions like STRD on ARM (store 8 bytes at a time), but that's 494 // for another day. 495 496 RegI32 zero = ra->needI32(); 497 masm.mov(ImmWord(0), zero); 498 499 // For the general case we want to have a loop body of UNROLL_LIMIT stores 500 // and then a tail of less than UNROLL_LIMIT stores. When initWords is less 501 // than 2*UNROLL_LIMIT the loop trip count is at most 1 and there is no 502 // benefit to having the pointer calculations and the compare-and-branch. 503 // So we completely unroll when we have initWords < 2 * UNROLL_LIMIT. (In 504 // this case we'll end up using 32-bit offsets on x64 for up to half of the 505 // stores, though.) 506 507 // Fully-unrolled case. 508 509 if (initWords < 2 * UNROLL_LIMIT) { 510 for (uint32_t i = low; i < high; i += wordSize) { 511 masm.storePtr(zero, Address(sp_, localOffset(i + wordSize))); 512 } 513 ra->freeI32(zero); 514 return; 515 } 516 517 // Unrolled loop with a tail. Stores will use negative offsets. That's OK 518 // for x86 and ARM, at least. 519 520 // Compute pointer to the highest-addressed slot on the frame. 521 RegI32 p = ra->needI32(); 522 masm.computeEffectiveAddress(Address(sp_, localOffset(low + wordSize)), p); 523 524 // Compute pointer to the lowest-addressed slot on the frame that will be 525 // initialized by the loop body. 526 RegI32 lim = ra->needI32(); 527 masm.computeEffectiveAddress(Address(sp_, localOffset(loopHigh + wordSize)), 528 lim); 529 530 // The loop body. Eventually we'll have p == lim and exit the loop. 531 Label again; 532 masm.bind(&again); 533 for (uint32_t i = 0; i < UNROLL_LIMIT; ++i) { 534 masm.storePtr(zero, Address(p, -(wordSize * i))); 535 } 536 masm.subPtr(Imm32(UNROLL_LIMIT * wordSize), p); 537 masm.branchPtr(Assembler::LessThan, lim, p, &again); 538 539 // The tail. 540 for (uint32_t i = 0; i < tailWords; ++i) { 541 masm.storePtr(zero, Address(p, -(wordSize * i))); 542 } 543 544 ra->freeI32(p); 545 ra->freeI32(lim); 546 ra->freeI32(zero); 547 } 548 549 } // namespace wasm 550 } // namespace js