1 //===- subzero/src/IceGlobalContext.cpp - Global context defs -------------===// 2 // 3 // The Subzero Code Generator 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 /// 10 /// \file 11 /// \brief Defines aspects of the compilation that persist across multiple 12 /// functions. 13 /// 14 //===----------------------------------------------------------------------===// 15 16 #include "IceGlobalContext.h" 17 18 #include "IceCfg.h" 19 #include "IceCfgNode.h" 20 #include "IceClFlags.h" 21 #include "IceDefs.h" 22 #include "IceELFObjectWriter.h" 23 #include "IceGlobalInits.h" 24 #include "IceLiveness.h" 25 #include "IceOperand.h" 26 #include "IceRevision.h" 27 #include "IceTargetLowering.h" 28 #include "IceTimerTree.h" 29 #include "IceTypes.def" 30 #include "IceTypes.h" 31 32 #ifdef __clang__ 33 #pragma clang diagnostic push 34 #pragma clang diagnostic ignored "-Wunused-parameter" 35 #endif // __clang__ 36 37 #include "llvm/Support/Timer.h" 38 39 #ifdef __clang__ 40 #pragma clang diagnostic pop 41 #endif // __clang__ 42 43 #include <algorithm> // max() 44 45 namespace std { 46 template <> struct hash<Ice::RelocatableTuple> { 47 size_t operator()(const Ice::RelocatableTuple &Key) const { 48 // Use the relocatable's name, plus the hash of a combination of the number 49 // of OffsetExprs and the known, fixed offset for the reloc. We left shift 50 // the known relocatable by 5 trying to minimize the interaction between the 51 // bits in OffsetExpr.size() and Key.Offset. 52 return hash<Ice::SizeT>()(Key.Name.getID()) + 53 hash<std::size_t>()(Key.OffsetExpr.size() + (Key.Offset << 5)); 54 } 55 }; 56 } // end of namespace std 57 58 namespace Ice { 59 60 namespace { 61 62 // Define the key comparison function for the constant pool's unordered_map, 63 // but only for key types of interest: integer types, floating point types, and 64 // the special RelocatableTuple. 65 template <typename KeyType, class Enable = void> struct KeyCompare {}; 66 67 template <typename KeyType> 68 struct KeyCompare<KeyType, 69 typename std::enable_if< 70 std::is_integral<KeyType>::value || 71 std::is_same<KeyType, RelocatableTuple>::value>::type> { 72 bool operator()(const KeyType &Value1, const KeyType &Value2) const { 73 return Value1 == Value2; 74 } 75 }; 76 template <typename KeyType> 77 struct KeyCompare<KeyType, typename std::enable_if< 78 std::is_floating_point<KeyType>::value>::type> { 79 bool operator()(const KeyType &Value1, const KeyType &Value2) const { 80 return !memcmp(&Value1, &Value2, sizeof(KeyType)); 81 } 82 }; 83 84 // Define a key comparison function for sorting the constant pool's values 85 // after they are dumped to a vector. This covers integer types, floating point 86 // types, and ConstantRelocatable values. 87 template <typename ValueType, class Enable = void> struct KeyCompareLess {}; 88 89 template <typename ValueType> 90 struct KeyCompareLess<ValueType, 91 typename std::enable_if<std::is_floating_point< 92 typename ValueType::PrimType>::value>::type> { 93 bool operator()(const Constant *Const1, const Constant *Const2) const { 94 using CompareType = uint64_t; 95 static_assert(sizeof(typename ValueType::PrimType) <= sizeof(CompareType), 96 "Expected floating-point type of width 64-bit or less"); 97 typename ValueType::PrimType V1 = llvm::cast<ValueType>(Const1)->getValue(); 98 typename ValueType::PrimType V2 = llvm::cast<ValueType>(Const2)->getValue(); 99 // We avoid "V1<V2" because of NaN. 100 // We avoid "memcmp(&V1,&V2,sizeof(V1))<0" which depends on the 101 // endian-ness of the host system running Subzero. 102 // Instead, compare the result of bit_cast to uint64_t. 103 uint64_t I1 = 0, I2 = 0; 104 memcpy(&I1, &V1, sizeof(V1)); 105 memcpy(&I2, &V2, sizeof(V2)); 106 return I1 < I2; 107 } 108 }; 109 template <typename ValueType> 110 struct KeyCompareLess<ValueType, 111 typename std::enable_if<std::is_integral< 112 typename ValueType::PrimType>::value>::type> { 113 bool operator()(const Constant *Const1, const Constant *Const2) const { 114 typename ValueType::PrimType V1 = llvm::cast<ValueType>(Const1)->getValue(); 115 typename ValueType::PrimType V2 = llvm::cast<ValueType>(Const2)->getValue(); 116 return V1 < V2; 117 } 118 }; 119 template <typename ValueType> 120 struct KeyCompareLess< 121 ValueType, typename std::enable_if< 122 std::is_same<ValueType, ConstantRelocatable>::value>::type> { 123 bool operator()(const Constant *Const1, const Constant *Const2) const { 124 auto *V1 = llvm::cast<ValueType>(Const1); 125 auto *V2 = llvm::cast<ValueType>(Const2); 126 if (V1->getName() == V2->getName()) 127 return V1->getOffset() < V2->getOffset(); 128 return V1->getName() < V2->getName(); 129 } 130 }; 131 132 // TypePool maps constants of type KeyType (e.g. float) to pointers to 133 // type ValueType (e.g. ConstantFloat). 134 template <Type Ty, typename KeyType, typename ValueType> class TypePool { 135 TypePool(const TypePool &) = delete; 136 TypePool &operator=(const TypePool &) = delete; 137 138 public: 139 TypePool() = default; 140 ValueType *getOrAdd(GlobalContext *Ctx, KeyType Key) { 141 auto Iter = Pool.find(Key); 142 if (Iter != Pool.end()) { 143 Iter->second->updateLookupCount(); 144 return Iter->second; 145 } 146 auto *Result = ValueType::create(Ctx, Ty, Key); 147 Pool[Key] = Result; 148 Result->updateLookupCount(); 149 return Result; 150 } 151 ConstantList getConstantPool() const { 152 ConstantList Constants; 153 Constants.reserve(Pool.size()); 154 for (auto &I : Pool) 155 Constants.push_back(I.second); 156 // The sort (and its KeyCompareLess machinery) is not strictly necessary, 157 // but is desirable for producing output that is deterministic across 158 // unordered_map::iterator implementations. 159 std::sort(Constants.begin(), Constants.end(), KeyCompareLess<ValueType>()); 160 return Constants; 161 } 162 size_t size() const { return Pool.size(); } 163 164 private: 165 // Use the default hash function, and a custom key comparison function. The 166 // key comparison function for floating point variables can't use the default 167 // == based implementation because of special C++ semantics regarding +0.0, 168 // -0.0, and NaN comparison. However, it's OK to use the default hash for 169 // floating point values because KeyCompare is the final source of truth - in 170 // the worst case a "false" collision must be resolved. 171 using ContainerType = 172 std::unordered_map<KeyType, ValueType *, std::hash<KeyType>, 173 KeyCompare<KeyType>>; 174 ContainerType Pool; 175 }; 176 177 // UndefPool maps ICE types to the corresponding ConstantUndef values. 178 class UndefPool { 179 UndefPool(const UndefPool &) = delete; 180 UndefPool &operator=(const UndefPool &) = delete; 181 182 public: 183 UndefPool() : Pool(IceType_NUM) {} 184 185 ConstantUndef *getOrAdd(GlobalContext *Ctx, Type Ty) { 186 if (Pool[Ty] == nullptr) 187 Pool[Ty] = ConstantUndef::create(Ctx, Ty); 188 return Pool[Ty]; 189 } 190 191 private: 192 std::vector<ConstantUndef *> Pool; 193 }; 194 195 } // end of anonymous namespace 196 197 // The global constant pool bundles individual pools of each type of 198 // interest. 199 class ConstantPool { 200 ConstantPool(const ConstantPool &) = delete; 201 ConstantPool &operator=(const ConstantPool &) = delete; 202 203 public: 204 ConstantPool() = default; 205 TypePool<IceType_f32, float, ConstantFloat> Floats; 206 TypePool<IceType_f64, double, ConstantDouble> Doubles; 207 TypePool<IceType_i1, int8_t, ConstantInteger32> Integers1; 208 TypePool<IceType_i8, int8_t, ConstantInteger32> Integers8; 209 TypePool<IceType_i16, int16_t, ConstantInteger32> Integers16; 210 TypePool<IceType_i32, int32_t, ConstantInteger32> Integers32; 211 TypePool<IceType_i64, int64_t, ConstantInteger64> Integers64; 212 TypePool<IceType_i32, RelocatableTuple, ConstantRelocatable> Relocatables; 213 TypePool<IceType_i32, RelocatableTuple, ConstantRelocatable> 214 ExternRelocatables; 215 UndefPool Undefs; 216 }; 217 218 void GlobalContext::waitForWorkerThreads() { 219 if (WaitForWorkerThreadsCalled.exchange(true)) 220 return; 221 optQueueNotifyEnd(); 222 for (std::thread &Worker : TranslationThreads) { 223 Worker.join(); 224 } 225 TranslationThreads.clear(); 226 227 // Only notify the emit queue to end after all the translation threads have 228 // ended. 229 emitQueueNotifyEnd(); 230 for (std::thread &Worker : EmitterThreads) { 231 Worker.join(); 232 } 233 EmitterThreads.clear(); 234 235 if (BuildDefs::timers()) { 236 auto Timers = getTimers(); 237 for (ThreadContext *TLS : AllThreadContexts) 238 Timers->mergeFrom(TLS->Timers); 239 } 240 if (BuildDefs::dump()) { 241 // Do a separate loop over AllThreadContexts to avoid holding two locks at 242 // once. 243 auto Stats = getStatsCumulative(); 244 for (ThreadContext *TLS : AllThreadContexts) 245 Stats->add(TLS->StatsCumulative); 246 } 247 } 248 249 void GlobalContext::CodeStats::dump(const Cfg *Func, GlobalContext *Ctx) { 250 if (!BuildDefs::dump()) 251 return; 252 OstreamLocker _(Ctx); 253 Ostream &Str = Ctx->getStrDump(); 254 const std::string Name = 255 (Func == nullptr ? "_FINAL_" : Func->getFunctionNameAndSize()); 256 #define X(str, tag) \ 257 Str << "|" << Name << "|" str "|" << Stats[CS_##tag] << "\n"; 258 CODESTATS_TABLE 259 #undef X 260 Str << "|" << Name << "|Spills+Fills|" 261 << Stats[CS_NumSpills] + Stats[CS_NumFills] << "\n"; 262 Str << "|" << Name << "|Memory Usage |"; 263 if (const auto MemUsed = static_cast<size_t>( 264 llvm::TimeRecord::getCurrentTime(false).getMemUsed())) { 265 static constexpr size_t _1MB = 1024 * 1024; 266 Str << (MemUsed / _1MB) << " MB"; 267 } else { 268 Str << "(requires '-track-memory')"; 269 } 270 Str << "\n"; 271 Str << "|" << Name << "|CPool Sizes "; 272 { 273 auto Pool = Ctx->getConstPool(); 274 Str << "|f32=" << Pool->Floats.size(); 275 Str << "|f64=" << Pool->Doubles.size(); 276 Str << "|i1=" << Pool->Integers1.size(); 277 Str << "|i8=" << Pool->Integers8.size(); 278 Str << "|i16=" << Pool->Integers16.size(); 279 Str << "|i32=" << Pool->Integers32.size(); 280 Str << "|i64=" << Pool->Integers64.size(); 281 Str << "|Rel=" << Pool->Relocatables.size(); 282 Str << "|ExtRel=" << Pool->ExternRelocatables.size(); 283 } 284 Str << "\n"; 285 if (Func != nullptr) { 286 Str << "|" << Name << "|Cfg Memory |" << Func->getTotalMemoryMB() 287 << " MB\n"; 288 Str << "|" << Name << "|Liveness Memory |" << Func->getLivenessMemoryMB() 289 << " MB\n"; 290 } 291 } 292 293 namespace { 294 295 // By default, wake up the main parser thread when the OptQ gets half empty. 296 static constexpr size_t DefaultOptQWakeupSize = GlobalContext::MaxOptQSize >> 1; 297 298 } // end of anonymous namespace 299 300 GlobalContext::GlobalContext(Ostream *OsDump, Ostream *OsEmit, Ostream *OsError, 301 ELFStreamer *ELFStr) 302 : Strings(new StringPool()), ConstPool(new ConstantPool()), ErrorStatus(), 303 StrDump(OsDump), StrEmit(OsEmit), StrError(OsError), IntrinsicsInfo(this), 304 ObjectWriter(), 305 OptQWakeupSize(std::max(DefaultOptQWakeupSize, 306 size_t(getFlags().getNumTranslationThreads()))), 307 OptQ(/*Sequential=*/getFlags().isSequential(), 308 /*MaxSize=*/ 309 getFlags().isParseParallel() 310 ? MaxOptQSize 311 : getFlags().getNumTranslationThreads()), 312 // EmitQ is allowed unlimited size. 313 EmitQ(/*Sequential=*/getFlags().isSequential()), 314 DataLowering(TargetDataLowering::createLowering(this)) { 315 assert(OsDump && "OsDump is not defined for GlobalContext"); 316 assert(OsEmit && "OsEmit is not defined for GlobalContext"); 317 assert(OsError && "OsError is not defined for GlobalContext"); 318 // Make sure thread_local fields are properly initialized before any 319 // accesses are made. Do this here instead of at the start of 320 // main() so that all clients (e.g. unit tests) can benefit for 321 // free. 322 GlobalContext::TlsInit(); 323 Cfg::TlsInit(); 324 Liveness::TlsInit(); 325 // Create a new ThreadContext for the current thread. No need to 326 // lock AllThreadContexts at this point since no other threads have 327 // access yet to this GlobalContext object. 328 ThreadContext *MyTLS = new ThreadContext(); 329 AllThreadContexts.push_back(MyTLS); 330 ICE_TLS_SET_FIELD(TLS, MyTLS); 331 // Pre-register built-in stack names. 332 if (BuildDefs::timers()) { 333 // TODO(stichnot): There needs to be a strong relationship between 334 // the newTimerStackID() return values and TSK_Default/TSK_Funcs. 335 newTimerStackID("Total across all functions"); 336 newTimerStackID("Per-function summary"); 337 } 338 Timers.initInto(MyTLS->Timers); 339 switch (getFlags().getOutFileType()) { 340 case FT_Elf: 341 ObjectWriter.reset(new ELFObjectWriter(*this, *ELFStr)); 342 break; 343 case FT_Asm: 344 case FT_Iasm: 345 break; 346 } 347 // Cache up front common constants. 348 #define X(tag, sizeLog2, align, elts, elty, str, rcstr) \ 349 ConstZeroForType[IceType_##tag] = getConstantZeroInternal(IceType_##tag); 350 ICETYPE_TABLE; 351 #undef X 352 ConstantTrue = getConstantInt1Internal(1); 353 // Define runtime helper functions. 354 #define X(Tag, Name) \ 355 RuntimeHelperFunc[static_cast<size_t>(RuntimeHelper::H_##Tag)] = \ 356 getConstantExternSym(getGlobalString(Name)); 357 RUNTIME_HELPER_FUNCTIONS_TABLE 358 #undef X 359 360 TargetLowering::staticInit(this); 361 362 if (getFlags().getEmitRevision()) { 363 // Embed the Subzero revision into the compiled binary by creating a special 364 // global variable initialized with the revision string. 365 auto *Revision = VariableDeclaration::create(&Globals, true); 366 Revision->setName(this, "__Sz_revision"); 367 Revision->setIsConstant(true); 368 const char *RevisionString = getSubzeroRevision(); 369 Revision->addInitializer(VariableDeclaration::DataInitializer::create( 370 &Globals, RevisionString, 1 + strlen(RevisionString))); 371 Globals.push_back(Revision); 372 } 373 } 374 375 void GlobalContext::translateFunctionsWrapper(ThreadContext *MyTLS) { 376 ICE_TLS_SET_FIELD(TLS, MyTLS); 377 translateFunctions(); 378 } 379 380 void GlobalContext::translateFunctions() { 381 TimerMarker Timer(TimerStack::TT_translateFunctions, this); 382 while (std::unique_ptr<OptWorkItem> OptItem = optQueueBlockingPop()) { 383 std::unique_ptr<EmitterWorkItem> Item; 384 auto Func = OptItem->getParsedCfg(); 385 // Install Func in TLS for Cfg-specific container allocators. 386 CfgLocalAllocatorScope _(Func.get()); 387 // Reset per-function stats being accumulated in TLS. 388 resetStats(); 389 // Set verbose level to none if the current function does NOT match the 390 // -verbose-focus command-line option. 391 if (!getFlags().matchVerboseFocusOn(Func->getFunctionName(), 392 Func->getSequenceNumber())) 393 Func->setVerbose(IceV_None); 394 // Disable translation if -notranslate is specified, or if the current 395 // function matches the -translate-only option. If translation is disabled, 396 // just dump the high-level IR and continue. 397 if (getFlags().getDisableTranslation() || 398 !getFlags().matchTranslateOnly(Func->getFunctionName(), 399 Func->getSequenceNumber())) { 400 Func->dump(); 401 // Add a dummy work item as a placeholder. This maintains sequence 402 // numbers so that the emitter thread will emit subsequent functions. 403 Item = makeUnique<EmitterWorkItem>(Func->getSequenceNumber()); 404 emitQueueBlockingPush(std::move(Item)); 405 continue; // Func goes out of scope and gets deleted 406 } 407 408 Func->translate(); 409 if (Func->hasError()) { 410 getErrorStatus()->assign(EC_Translation); 411 OstreamLocker L(this); 412 getStrError() << "ICE translation error: " << Func->getFunctionName() 413 << ": " << Func->getError() << ": " 414 << Func->getFunctionNameAndSize() << "\n"; 415 Item = makeUnique<EmitterWorkItem>(Func->getSequenceNumber()); 416 } else { 417 Func->getAssembler<>()->setInternal(Func->getInternal()); 418 switch (getFlags().getOutFileType()) { 419 case FT_Elf: 420 case FT_Iasm: { 421 Func->emitIAS(); 422 // The Cfg has already emitted into the assembly buffer, so 423 // stats have been fully collected into this thread's TLS. 424 // Dump them before TLS is reset for the next Cfg. 425 if (BuildDefs::dump()) 426 dumpStats(Func.get()); 427 auto Asm = Func->releaseAssembler(); 428 // Copy relevant fields into Asm before Func is deleted. 429 Asm->setFunctionName(Func->getFunctionName()); 430 Item = makeUnique<EmitterWorkItem>(Func->getSequenceNumber(), 431 std::move(Asm)); 432 Item->setGlobalInits(Func->getGlobalInits()); 433 } break; 434 case FT_Asm: 435 // The Cfg has not been emitted yet, so stats are not ready 436 // to be dumped. 437 std::unique_ptr<VariableDeclarationList> GlobalInits = 438 Func->getGlobalInits(); 439 Item = makeUnique<EmitterWorkItem>(Func->getSequenceNumber(), 440 std::move(Func)); 441 Item->setGlobalInits(std::move(GlobalInits)); 442 break; 443 } 444 } 445 assert(Item != nullptr); 446 emitQueueBlockingPush(std::move(Item)); 447 // The Cfg now gets deleted as Func goes out of scope. 448 } 449 } 450 451 namespace { 452 453 // Ensure Pending is large enough that Pending[Index] is valid. 454 void resizePending(std::vector<std::unique_ptr<EmitterWorkItem>> *Pending, 455 uint32_t Index) { 456 if (Index >= Pending->size()) 457 Utils::reserveAndResize(*Pending, Index + 1); 458 } 459 460 } // end of anonymous namespace 461 462 // static 463 void GlobalContext::TlsInit() { ICE_TLS_INIT_FIELD(TLS); } 464 465 void GlobalContext::emitFileHeader() { 466 TimerMarker T1(Ice::TimerStack::TT_emitAsm, this); 467 if (getFlags().getOutFileType() == FT_Elf) { 468 getObjectWriter()->writeInitialELFHeader(); 469 } else { 470 if (!BuildDefs::dump()) { 471 getStrError() << "emitFileHeader for non-ELF"; 472 getErrorStatus()->assign(EC_Translation); 473 } 474 TargetHeaderLowering::createLowering(this)->lower(); 475 } 476 } 477 478 void GlobalContext::lowerConstants() { DataLowering->lowerConstants(); } 479 480 void GlobalContext::lowerJumpTables() { DataLowering->lowerJumpTables(); } 481 482 void GlobalContext::emitTargetRODataSections() { 483 DataLowering->emitTargetRODataSections(); 484 } 485 486 void GlobalContext::saveBlockInfoPtrs() { 487 for (VariableDeclaration *Global : Globals) { 488 if (Cfg::isProfileGlobal(*Global)) { 489 ProfileBlockInfos.push_back(Global); 490 } 491 } 492 } 493 494 void GlobalContext::lowerGlobals(const std::string &SectionSuffix) { 495 TimerMarker T(TimerStack::TT_emitGlobalInitializers, this); 496 const bool DumpGlobalVariables = 497 BuildDefs::dump() && (getFlags().getVerbose() & IceV_GlobalInit) && 498 getFlags().matchVerboseFocusOn("", 0); 499 if (DumpGlobalVariables) { 500 OstreamLocker L(this); 501 Ostream &Stream = getStrDump(); 502 for (const Ice::VariableDeclaration *Global : Globals) { 503 Global->dump(Stream); 504 } 505 } 506 if (getFlags().getDisableTranslation()) 507 return; 508 509 saveBlockInfoPtrs(); 510 // If we need to shuffle the layout of global variables, shuffle them now. 511 if (getFlags().getReorderGlobalVariables()) { 512 // Create a random number generator for global variable reordering. 513 RandomNumberGenerator RNG(getFlags().getRandomSeed(), 514 RPE_GlobalVariableReordering); 515 RandomShuffle(Globals.begin(), Globals.end(), 516 [&RNG](int N) { return (uint32_t)RNG.next(N); }); 517 } 518 519 if (!BuildDefs::minimal() && Instrumentor) 520 Instrumentor->instrumentGlobals(Globals); 521 522 DataLowering->lowerGlobals(Globals, SectionSuffix); 523 if (ProfileBlockInfos.empty() && DisposeGlobalVariablesAfterLowering) { 524 Globals.clearAndPurge(); 525 } else { 526 Globals.clear(); 527 } 528 } 529 530 void GlobalContext::lowerProfileData() { 531 // ProfileBlockInfoVarDecl is initialized in the constructor, and will only 532 // ever be nullptr after this method completes. This assertion is a convoluted 533 // way of ensuring lowerProfileData is invoked a single time. 534 assert(ProfileBlockInfoVarDecl == nullptr); 535 536 auto GlobalVariablePool = getInitializerAllocator(); 537 ProfileBlockInfoVarDecl = 538 VariableDeclaration::createExternal(GlobalVariablePool.get()); 539 ProfileBlockInfoVarDecl->setAlignment(typeWidthInBytes(IceType_i64)); 540 ProfileBlockInfoVarDecl->setIsConstant(true); 541 542 // Note: if you change this symbol, make sure to update 543 // runtime/szrt_profiler.c as well. 544 ProfileBlockInfoVarDecl->setName(this, "__Sz_block_profile_info"); 545 546 for (const VariableDeclaration *PBI : ProfileBlockInfos) { 547 if (Cfg::isProfileGlobal(*PBI)) { 548 constexpr RelocOffsetT BlockExecutionCounterOffset = 0; 549 ProfileBlockInfoVarDecl->addInitializer( 550 VariableDeclaration::RelocInitializer::create( 551 GlobalVariablePool.get(), PBI, 552 {RelocOffset::create(this, BlockExecutionCounterOffset)})); 553 } 554 } 555 556 // This adds a 64-bit sentinel entry to the end of our array. For 32-bit 557 // architectures this will waste 4 bytes. 558 const SizeT Sizeof64BitNullPtr = typeWidthInBytes(IceType_i64); 559 ProfileBlockInfoVarDecl->addInitializer( 560 VariableDeclaration::ZeroInitializer::create(GlobalVariablePool.get(), 561 Sizeof64BitNullPtr)); 562 Globals.push_back(ProfileBlockInfoVarDecl); 563 constexpr char ProfileDataSection[] = "$sz_profiler$"; 564 lowerGlobals(ProfileDataSection); 565 } 566 567 void GlobalContext::emitterWrapper(ThreadContext *MyTLS) { 568 ICE_TLS_SET_FIELD(TLS, MyTLS); 569 emitItems(); 570 } 571 572 void GlobalContext::emitItems() { 573 const bool Threaded = !getFlags().isSequential(); 574 // Pending is a vector containing the reassembled, ordered list of 575 // work items. When we're ready for the next item, we first check 576 // whether it's in the Pending list. If not, we take an item from 577 // the work queue, and if it's not the item we're waiting for, we 578 // insert it into Pending and repeat. The work item is deleted 579 // after it is processed. 580 std::vector<std::unique_ptr<EmitterWorkItem>> Pending; 581 uint32_t DesiredSequenceNumber = getFirstSequenceNumber(); 582 uint32_t ShuffleStartIndex = DesiredSequenceNumber; 583 uint32_t ShuffleEndIndex = DesiredSequenceNumber; 584 bool EmitQueueEmpty = false; 585 const uint32_t ShuffleWindowSize = 586 std::max(1u, getFlags().getReorderFunctionsWindowSize()); 587 bool Shuffle = Threaded && getFlags().getReorderFunctions(); 588 // Create a random number generator for function reordering. 589 RandomNumberGenerator RNG(getFlags().getRandomSeed(), RPE_FunctionReordering); 590 591 while (!EmitQueueEmpty) { 592 resizePending(&Pending, DesiredSequenceNumber); 593 // See if Pending contains DesiredSequenceNumber. 594 if (Pending[DesiredSequenceNumber] == nullptr) { 595 // We need to fetch an EmitterWorkItem from the queue. 596 auto RawItem = emitQueueBlockingPop(); 597 if (RawItem == nullptr) { 598 // This is the notifier for an empty queue. 599 EmitQueueEmpty = true; 600 } else { 601 // We get an EmitterWorkItem, we need to add it to Pending. 602 uint32_t ItemSeq = RawItem->getSequenceNumber(); 603 if (Threaded && ItemSeq != DesiredSequenceNumber) { 604 // Not the desired one, add it to Pending but do not increase 605 // DesiredSequenceNumber. Continue the loop, do not emit the item. 606 resizePending(&Pending, ItemSeq); 607 Pending[ItemSeq] = std::move(RawItem); 608 continue; 609 } 610 // ItemSeq == DesiredSequenceNumber, we need to check if we should 611 // emit it or not. If !Threaded, we're OK with ItemSeq != 612 // DesiredSequenceNumber. 613 Pending[DesiredSequenceNumber] = std::move(RawItem); 614 } 615 } 616 const auto *CurrentWorkItem = Pending[DesiredSequenceNumber].get(); 617 618 // We have the desired EmitterWorkItem or nullptr as the end notifier. 619 // If the emitter queue is not empty, increase DesiredSequenceNumber and 620 // ShuffleEndIndex. 621 if (!EmitQueueEmpty) { 622 DesiredSequenceNumber++; 623 ShuffleEndIndex++; 624 } 625 626 if (Shuffle) { 627 // Continue fetching EmitterWorkItem if function reordering is turned on, 628 // and emit queue is not empty, and the number of consecutive pending 629 // items is smaller than the window size, and RawItem is not a 630 // WI_GlobalInits kind. Emit WI_GlobalInits kind block first to avoid 631 // holding an arbitrarily large GlobalDeclarationList. 632 if (!EmitQueueEmpty && 633 ShuffleEndIndex - ShuffleStartIndex < ShuffleWindowSize && 634 CurrentWorkItem->getKind() != EmitterWorkItem::WI_GlobalInits) 635 continue; 636 637 // Emit the EmitterWorkItem between Pending[ShuffleStartIndex] to 638 // Pending[ShuffleEndIndex]. If function reordering turned on, shuffle the 639 // pending items from Pending[ShuffleStartIndex] to 640 // Pending[ShuffleEndIndex]. 641 RandomShuffle(Pending.begin() + ShuffleStartIndex, 642 Pending.begin() + ShuffleEndIndex, 643 [&RNG](uint64_t N) { return (uint32_t)RNG.next(N); }); 644 } 645 646 // Emit the item from ShuffleStartIndex to ShuffleEndIndex. 647 for (uint32_t I = ShuffleStartIndex; I < ShuffleEndIndex; I++) { 648 std::unique_ptr<EmitterWorkItem> Item = std::move(Pending[I]); 649 650 switch (Item->getKind()) { 651 case EmitterWorkItem::WI_Nop: 652 break; 653 case EmitterWorkItem::WI_GlobalInits: { 654 accumulateGlobals(Item->getGlobalInits()); 655 } break; 656 case EmitterWorkItem::WI_Asm: { 657 lowerGlobalsIfNoCodeHasBeenSeen(); 658 accumulateGlobals(Item->getGlobalInits()); 659 660 std::unique_ptr<Assembler> Asm = Item->getAsm(); 661 Asm->alignFunction(); 662 GlobalString Name = Asm->getFunctionName(); 663 switch (getFlags().getOutFileType()) { 664 case FT_Elf: 665 getObjectWriter()->writeFunctionCode(Name, Asm->getInternal(), 666 Asm.get()); 667 break; 668 case FT_Iasm: { 669 OstreamLocker L(this); 670 Cfg::emitTextHeader(Name, this, Asm.get()); 671 Asm->emitIASBytes(this); 672 } break; 673 case FT_Asm: 674 llvm::report_fatal_error("Unexpected FT_Asm"); 675 break; 676 } 677 } break; 678 case EmitterWorkItem::WI_Cfg: { 679 if (!BuildDefs::dump()) 680 llvm::report_fatal_error("WI_Cfg work item created inappropriately"); 681 lowerGlobalsIfNoCodeHasBeenSeen(); 682 accumulateGlobals(Item->getGlobalInits()); 683 684 assert(getFlags().getOutFileType() == FT_Asm); 685 std::unique_ptr<Cfg> Func = Item->getCfg(); 686 // Unfortunately, we have to temporarily install the Cfg in TLS 687 // because Variable::asType() uses the allocator to create the 688 // differently-typed copy. 689 CfgLocalAllocatorScope _(Func.get()); 690 Func->emit(); 691 dumpStats(Func.get()); 692 } break; 693 } 694 } 695 // Update the start index for next shuffling queue 696 ShuffleStartIndex = ShuffleEndIndex; 697 } 698 699 // In case there are no code to be generated, we invoke the conditional 700 // lowerGlobals again -- this is a no-op if code has been emitted. 701 lowerGlobalsIfNoCodeHasBeenSeen(); 702 } 703 704 GlobalContext::~GlobalContext() { 705 llvm::DeleteContainerPointers(AllThreadContexts); 706 LockedPtr<DestructorArray> Dtors = getDestructors(); 707 // Destructors are invoked in the opposite object construction order. 708 for (const auto &Dtor : reverse_range(*Dtors)) 709 Dtor(); 710 } 711 712 void GlobalContext::dumpStrings() { 713 if (!getFlags().getDumpStrings()) 714 return; 715 OstreamLocker _(this); 716 Ostream &Str = getStrDump(); 717 Str << "GlobalContext strings:\n"; 718 getStrings()->dump(Str); 719 } 720 721 void GlobalContext::dumpConstantLookupCounts() { 722 if (!BuildDefs::dump()) 723 return; 724 const bool DumpCounts = (getFlags().getVerbose() & IceV_ConstPoolStats) && 725 getFlags().matchVerboseFocusOn("", 0); 726 if (!DumpCounts) 727 return; 728 729 OstreamLocker _(this); 730 Ostream &Str = getStrDump(); 731 Str << "Constant pool use stats: count+value+type\n"; 732 #define X(WhichPool) \ 733 for (auto *C : getConstPool()->WhichPool.getConstantPool()) { \ 734 Str << C->getLookupCount() << " "; \ 735 C->dump(Str); \ 736 Str << " " << C->getType() << "\n"; \ 737 } 738 X(Integers1); 739 X(Integers8); 740 X(Integers16); 741 X(Integers32); 742 X(Integers64); 743 X(Floats); 744 X(Doubles); 745 X(Relocatables); 746 X(ExternRelocatables); 747 #undef X 748 } 749 750 // TODO(stichnot): Consider adding thread-local caches of constant pool entries 751 // to reduce contention. 752 753 // All locking is done by the getConstantInt[0-9]+() target function. 754 Constant *GlobalContext::getConstantInt(Type Ty, int64_t Value) { 755 switch (Ty) { 756 case IceType_i1: 757 return getConstantInt1(Value); 758 case IceType_i8: 759 return getConstantInt8(Value); 760 case IceType_i16: 761 return getConstantInt16(Value); 762 case IceType_i32: 763 return getConstantInt32(Value); 764 case IceType_i64: 765 return getConstantInt64(Value); 766 default: 767 llvm_unreachable("Bad integer type for getConstant"); 768 } 769 return nullptr; 770 } 771 772 Constant *GlobalContext::getConstantInt1Internal(int8_t ConstantInt1) { 773 ConstantInt1 &= INT8_C(1); 774 return getConstPool()->Integers1.getOrAdd(this, ConstantInt1); 775 } 776 777 Constant *GlobalContext::getConstantInt8Internal(int8_t ConstantInt8) { 778 return getConstPool()->Integers8.getOrAdd(this, ConstantInt8); 779 } 780 781 Constant *GlobalContext::getConstantInt16Internal(int16_t ConstantInt16) { 782 return getConstPool()->Integers16.getOrAdd(this, ConstantInt16); 783 } 784 785 Constant *GlobalContext::getConstantInt32Internal(int32_t ConstantInt32) { 786 return getConstPool()->Integers32.getOrAdd(this, ConstantInt32); 787 } 788 789 Constant *GlobalContext::getConstantInt64Internal(int64_t ConstantInt64) { 790 return getConstPool()->Integers64.getOrAdd(this, ConstantInt64); 791 } 792 793 Constant *GlobalContext::getConstantFloat(float ConstantFloat) { 794 return getConstPool()->Floats.getOrAdd(this, ConstantFloat); 795 } 796 797 Constant *GlobalContext::getConstantDouble(double ConstantDouble) { 798 return getConstPool()->Doubles.getOrAdd(this, ConstantDouble); 799 } 800 801 Constant *GlobalContext::getConstantSymWithEmitString( 802 const RelocOffsetT Offset, const RelocOffsetArray &OffsetExpr, 803 GlobalString Name, const std::string &EmitString) { 804 return getConstPool()->Relocatables.getOrAdd( 805 this, RelocatableTuple(Offset, OffsetExpr, Name, EmitString)); 806 } 807 808 Constant *GlobalContext::getConstantSym(RelocOffsetT Offset, 809 GlobalString Name) { 810 constexpr char EmptyEmitString[] = ""; 811 return getConstantSymWithEmitString(Offset, {}, Name, EmptyEmitString); 812 } 813 814 Constant *GlobalContext::getConstantExternSym(GlobalString Name) { 815 constexpr RelocOffsetT Offset = 0; 816 return getConstPool()->ExternRelocatables.getOrAdd( 817 this, RelocatableTuple(Offset, {}, Name)); 818 } 819 820 Constant *GlobalContext::getConstantUndef(Type Ty) { 821 return getConstPool()->Undefs.getOrAdd(this, Ty); 822 } 823 824 Constant *GlobalContext::getConstantZero(Type Ty) { 825 Constant *Zero = ConstZeroForType[Ty]; 826 if (Zero == nullptr) 827 llvm::report_fatal_error("Unsupported constant type: " + typeStdString(Ty)); 828 return Zero; 829 } 830 831 // All locking is done by the getConstant*() target function. 832 Constant *GlobalContext::getConstantZeroInternal(Type Ty) { 833 switch (Ty) { 834 case IceType_i1: 835 return getConstantInt1Internal(0); 836 case IceType_i8: 837 return getConstantInt8Internal(0); 838 case IceType_i16: 839 return getConstantInt16Internal(0); 840 case IceType_i32: 841 return getConstantInt32Internal(0); 842 case IceType_i64: 843 return getConstantInt64Internal(0); 844 case IceType_f32: 845 return getConstantFloat(0); 846 case IceType_f64: 847 return getConstantDouble(0); 848 default: 849 return nullptr; 850 } 851 } 852 853 ConstantList GlobalContext::getConstantPool(Type Ty) { 854 switch (Ty) { 855 case IceType_i1: 856 case IceType_i8: 857 return getConstPool()->Integers8.getConstantPool(); 858 case IceType_i16: 859 return getConstPool()->Integers16.getConstantPool(); 860 case IceType_i32: 861 return getConstPool()->Integers32.getConstantPool(); 862 case IceType_i64: 863 return getConstPool()->Integers64.getConstantPool(); 864 case IceType_f32: 865 return getConstPool()->Floats.getConstantPool(); 866 case IceType_f64: 867 return getConstPool()->Doubles.getConstantPool(); 868 case IceType_v4i1: 869 case IceType_v8i1: 870 case IceType_v16i1: 871 case IceType_v16i8: 872 case IceType_v8i16: 873 case IceType_v4i32: 874 case IceType_v4f32: 875 llvm::report_fatal_error("Unsupported constant type: " + typeStdString(Ty)); 876 break; 877 case IceType_void: 878 case IceType_NUM: 879 break; 880 } 881 llvm_unreachable("Unknown type"); 882 } 883 884 ConstantList GlobalContext::getConstantExternSyms() { 885 return getConstPool()->ExternRelocatables.getConstantPool(); 886 } 887 888 GlobalString GlobalContext::getGlobalString(const std::string &Name) { 889 return GlobalString::createWithString(this, Name); 890 } 891 892 JumpTableDataList GlobalContext::getJumpTables() { 893 JumpTableDataList JumpTables(*getJumpTableList()); 894 // Make order deterministic by sorting into functions and then ID of the jump 895 // table within that function. 896 std::sort(JumpTables.begin(), JumpTables.end(), 897 [](const JumpTableData &A, const JumpTableData &B) { 898 if (A.getFunctionName() != B.getFunctionName()) 899 return A.getFunctionName() < B.getFunctionName(); 900 return A.getId() < B.getId(); 901 }); 902 903 if (getFlags().getReorderPooledConstants()) { 904 // If reorder-pooled-constants option is set to true, we also shuffle the 905 // jump tables before emitting them. 906 907 // Create a random number generator for jump tables reordering, considering 908 // jump tables as pooled constants. 909 RandomNumberGenerator RNG(getFlags().getRandomSeed(), 910 RPE_PooledConstantReordering); 911 RandomShuffle(JumpTables.begin(), JumpTables.end(), 912 [&RNG](uint64_t N) { return (uint32_t)RNG.next(N); }); 913 } 914 return JumpTables; 915 } 916 917 void GlobalContext::addJumpTableData(JumpTableData JumpTable) { 918 getJumpTableList()->emplace_back(std::move(JumpTable)); 919 } 920 921 TimerStackIdT GlobalContext::newTimerStackID(const std::string &Name) { 922 if (!BuildDefs::timers()) 923 return 0; 924 auto Timers = getTimers(); 925 TimerStackIdT NewID = Timers->size(); 926 Timers->push_back(TimerStack(Name)); 927 return NewID; 928 } 929 930 TimerIdT GlobalContext::getTimerID(TimerStackIdT StackID, 931 const std::string &Name) { 932 auto *Timers = &ICE_TLS_GET_FIELD(TLS)->Timers; 933 assert(StackID < Timers->size()); 934 return Timers->at(StackID).getTimerID(Name); 935 } 936 937 void GlobalContext::pushTimer(TimerIdT ID, TimerStackIdT StackID) { 938 auto *Timers = &ICE_TLS_GET_FIELD(TLS)->Timers; 939 assert(StackID < Timers->size()); 940 Timers->at(StackID).push(ID); 941 } 942 943 void GlobalContext::popTimer(TimerIdT ID, TimerStackIdT StackID) { 944 auto *Timers = &ICE_TLS_GET_FIELD(TLS)->Timers; 945 assert(StackID < Timers->size()); 946 Timers->at(StackID).pop(ID); 947 } 948 949 void GlobalContext::resetTimer(TimerStackIdT StackID) { 950 auto *Timers = &ICE_TLS_GET_FIELD(TLS)->Timers; 951 assert(StackID < Timers->size()); 952 Timers->at(StackID).reset(); 953 } 954 955 std::string GlobalContext::getTimerName(TimerStackIdT StackID) { 956 auto *Timers = &ICE_TLS_GET_FIELD(TLS)->Timers; 957 assert(StackID < Timers->size()); 958 return Timers->at(StackID).getName(); 959 } 960 961 void GlobalContext::setTimerName(TimerStackIdT StackID, 962 const std::string &NewName) { 963 auto *Timers = &ICE_TLS_GET_FIELD(TLS)->Timers; 964 assert(StackID < Timers->size()); 965 Timers->at(StackID).setName(NewName); 966 } 967 968 // Note: optQueueBlockingPush and optQueueBlockingPop use unique_ptr at the 969 // interface to take and transfer ownership, but they internally store the raw 970 // Cfg pointer in the work queue. This allows e.g. future queue optimizations 971 // such as the use of atomics to modify queue elements. 972 void GlobalContext::optQueueBlockingPush(std::unique_ptr<OptWorkItem> Item) { 973 assert(Item); 974 { 975 TimerMarker _(TimerStack::TT_qTransPush, this); 976 OptQ.blockingPush(std::move(Item)); 977 } 978 if (getFlags().isSequential()) 979 translateFunctions(); 980 } 981 982 std::unique_ptr<OptWorkItem> GlobalContext::optQueueBlockingPop() { 983 TimerMarker _(TimerStack::TT_qTransPop, this); 984 return OptQ.blockingPop(OptQWakeupSize); 985 } 986 987 void GlobalContext::emitQueueBlockingPush( 988 std::unique_ptr<EmitterWorkItem> Item) { 989 assert(Item); 990 { 991 TimerMarker _(TimerStack::TT_qEmitPush, this); 992 EmitQ.blockingPush(std::move(Item)); 993 } 994 if (getFlags().isSequential()) 995 emitItems(); 996 } 997 998 std::unique_ptr<EmitterWorkItem> GlobalContext::emitQueueBlockingPop() { 999 TimerMarker _(TimerStack::TT_qEmitPop, this); 1000 return EmitQ.blockingPop(); 1001 } 1002 1003 void GlobalContext::initParserThread() { 1004 ThreadContext *Tls = new ThreadContext(); 1005 auto Timers = getTimers(); 1006 Timers->initInto(Tls->Timers); 1007 AllThreadContexts.push_back(Tls); 1008 ICE_TLS_SET_FIELD(TLS, Tls); 1009 } 1010 1011 void GlobalContext::startWorkerThreads() { 1012 size_t NumWorkers = getFlags().getNumTranslationThreads(); 1013 auto Timers = getTimers(); 1014 for (size_t i = 0; i < NumWorkers; ++i) { 1015 ThreadContext *WorkerTLS = new ThreadContext(); 1016 Timers->initInto(WorkerTLS->Timers); 1017 AllThreadContexts.push_back(WorkerTLS); 1018 TranslationThreads.push_back(std::thread( 1019 &GlobalContext::translateFunctionsWrapper, this, WorkerTLS)); 1020 } 1021 if (NumWorkers) { 1022 ThreadContext *WorkerTLS = new ThreadContext(); 1023 Timers->initInto(WorkerTLS->Timers); 1024 AllThreadContexts.push_back(WorkerTLS); 1025 EmitterThreads.push_back( 1026 std::thread(&GlobalContext::emitterWrapper, this, WorkerTLS)); 1027 } 1028 } 1029 1030 void GlobalContext::resetStats() { 1031 if (BuildDefs::dump()) 1032 ICE_TLS_GET_FIELD(TLS)->StatsFunction.reset(); 1033 } 1034 1035 void GlobalContext::dumpStats(const Cfg *Func) { 1036 if (!getFlags().getDumpStats()) 1037 return; 1038 if (Func == nullptr) { 1039 getStatsCumulative()->dump(Func, this); 1040 } else { 1041 ICE_TLS_GET_FIELD(TLS)->StatsFunction.dump(Func, this); 1042 } 1043 } 1044 1045 void GlobalContext::statsUpdateEmitted(uint32_t InstCount) { 1046 if (!getFlags().getDumpStats()) 1047 return; 1048 ThreadContext *Tls = ICE_TLS_GET_FIELD(TLS); 1049 Tls->StatsFunction.update(CodeStats::CS_InstCount, InstCount); 1050 Tls->StatsCumulative.update(CodeStats::CS_InstCount, InstCount); 1051 } 1052 1053 void GlobalContext::statsUpdateRegistersSaved(uint32_t Num) { 1054 if (!getFlags().getDumpStats()) 1055 return; 1056 ThreadContext *Tls = ICE_TLS_GET_FIELD(TLS); 1057 Tls->StatsFunction.update(CodeStats::CS_RegsSaved, Num); 1058 Tls->StatsCumulative.update(CodeStats::CS_RegsSaved, Num); 1059 } 1060 1061 void GlobalContext::statsUpdateFrameBytes(uint32_t Bytes) { 1062 if (!getFlags().getDumpStats()) 1063 return; 1064 ThreadContext *Tls = ICE_TLS_GET_FIELD(TLS); 1065 Tls->StatsFunction.update(CodeStats::CS_FrameByte, Bytes); 1066 Tls->StatsCumulative.update(CodeStats::CS_FrameByte, Bytes); 1067 } 1068 1069 void GlobalContext::statsUpdateSpills() { 1070 if (!getFlags().getDumpStats()) 1071 return; 1072 ThreadContext *Tls = ICE_TLS_GET_FIELD(TLS); 1073 Tls->StatsFunction.update(CodeStats::CS_NumSpills); 1074 Tls->StatsCumulative.update(CodeStats::CS_NumSpills); 1075 } 1076 1077 void GlobalContext::statsUpdateFills() { 1078 if (!getFlags().getDumpStats()) 1079 return; 1080 ThreadContext *Tls = ICE_TLS_GET_FIELD(TLS); 1081 Tls->StatsFunction.update(CodeStats::CS_NumFills); 1082 Tls->StatsCumulative.update(CodeStats::CS_NumFills); 1083 } 1084 1085 void GlobalContext::statsUpdateRPImms() { 1086 if (!getFlags().getDumpStats()) 1087 return; 1088 ThreadContext *Tls = ICE_TLS_GET_FIELD(TLS); 1089 Tls->StatsFunction.update(CodeStats::CS_NumRPImms); 1090 Tls->StatsCumulative.update(CodeStats::CS_NumRPImms); 1091 } 1092 1093 void GlobalContext::dumpTimers(TimerStackIdT StackID, bool DumpCumulative) { 1094 if (!BuildDefs::timers()) 1095 return; 1096 auto Timers = getTimers(); 1097 assert(Timers->size() > StackID); 1098 OstreamLocker L(this); 1099 Timers->at(StackID).dump(getStrDump(), DumpCumulative); 1100 } 1101 1102 void GlobalContext::dumpLocalTimers(const std::string &TimerNameOverride, 1103 TimerStackIdT StackID, 1104 bool DumpCumulative) { 1105 if (!BuildDefs::timers()) 1106 return; 1107 auto *Timers = &ICE_TLS_GET_FIELD(TLS)->Timers; 1108 assert(Timers->size() > StackID); 1109 // Temporarily override the thread-local timer name with the given name. 1110 // Don't do it permanently because the final timer merge at the end expects 1111 // the thread-local timer names to be the same as the global timer name. 1112 auto OrigName = getTimerName(StackID); 1113 setTimerName(StackID, TimerNameOverride); 1114 { 1115 OstreamLocker _(this); 1116 Timers->at(StackID).dump(getStrDump(), DumpCumulative); 1117 } 1118 setTimerName(StackID, OrigName); 1119 } 1120 1121 LockedPtr<StringPool> 1122 GlobalStringPoolTraits::getStrings(const GlobalContext *PoolOwner) { 1123 return PoolOwner->getStrings(); 1124 } 1125 1126 TimerIdT TimerMarker::getTimerIdFromFuncName(GlobalContext *Ctx, 1127 const std::string &FuncName) { 1128 if (!BuildDefs::timers()) 1129 return 0; 1130 if (!getFlags().getTimeEachFunction()) 1131 return 0; 1132 return Ctx->getTimerID(GlobalContext::TSK_Funcs, FuncName); 1133 } 1134 1135 void TimerMarker::push() { 1136 switch (StackID) { 1137 case GlobalContext::TSK_Default: 1138 Active = getFlags().getSubzeroTimingEnabled() || 1139 !getFlags().getTimingFocusOnString().empty(); 1140 break; 1141 case GlobalContext::TSK_Funcs: 1142 Active = getFlags().getTimeEachFunction(); 1143 break; 1144 default: 1145 break; 1146 } 1147 if (Active) 1148 Ctx->pushTimer(ID, StackID); 1149 } 1150 1151 void TimerMarker::pushCfg(const Cfg *Func) { 1152 Ctx = Func->getContext(); 1153 Active = Func->getFocusedTiming() || getFlags().getSubzeroTimingEnabled(); 1154 if (Active) 1155 Ctx->pushTimer(ID, StackID); 1156 } 1157 1158 ICE_TLS_DEFINE_FIELD(GlobalContext::ThreadContext *, GlobalContext, TLS); 1159 1160 } // end of namespace Ice 1161