WasmTypeDef.cpp (18428B)
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 2015 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/WasmTypeDef.h" 20 21 #include "mozilla/MathAlgorithms.h" 22 23 #include "jit/JitOptions.h" 24 #include "js/friend/ErrorMessages.h" // JSMSG_* 25 #include "js/HashTable.h" 26 #include "js/Printf.h" 27 #include "js/Value.h" 28 #include "threading/ExclusiveData.h" 29 #include "vm/Runtime.h" 30 #include "vm/StringType.h" 31 #include "wasm/WasmCodegenConstants.h" 32 #include "wasm/WasmGcObject.h" 33 #include "wasm/WasmJS.h" 34 35 #include "gc/ObjectKind-inl.h" 36 37 using namespace js; 38 using namespace js::jit; 39 using namespace js::wasm; 40 41 using mozilla::CheckedInt32; 42 using mozilla::CheckedUint32; 43 using mozilla::IsPowerOfTwo; 44 using mozilla::MallocSizeOf; 45 46 // [SMDOC] Immediate type signature encoding 47 // 48 // call_indirect requires a signature check to ensure the dynamic callee type 49 // matches the static specified callee type. This involves comparing whether 50 // the two function types are equal. We canonicalize function types so that 51 // comparing the pointers of the types will indicate if they're equal. The 52 // canonicalized function types are loaded from the instance at runtime. 53 // 54 // For the common case of simple/small function types, we can avoid the cost 55 // of loading the function type pointers from the instance by having an 56 // alternate 'immediate' form that encodes a function type in a constant. 57 // We encode the function types such that bitwise equality implies the original 58 // function types were equal. We use a tag bit such that if one of the types 59 // is a pointer and the other an immediate, they will not compare as equal. 60 // 61 // The encoding is optimized for common function types that have at most one 62 // result and an arbitrary amount of arguments. 63 // 64 // [ 65 // 1 bit : tag (always 1), 66 // 1 bit : numResults, 67 // 3 bits : numArgs, 68 // numResults * 3 bits : results, 69 // numArgs * 3 bits : args 70 // ] 71 // (lsb -> msb order) 72 // 73 // Any function type that cannot be encoded in the above format is falls back 74 // to the pointer representation. 75 // 76 77 //========================================================================= 78 // ImmediateType 79 80 // ImmediateType is 32-bits to ensure it's easy to materialize the constant 81 // on all platforms. 82 using ImmediateType = uint32_t; 83 static const unsigned sTotalBits = sizeof(ImmediateType) * 8; 84 static const unsigned sTagBits = 1; 85 static const unsigned sNumResultsBits = 1; 86 static const unsigned sNumArgsBits = 3; 87 static const unsigned sValTypeBits = 3; 88 static const unsigned sMaxValTypes = 8; 89 90 static_assert(((1 << sNumResultsBits) - 1) + ((1 << sNumArgsBits) - 1) == 91 sMaxValTypes, 92 "sNumResultsBits, sNumArgsBits, sMaxValTypes are consistent"); 93 94 static_assert(sTagBits + sNumResultsBits + sNumArgsBits + 95 sValTypeBits * sMaxValTypes <= 96 sTotalBits, 97 "have room"); 98 99 static bool IsImmediateValType(ValType vt) { 100 switch (vt.kind()) { 101 case ValType::I32: 102 case ValType::I64: 103 case ValType::F32: 104 case ValType::F64: 105 case ValType::V128: 106 return true; 107 case ValType::Ref: 108 // We don't have space to encode nullability, so we optimize for 109 // non-nullable types. 110 if (!vt.isNullable()) { 111 return false; 112 } 113 switch (vt.refType().kind()) { 114 case RefType::Func: 115 case RefType::Extern: 116 case RefType::Any: 117 return true; 118 default: 119 return false; 120 } 121 default: 122 return false; 123 } 124 } 125 126 static unsigned EncodeImmediateValType(ValType vt) { 127 // We have run out of bits for each type, anything new must increase the 128 // sValTypeBits. 129 static_assert(7 < (1 << sValTypeBits), "enough space for ValType kind"); 130 131 switch (vt.kind()) { 132 case ValType::I32: 133 return 0; 134 case ValType::I64: 135 return 1; 136 case ValType::F32: 137 return 2; 138 case ValType::F64: 139 return 3; 140 case ValType::V128: 141 return 4; 142 case ValType::Ref: 143 MOZ_ASSERT(vt.isNullable()); 144 switch (vt.refType().kind()) { 145 case RefType::Func: 146 return 5; 147 case RefType::Extern: 148 return 6; 149 case RefType::Any: 150 return 7; 151 default: 152 MOZ_CRASH("bad RefType"); 153 } 154 default: 155 MOZ_CRASH("bad ValType"); 156 } 157 } 158 159 static bool IsImmediateFuncType(const FuncType& funcType) { 160 const ValTypeVector& results = funcType.results(); 161 const ValTypeVector& args = funcType.args(); 162 163 // Check the number of results and args fits 164 if (results.length() > ((1 << sNumResultsBits) - 1) || 165 args.length() > ((1 << sNumArgsBits) - 1)) { 166 return false; 167 } 168 169 // Ensure every result is compatible 170 for (ValType v : results) { 171 if (!IsImmediateValType(v)) { 172 return false; 173 } 174 } 175 176 // Ensure every arg is compatible 177 for (ValType v : args) { 178 if (!IsImmediateValType(v)) { 179 return false; 180 } 181 } 182 183 return true; 184 } 185 186 static ImmediateType EncodeNumResults(uint32_t numResults) { 187 MOZ_ASSERT(numResults <= (1 << sNumResultsBits) - 1); 188 return numResults; 189 } 190 191 static ImmediateType EncodeNumArgs(uint32_t numArgs) { 192 MOZ_ASSERT(numArgs <= (1 << sNumArgsBits) - 1); 193 return numArgs; 194 } 195 196 static ImmediateType EncodeImmediateFuncType(const FuncType& funcType) { 197 ImmediateType immediate = FuncType::ImmediateBit; 198 uint32_t shift = sTagBits; 199 200 // Encode the results 201 immediate |= EncodeNumResults(funcType.results().length()) << shift; 202 shift += sNumResultsBits; 203 204 for (ValType resultType : funcType.results()) { 205 immediate |= EncodeImmediateValType(resultType) << shift; 206 shift += sValTypeBits; 207 } 208 209 // Encode the args 210 immediate |= EncodeNumArgs(funcType.args().length()) << shift; 211 shift += sNumArgsBits; 212 213 for (ValType argType : funcType.args()) { 214 immediate |= EncodeImmediateValType(argType) << shift; 215 shift += sValTypeBits; 216 } 217 218 MOZ_ASSERT(shift <= sTotalBits); 219 return immediate; 220 } 221 222 //========================================================================= 223 // FuncType 224 225 void FuncType::initImmediateTypeId(bool isFinal, const TypeDef* superTypeDef, 226 uint32_t recGroupLength) { 227 // To improve the performance of the structural type check in 228 // the call_indirect function prologue, we attempt to encode the 229 // entire function type into an immediate such that bitwise equality 230 // implies structural equality. With the GC proposal, we don't 231 // want to generalize the immediate form for the new type system, so 232 // we don't use it when a type is non-final (i.e. may have sub types), or 233 // has super types, or is in a recursion group with other types. 234 // 235 // If non-final types are allowed, then the type can have subtypes, and we 236 // should therefore do a full subtype check on call_indirect, which 237 // doesn't work well with immediates. If the type has a super type, the 238 // same reason applies. And finally, types in recursion groups of 239 // size > 1 may not be considered equivalent even if they are 240 // structurally equivalent in every respect. 241 if (!isFinal || superTypeDef || recGroupLength != 1) { 242 immediateTypeId_ = NO_IMMEDIATE_TYPE_ID; 243 return; 244 } 245 246 // Otherwise, try to encode this function type into an immediate. 247 if (!IsImmediateFuncType(*this)) { 248 immediateTypeId_ = NO_IMMEDIATE_TYPE_ID; 249 return; 250 } 251 immediateTypeId_ = EncodeImmediateFuncType(*this); 252 } 253 254 bool FuncType::canHaveJitEntry() const { 255 return !hasUnexposableArgOrRet() && 256 !temporarilyUnsupportedReftypeForEntry() && 257 !temporarilyUnsupportedResultCountForJitEntry() && 258 JitOptions.enableWasmJitEntry; 259 } 260 261 bool FuncType::canHaveJitExit() const { 262 return !hasUnexposableArgOrRet() && !temporarilyUnsupportedReftypeForExit() && 263 !hasInt64Arg() && !temporarilyUnsupportedResultCountForJitExit() && 264 JitOptions.enableWasmJitExit; 265 } 266 267 size_t FuncType::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const { 268 return args_.sizeOfExcludingThis(mallocSizeOf); 269 } 270 271 //========================================================================= 272 // StructType 273 274 bool StructType::init() { 275 isDefaultable_ = true; 276 277 // Ensures the inline storage area is word-aligned. 278 static_assert((sizeof(WasmStructObject) % sizeof(uintptr_t)) == 0); 279 280 MOZ_ASSERT(fieldAccessPaths_.empty() && outlineTraceOffsets_.empty() && 281 inlineTraceOffsets_.empty()); 282 if (!fieldAccessPaths_.reserve(fields_.length())) { 283 return false; 284 } 285 286 // So as to guarantee the value will fit in payloadOffsetIL_, since it is 287 // a uint8_t. 288 static_assert(WasmStructObject_Size_ASSUMED < 289 (1 << (8 * sizeof(StructType::payloadOffsetIL_)))); 290 payloadOffsetIL_ = WasmStructObject_Size_ASSUMED; 291 292 StructLayout layout; 293 if (!layout.init(payloadOffsetIL_, WasmStructObject_MaxInlineBytes_ASSUMED)) { 294 return false; 295 } 296 297 for (FieldType& field : fields_) { 298 // Get a placement decision for the field 299 FieldAccessPath path; 300 if (!layout.addField(field.type.size(), &path)) { 301 return false; 302 } 303 304 // Add the access path to the vector thereof 305 fieldAccessPaths_.infallibleAppend(path); 306 307 // If any field is not defaultable, this whole struct is not defaultable 308 if (!field.type.isDefaultable()) { 309 isDefaultable_ = false; 310 } 311 312 // If this field is a ref, add it to the trace vectors 313 if (field.type.isRefRepr()) { 314 if (path.hasOOL()) { 315 if (!outlineTraceOffsets_.append(path.oolOffset())) { 316 return false; 317 } 318 } else { 319 if (!inlineTraceOffsets_.append(path.ilOffset())) { 320 return false; 321 } 322 } 323 } 324 } 325 326 if (layout.hasOOL()) { 327 totalSizeOOL_ = layout.totalSizeOOL(); 328 FieldAccessPath oolPointerPath = layout.oolPointerPath(); 329 MOZ_ASSERT(!oolPointerPath.hasOOL()); 330 oolPointerOffset_ = oolPointerPath.ilOffset(); 331 } else { 332 totalSizeOOL_ = 0; 333 oolPointerOffset_ = StructType::InvalidOffset; 334 } 335 336 totalSizeIL_ = layout.totalSizeIL(); 337 // payloadOffsetIL_ set above 338 allocKind_ = gc::GetGCObjectKindForBytes(totalSizeIL_); 339 // isDefaultable_ set above 340 341 return true; 342 } 343 344 /* static */ 345 bool StructType::createImmutable(const ValTypeVector& types, 346 StructType* struct_) { 347 FieldTypeVector fields; 348 if (!fields.resize(types.length())) { 349 return false; 350 } 351 for (size_t i = 0; i < types.length(); i++) { 352 fields[i].type = StorageType(types[i].packed()); 353 fields[i].isMutable = false; 354 } 355 *struct_ = StructType(std::move(fields)); 356 return struct_->init(); 357 } 358 359 size_t StructType::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const { 360 return fields_.sizeOfExcludingThis(mallocSizeOf); 361 } 362 363 size_t ArrayType::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const { 364 return 0; 365 } 366 367 size_t TypeDef::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const { 368 switch (kind_) { 369 case TypeDefKind::Struct: { 370 return structType_.sizeOfExcludingThis(mallocSizeOf); 371 } 372 case TypeDefKind::Func: { 373 return funcType_.sizeOfExcludingThis(mallocSizeOf); 374 } 375 case TypeDefKind::Array: { 376 return arrayType_.sizeOfExcludingThis(mallocSizeOf); 377 } 378 case TypeDefKind::None: { 379 return 0; 380 } 381 default: 382 break; 383 } 384 MOZ_ASSERT_UNREACHABLE(); 385 return 0; 386 } 387 388 //========================================================================= 389 // SuperTypeVector 390 391 /* static */ 392 size_t SuperTypeVector::offsetOfSTVInVector(uint32_t subTypingDepth) { 393 return offsetof(SuperTypeVector, types_) + sizeof(void*) * subTypingDepth; 394 } 395 396 /* static */ 397 size_t SuperTypeVector::lengthForTypeDef(const TypeDef& typeDef) { 398 return std::max(uint32_t(typeDef.subTypingDepth()) + 1, 399 MinSuperTypeVectorLength); 400 } 401 402 /* static */ 403 size_t SuperTypeVector::byteSizeForTypeDef(const TypeDef& typeDef) { 404 static_assert( 405 sizeof(SuperTypeVector) + sizeof(void*) * (MaxSubTypingDepth + 1) <= 406 UINT16_MAX, 407 "cannot overflow"); 408 return sizeof(SuperTypeVector) + (sizeof(void*) * lengthForTypeDef(typeDef)); 409 } 410 411 /* static */ 412 const SuperTypeVector* SuperTypeVector::createMultipleForRecGroup( 413 RecGroup* recGroup) { 414 // Pre-size the amount of space needed for all the super type vectors in this 415 // recursion group. 416 CheckedUint32 totalBytes = 0; 417 for (uint32_t typeIndex = 0; typeIndex < recGroup->numTypes(); typeIndex++) { 418 totalBytes += 419 SuperTypeVector::byteSizeForTypeDef(recGroup->type(typeIndex)); 420 } 421 if (!totalBytes.isValid()) { 422 return nullptr; 423 } 424 425 // Allocate the batch, and retain reference to the first one. 426 SuperTypeVector* firstVector = 427 (SuperTypeVector*)js_malloc(totalBytes.value()); 428 if (!firstVector) { 429 return nullptr; 430 } 431 432 // Initialize the vectors, one by one 433 SuperTypeVector* currentVector = firstVector; 434 for (uint32_t typeIndex = 0; typeIndex < recGroup->numTypes(); typeIndex++) { 435 TypeDef& typeDef = recGroup->type(typeIndex); 436 437 // Compute the size again to know where the next vector can be found. 438 size_t vectorByteSize = SuperTypeVector::byteSizeForTypeDef(typeDef); 439 440 // Make the typedef and the vector point at each other. 441 typeDef.setSuperTypeVector(currentVector); 442 currentVector->typeDef_ = &typeDef; 443 currentVector->subTypingDepth_ = typeDef.subTypingDepth(); 444 445 // Every vector stores all ancestor types and itself. 446 currentVector->length_ = SuperTypeVector::lengthForTypeDef(typeDef); 447 448 // Initialize the entries in the vector 449 const TypeDef* currentTypeDef = &typeDef; 450 for (uint32_t index = 0; index < currentVector->length(); index++) { 451 uint32_t reverseIndex = currentVector->length() - index - 1; 452 453 // If this entry is required just to hit the minimum size, then 454 // initialize it to null. 455 if (reverseIndex > typeDef.subTypingDepth()) { 456 currentVector->types_[reverseIndex] = nullptr; 457 continue; 458 } 459 460 // Otherwise we should always be iterating at the same depth as our 461 // currentTypeDef. 462 MOZ_ASSERT(reverseIndex == currentTypeDef->subTypingDepth()); 463 464 currentVector->types_[reverseIndex] = currentTypeDef->superTypeVector(); 465 currentTypeDef = currentTypeDef->superTypeDef(); 466 } 467 468 // There should be no more super types left over 469 MOZ_ASSERT(currentTypeDef == nullptr); 470 471 // Advance to the next super type vector 472 currentVector = 473 (SuperTypeVector*)(((const char*)currentVector) + vectorByteSize); 474 } 475 476 return firstVector; 477 } 478 479 //========================================================================= 480 // TypeIdSet and TypeContext 481 482 struct RecGroupHashPolicy { 483 using Lookup = const SharedRecGroup&; 484 485 static HashNumber hash(Lookup lookup) { return lookup->hash(); } 486 487 static bool match(const SharedRecGroup& lhs, Lookup rhs) { 488 return RecGroup::isoEquals(*rhs, *lhs); 489 } 490 }; 491 492 // A global hash set of recursion groups for use in fast type equality checks. 493 class TypeIdSet { 494 using Set = HashSet<SharedRecGroup, RecGroupHashPolicy, SystemAllocPolicy>; 495 Set set_; 496 497 public: 498 // Attempt to insert a recursion group into the set, returning an existing 499 // recursion group if there was one. 500 SharedRecGroup insert(SharedRecGroup recGroup) { 501 Set::AddPtr p = set_.lookupForAdd(recGroup); 502 if (p) { 503 // A canonical recursion group already existed, return it. 504 return *p; 505 } 506 507 // Insert this recursion group into the set, and return it as the canonical 508 // recursion group instance. 509 if (!set_.add(p, recGroup)) { 510 return nullptr; 511 } 512 return recGroup; 513 } 514 515 void purge() { 516 // TODO: this is not guaranteed to remove all types that are not referenced 517 // from outside the canonical set, as removing a type may make a previous 518 // type we've visited now only have one ref and be eligible to be freed. 519 // 520 // Solving this either involves iterating to a fixed point, or else a much 521 // more invasive change to the lifetime management of recursion groups. 522 for (auto iter = set_.modIter(); !iter.done(); iter.next()) { 523 if (iter.get()->hasOneRef()) { 524 iter.remove(); 525 } 526 } 527 } 528 529 // Release the provided recursion group reference and remove it from the 530 // canonical set if it was the last reference. This is one unified method 531 // because we need to perform the lookup before releasing the reference, but 532 // need to release the reference in order to see if it was the last reference 533 // outside the canonical set. 534 void clearRecGroup(SharedRecGroup* recGroupCell) { 535 if (Set::Ptr p = set_.lookup(*recGroupCell)) { 536 *recGroupCell = nullptr; 537 if ((*p)->hasOneRef()) { 538 set_.remove(p); 539 } 540 } else { 541 *recGroupCell = nullptr; 542 } 543 } 544 }; 545 546 MOZ_RUNINIT ExclusiveData<TypeIdSet> typeIdSet(mutexid::WasmTypeIdSet); 547 548 void wasm::PurgeCanonicalTypes() { 549 ExclusiveData<TypeIdSet>::Guard locked = typeIdSet.lock(); 550 locked->purge(); 551 } 552 553 SharedRecGroup TypeContext::canonicalizeGroup(SharedRecGroup recGroup) { 554 ExclusiveData<TypeIdSet>::Guard locked = typeIdSet.lock(); 555 return locked->insert(recGroup); 556 } 557 558 TypeContext::~TypeContext() { 559 ExclusiveData<TypeIdSet>::Guard locked = typeIdSet.lock(); 560 561 // Clear out the recursion groups in this module, freeing them from the 562 // canonical type set if needed. 563 // 564 // We iterate backwards here so that we free every previous recursion group 565 // that may be referring to the current recursion group we're freeing. This 566 // is possible due to recursion groups being ordered. 567 for (int32_t groupIndex = recGroups_.length() - 1; groupIndex >= 0; 568 groupIndex--) { 569 // Try to remove this entry from the canonical set if we have the last 570 // strong reference. The entry may not exist if canonicalization failed 571 // and this type context was aborted. This will clear the reference in the 572 // vector. 573 locked->clearRecGroup(&recGroups_[groupIndex]); 574 } 575 }