external/swiftshader
リビジョン | 932640b2d9cd2611ff60907a3d3d1ba0ad966c14 (tree) |
---|---|
日時 | 2018-06-23 03:25:25 |
作者 | Alexis Hetu <sugoi@goog...> |
コミッター | Alexis Hétu |
Std:unordered_map removed from Optimizer for improved performance
The use of std::unordered_map was the main source of slowdowns in
the optimizer code, so it was re-written without any maps. In order
to do so, the information originally carried by the maps was moved
to user-defined information stored within Subzero classes. The
optimizer now manages the memory used to store this information.
Bug swiftshader:69
Bug b/67872293
Change-Id: I2757169f0d3d467766317af6e00e149b4317fb9c
Reviewed-on: https://swiftshader-review.googlesource.com/19508
Tested-by: Alexis Hétu <sugoi@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
@@ -17,7 +17,6 @@ | ||
17 | 17 | #include "src/IceCfg.h" |
18 | 18 | #include "src/IceCfgNode.h" |
19 | 19 | |
20 | -#include <unordered_map> | |
21 | 20 | #include <vector> |
22 | 21 | |
23 | 22 | namespace |
@@ -61,9 +60,35 @@ namespace | ||
61 | 60 | std::vector<Ice::Inst*> stores; |
62 | 61 | }; |
63 | 62 | |
64 | - std::unordered_map<Ice::Operand*, Uses> uses; | |
65 | - std::unordered_map<Ice::Inst*, Ice::CfgNode*> node; | |
66 | - std::unordered_map<Ice::Variable*, Ice::Inst*> definition; | |
63 | + struct LoadStoreInst | |
64 | + { | |
65 | + LoadStoreInst(Ice::Inst* inst, bool isStore) | |
66 | + : inst(inst), | |
67 | + address(isStore ? storeAddress(inst) : loadAddress(inst)), | |
68 | + isStore(isStore) | |
69 | + { | |
70 | + } | |
71 | + | |
72 | + Ice::Inst* inst; | |
73 | + Ice::Operand *address; | |
74 | + bool isStore; | |
75 | + }; | |
76 | + | |
77 | + Optimizer::Uses* getUses(Ice::Operand*); | |
78 | + void setUses(Ice::Operand*, Optimizer::Uses*); | |
79 | + bool hasUses(Ice::Operand*) const; | |
80 | + | |
81 | + Ice::CfgNode* getNode(Ice::Inst*); | |
82 | + void setNode(Ice::Inst*, Ice::CfgNode*); | |
83 | + | |
84 | + Ice::Inst* getDefinition(Ice::Variable*); | |
85 | + void setDefinition(Ice::Variable*, Ice::Inst*); | |
86 | + | |
87 | + const std::vector<LoadStoreInst>& getLoadStoreInsts(Ice::CfgNode*); | |
88 | + void setLoadStoreInsts(Ice::CfgNode*, std::vector<LoadStoreInst>*); | |
89 | + bool hasLoadStoreInsts(Ice::CfgNode* node) const; | |
90 | + | |
91 | + std::vector<Optimizer::Uses*> allocatedUses; | |
67 | 92 | }; |
68 | 93 | |
69 | 94 | void Optimizer::run(Ice::Cfg *function) |
@@ -78,6 +103,12 @@ namespace | ||
78 | 103 | eliminateLoadsFollowingSingleStore(); |
79 | 104 | optimizeStoresInSingleBasicBlock(); |
80 | 105 | eliminateDeadCode(); |
106 | + | |
107 | + for(auto uses : allocatedUses) | |
108 | + { | |
109 | + delete uses; | |
110 | + } | |
111 | + allocatedUses.clear(); | |
81 | 112 | } |
82 | 113 | |
83 | 114 | void Optimizer::eliminateDeadCode() |
@@ -123,8 +154,13 @@ namespace | ||
123 | 154 | } |
124 | 155 | |
125 | 156 | Ice::Operand *address = alloca.getDest(); |
126 | - const auto &addressEntry = uses.find(address); | |
127 | - const auto &addressUses = addressEntry->second; | |
157 | + | |
158 | + if(!hasUses(address)) | |
159 | + { | |
160 | + return; | |
161 | + } | |
162 | + | |
163 | + const auto &addressUses = *getUses(address); | |
128 | 164 | |
129 | 165 | if(!addressUses.areOnlyLoadStore()) |
130 | 166 | { |
@@ -137,26 +173,29 @@ namespace | ||
137 | 173 | { |
138 | 174 | Ice::Variable *loadData = load->getDest(); |
139 | 175 | |
140 | - for(Ice::Inst *use : uses[loadData]) | |
176 | + if(hasUses(loadData)) | |
141 | 177 | { |
142 | - for(Ice::SizeT i = 0; i < use->getSrcSize(); i++) | |
178 | + for(Ice::Inst *use : *getUses(loadData)) | |
143 | 179 | { |
144 | - if(use->getSrc(i) == loadData) | |
180 | + for(Ice::SizeT i = 0; i < use->getSrcSize(); i++) | |
145 | 181 | { |
146 | - auto *undef = context->getConstantUndef(loadData->getType()); | |
182 | + if(use->getSrc(i) == loadData) | |
183 | + { | |
184 | + auto *undef = context->getConstantUndef(loadData->getType()); | |
147 | 185 | |
148 | - use->replaceSource(i, undef); | |
186 | + use->replaceSource(i, undef); | |
187 | + } | |
149 | 188 | } |
150 | 189 | } |
151 | - } | |
152 | 190 | |
153 | - uses.erase(loadData); | |
191 | + setUses(loadData, nullptr); | |
192 | + } | |
154 | 193 | |
155 | 194 | load->setDeleted(); |
156 | 195 | } |
157 | 196 | |
158 | 197 | alloca.setDeleted(); |
159 | - uses.erase(addressEntry); | |
198 | + setUses(address, nullptr); | |
160 | 199 | } |
161 | 200 | } |
162 | 201 | } |
@@ -178,8 +217,13 @@ namespace | ||
178 | 217 | } |
179 | 218 | |
180 | 219 | Ice::Operand *address = alloca.getDest(); |
181 | - const auto &addressEntry = uses.find(address); | |
182 | - auto &addressUses = addressEntry->second; | |
220 | + | |
221 | + if(!hasUses(address)) | |
222 | + { | |
223 | + return; | |
224 | + } | |
225 | + | |
226 | + auto &addressUses = *getUses(address); | |
183 | 227 | |
184 | 228 | if(!addressUses.areOnlyLoadStore()) |
185 | 229 | { |
@@ -236,23 +280,26 @@ namespace | ||
236 | 280 | |
237 | 281 | alloca.setDeleted(); |
238 | 282 | store->setDeleted(); |
239 | - uses.erase(address); | |
240 | - | |
241 | - auto &valueUses = uses[storeValue]; | |
283 | + setUses(address, nullptr); | |
242 | 284 | |
243 | - for(size_t i = 0; i < valueUses.size(); i++) | |
285 | + if(hasUses(storeValue)) | |
244 | 286 | { |
245 | - if(valueUses[i] == store) | |
287 | + auto &valueUses = *getUses(storeValue); | |
288 | + | |
289 | + for(size_t i = 0; i < valueUses.size(); i++) | |
246 | 290 | { |
247 | - valueUses[i] = valueUses.back(); | |
248 | - valueUses.pop_back(); | |
249 | - break; | |
291 | + if(valueUses[i] == store) | |
292 | + { | |
293 | + valueUses[i] = valueUses.back(); | |
294 | + valueUses.pop_back(); | |
295 | + break; | |
296 | + } | |
250 | 297 | } |
251 | - } | |
252 | 298 | |
253 | - if(valueUses.empty()) | |
254 | - { | |
255 | - uses.erase(storeValue); | |
299 | + if(valueUses.empty()) | |
300 | + { | |
301 | + setUses(storeValue, nullptr); | |
302 | + } | |
256 | 303 | } |
257 | 304 | |
258 | 305 | break; |
@@ -266,21 +313,7 @@ namespace | ||
266 | 313 | { |
267 | 314 | Ice::CfgNode *entryBlock = function->getEntryNode(); |
268 | 315 | |
269 | - struct LoadStoreInst | |
270 | - { | |
271 | - LoadStoreInst(Ice::Inst* inst, bool isStore) | |
272 | - : inst(inst), | |
273 | - address(isStore ? storeAddress(inst) : loadAddress(inst)), | |
274 | - isStore(isStore) | |
275 | - { | |
276 | - } | |
277 | - | |
278 | - Ice::Inst* inst; | |
279 | - Ice::Operand *address; | |
280 | - bool isStore; | |
281 | - }; | |
282 | - | |
283 | - std::unordered_map<Ice::CfgNode*, std::vector<LoadStoreInst> > loadStoreMap; | |
316 | + std::vector<std::vector<LoadStoreInst>* > allocatedVectors; | |
284 | 317 | |
285 | 318 | for(Ice::Inst &alloca : entryBlock->getInsts()) |
286 | 319 | { |
@@ -295,20 +328,25 @@ namespace | ||
295 | 328 | } |
296 | 329 | |
297 | 330 | Ice::Operand *address = alloca.getDest(); |
298 | - const auto &addressEntry = uses.find(address); | |
299 | - const auto &addressUses = addressEntry->second; | |
331 | + | |
332 | + if(!hasUses(address)) | |
333 | + { | |
334 | + return; | |
335 | + } | |
336 | + | |
337 | + const auto &addressUses = *getUses(address); | |
300 | 338 | |
301 | 339 | if(!addressUses.areOnlyLoadStore()) |
302 | 340 | { |
303 | 341 | continue; |
304 | 342 | } |
305 | 343 | |
306 | - Ice::CfgNode *singleBasicBlock = node[addressUses.stores[0]]; | |
344 | + Ice::CfgNode *singleBasicBlock = getNode(addressUses.stores[0]); | |
307 | 345 | |
308 | 346 | for(size_t i = 1; i < addressUses.stores.size(); i++) |
309 | 347 | { |
310 | 348 | Ice::Inst *store = addressUses.stores[i]; |
311 | - if(node[store] != singleBasicBlock) | |
349 | + if(getNode(store) != singleBasicBlock) | |
312 | 350 | { |
313 | 351 | singleBasicBlock = nullptr; |
314 | 352 | break; |
@@ -317,9 +355,11 @@ namespace | ||
317 | 355 | |
318 | 356 | if(singleBasicBlock) |
319 | 357 | { |
320 | - if(loadStoreMap.find(singleBasicBlock) == loadStoreMap.end()) | |
358 | + if(!hasLoadStoreInsts(singleBasicBlock)) | |
321 | 359 | { |
322 | - std::vector<LoadStoreInst> &loadStoreVector = loadStoreMap[singleBasicBlock]; | |
360 | + std::vector<LoadStoreInst>* loadStoreInstVector = new std::vector<LoadStoreInst>(); | |
361 | + setLoadStoreInsts(singleBasicBlock, loadStoreInstVector); | |
362 | + allocatedVectors.push_back(loadStoreInstVector); | |
323 | 363 | for(Ice::Inst &inst : singleBasicBlock->getInsts()) |
324 | 364 | { |
325 | 365 | if(inst.isDeleted()) |
@@ -332,7 +372,7 @@ namespace | ||
332 | 372 | |
333 | 373 | if(isStoreInst || isLoadInst) |
334 | 374 | { |
335 | - loadStoreVector.push_back(LoadStoreInst(&inst, isStoreInst)); | |
375 | + loadStoreInstVector->push_back(LoadStoreInst(&inst, isStoreInst)); | |
336 | 376 | } |
337 | 377 | } |
338 | 378 | } |
@@ -341,7 +381,7 @@ namespace | ||
341 | 381 | Ice::Operand *storeValue = nullptr; |
342 | 382 | bool unmatchedLoads = false; |
343 | 383 | |
344 | - for (auto& loadStoreInst : loadStoreMap[singleBasicBlock]) | |
384 | + for (auto& loadStoreInst : getLoadStoreInsts(singleBasicBlock)) | |
345 | 385 | { |
346 | 386 | Ice::Inst* inst = loadStoreInst.inst; |
347 | 387 |
@@ -380,14 +420,15 @@ namespace | ||
380 | 420 | } |
381 | 421 | } |
382 | 422 | } |
423 | + | |
424 | + for(auto loadStoreInstVector : allocatedVectors) | |
425 | + { | |
426 | + delete loadStoreInstVector; | |
427 | + } | |
383 | 428 | } |
384 | 429 | |
385 | 430 | void Optimizer::analyzeUses(Ice::Cfg *function) |
386 | 431 | { |
387 | - uses.clear(); | |
388 | - node.clear(); | |
389 | - definition.clear(); | |
390 | - | |
391 | 432 | for(Ice::CfgNode *basicBlock : function->getNodes()) |
392 | 433 | { |
393 | 434 | for(Ice::Inst &instruction : basicBlock->getInsts()) |
@@ -397,8 +438,11 @@ namespace | ||
397 | 438 | continue; |
398 | 439 | } |
399 | 440 | |
400 | - node[&instruction] = basicBlock; | |
401 | - definition[instruction.getDest()] = &instruction; | |
441 | + setNode(&instruction, basicBlock); | |
442 | + if(instruction.getDest()) | |
443 | + { | |
444 | + setDefinition(instruction.getDest(), &instruction); | |
445 | + } | |
402 | 446 | |
403 | 447 | for(Ice::SizeT i = 0; i < instruction.getSrcSize(); i++) |
404 | 448 | { |
@@ -414,7 +458,7 @@ namespace | ||
414 | 458 | if(i == unique) |
415 | 459 | { |
416 | 460 | Ice::Operand *src = instruction.getSrc(i); |
417 | - uses[src].insert(src, &instruction); | |
461 | + getUses(src)->insert(src, &instruction); | |
418 | 462 | } |
419 | 463 | } |
420 | 464 | } |
@@ -430,23 +474,26 @@ namespace | ||
430 | 474 | newValue = context->getConstantUndef(oldValue->getType()); |
431 | 475 | } |
432 | 476 | |
433 | - for(Ice::Inst *use : uses[oldValue]) | |
477 | + if(hasUses(oldValue)) | |
434 | 478 | { |
435 | - assert(!use->isDeleted()); // Should have been removed from uses already | |
436 | - | |
437 | - for(Ice::SizeT i = 0; i < use->getSrcSize(); i++) | |
479 | + for(Ice::Inst *use : *getUses(oldValue)) | |
438 | 480 | { |
439 | - if(use->getSrc(i) == oldValue) | |
481 | + assert(!use->isDeleted()); // Should have been removed from uses already | |
482 | + | |
483 | + for(Ice::SizeT i = 0; i < use->getSrcSize(); i++) | |
440 | 484 | { |
441 | - use->replaceSource(i, newValue); | |
485 | + if(use->getSrc(i) == oldValue) | |
486 | + { | |
487 | + use->replaceSource(i, newValue); | |
488 | + } | |
442 | 489 | } |
490 | + | |
491 | + getUses(newValue)->insert(newValue, use); | |
443 | 492 | } |
444 | 493 | |
445 | - uses[newValue].insert(newValue, use); | |
494 | + setUses(oldValue, nullptr); | |
446 | 495 | } |
447 | 496 | |
448 | - uses.erase(oldValue); | |
449 | - | |
450 | 497 | deleteInstruction(instruction); |
451 | 498 | } |
452 | 499 |
@@ -463,21 +510,19 @@ namespace | ||
463 | 510 | { |
464 | 511 | Ice::Operand *src = instruction->getSrc(i); |
465 | 512 | |
466 | - const auto &srcEntry = uses.find(src); | |
467 | - | |
468 | - if(srcEntry != uses.end()) | |
513 | + if(hasUses(src)) | |
469 | 514 | { |
470 | - auto &srcUses = srcEntry->second; | |
515 | + auto &srcUses = *getUses(src); | |
471 | 516 | |
472 | 517 | srcUses.erase(instruction); |
473 | 518 | |
474 | 519 | if(srcUses.empty()) |
475 | 520 | { |
476 | - uses.erase(srcEntry); | |
521 | + setUses(src, nullptr); | |
477 | 522 | |
478 | 523 | if(Ice::Variable *var = llvm::dyn_cast<Ice::Variable>(src)) |
479 | 524 | { |
480 | - deleteInstruction(definition[var]); | |
525 | + deleteInstruction(getDefinition(var)); | |
481 | 526 | } |
482 | 527 | } |
483 | 528 | } |
@@ -490,17 +535,25 @@ namespace | ||
490 | 535 | |
491 | 536 | if(dest) |
492 | 537 | { |
493 | - return uses[dest].empty() && !instruction->hasSideEffects(); | |
538 | + return (!hasUses(dest) || getUses(dest)->empty()) && !instruction->hasSideEffects(); | |
494 | 539 | } |
495 | 540 | else if(isStore(*instruction)) |
496 | 541 | { |
497 | 542 | if(Ice::Variable *address = llvm::dyn_cast<Ice::Variable>(storeAddress(instruction))) |
498 | 543 | { |
499 | - Ice::Inst *def = definition[address]; | |
544 | + Ice::Inst *def = getDefinition(address); | |
500 | 545 | |
501 | 546 | if(def && llvm::isa<Ice::InstAlloca>(def)) |
502 | 547 | { |
503 | - return uses[address].size() == uses[address].stores.size(); // Dead if all uses are stores | |
548 | + if(hasUses(address)) | |
549 | + { | |
550 | + Optimizer::Uses* uses = getUses(address); | |
551 | + return uses->size() == uses->stores.size(); // Dead if all uses are stores | |
552 | + } | |
553 | + else | |
554 | + { | |
555 | + return true; // No uses | |
556 | + } | |
504 | 557 | } |
505 | 558 | } |
506 | 559 | } |
@@ -654,6 +707,63 @@ namespace | ||
654 | 707 | return false; |
655 | 708 | } |
656 | 709 | |
710 | + Optimizer::Uses* Optimizer::getUses(Ice::Operand* operand) | |
711 | + { | |
712 | + Optimizer::Uses* uses = (Optimizer::Uses*)operand->Ice::Operand::getExternalData(); | |
713 | + if(!uses) | |
714 | + { | |
715 | + uses = new Optimizer::Uses; | |
716 | + setUses(operand, uses); | |
717 | + allocatedUses.push_back(uses); | |
718 | + } | |
719 | + return uses; | |
720 | + } | |
721 | + | |
722 | + void Optimizer::setUses(Ice::Operand* operand, Optimizer::Uses* uses) | |
723 | + { | |
724 | + operand->Ice::Operand::setExternalData(uses); | |
725 | + } | |
726 | + | |
727 | + bool Optimizer::hasUses(Ice::Operand* operand) const | |
728 | + { | |
729 | + return operand->Ice::Operand::getExternalData() != nullptr; | |
730 | + } | |
731 | + | |
732 | + Ice::CfgNode* Optimizer::getNode(Ice::Inst* inst) | |
733 | + { | |
734 | + return (Ice::CfgNode*)inst->Ice::Inst::getExternalData(); | |
735 | + } | |
736 | + | |
737 | + void Optimizer::setNode(Ice::Inst* inst, Ice::CfgNode* node) | |
738 | + { | |
739 | + inst->Ice::Inst::setExternalData(node); | |
740 | + } | |
741 | + | |
742 | + Ice::Inst* Optimizer::getDefinition(Ice::Variable* var) | |
743 | + { | |
744 | + return (Ice::Inst*)var->Ice::Variable::getExternalData(); | |
745 | + } | |
746 | + | |
747 | + void Optimizer::setDefinition(Ice::Variable* var, Ice::Inst* inst) | |
748 | + { | |
749 | + var->Ice::Variable::setExternalData(inst); | |
750 | + } | |
751 | + | |
752 | + const std::vector<Optimizer::LoadStoreInst>& Optimizer::getLoadStoreInsts(Ice::CfgNode* node) | |
753 | + { | |
754 | + return *((const std::vector<LoadStoreInst>*)node->Ice::CfgNode::getExternalData()); | |
755 | + } | |
756 | + | |
757 | + void Optimizer::setLoadStoreInsts(Ice::CfgNode* node, std::vector<LoadStoreInst>* insts) | |
758 | + { | |
759 | + node->Ice::CfgNode::setExternalData(insts); | |
760 | + } | |
761 | + | |
762 | + bool Optimizer::hasLoadStoreInsts(Ice::CfgNode* node) const | |
763 | + { | |
764 | + return node->Ice::CfgNode::getExternalData() != nullptr; | |
765 | + } | |
766 | + | |
657 | 767 | bool Optimizer::Uses::areOnlyLoadStore() const |
658 | 768 | { |
659 | 769 | return size() == (loads.size() + stores.size()); |
@@ -127,6 +127,9 @@ public: | ||
127 | 127 | } |
128 | 128 | CfgNode *shortCircuit(); |
129 | 129 | |
130 | + inline void* getExternalData() const { return externalData; } | |
131 | + inline void setExternalData(void* data) { externalData = data; } | |
132 | + | |
130 | 133 | private: |
131 | 134 | CfgNode(Cfg *Func, SizeT Number) |
132 | 135 | : Func(Func), Number(Number), NumberOrig(Number), |
@@ -145,6 +148,11 @@ private: | ||
145 | 148 | NodeList OutEdges; /// in no particular order |
146 | 149 | PhiList Phis; /// unordered set of phi instructions |
147 | 150 | InstList Insts; /// ordered list of non-phi instructions |
151 | + | |
152 | + /// External data can be set by an optimizer to compute and retain any | |
153 | + /// information related to the current node. All the memory used to | |
154 | + /// store this information must be managed by the optimizer. | |
155 | + void* externalData = nullptr; | |
148 | 156 | }; |
149 | 157 | |
150 | 158 | } // end of namespace Ice |
@@ -199,6 +199,9 @@ public: | ||
199 | 199 | llvm::report_fatal_error("Inst unexpectedly deleted"); |
200 | 200 | } |
201 | 201 | |
202 | + inline void* getExternalData() const { return externalData; } | |
203 | + inline void setExternalData(void* data) { externalData = data; } | |
204 | + | |
202 | 205 | protected: |
203 | 206 | Inst(Cfg *Func, InstKind Kind, SizeT MaxSrcs, Variable *Dest); |
204 | 207 | void addSource(Operand *Src) { |
@@ -236,6 +239,10 @@ protected: | ||
236 | 239 | /// live range recorded in a basic block has at most one start and at most one |
237 | 240 | /// end. |
238 | 241 | bool IsDestRedefined = false; |
242 | + /// External data can be set by an optimizer to compute and retain any | |
243 | + /// information related to the current instruction. All the memory used to | |
244 | + /// store this information must be managed by the optimizer. | |
245 | + void* externalData = nullptr; | |
239 | 246 | |
240 | 247 | Variable *Dest; |
241 | 248 | const SizeT MaxSrcs; // only used for assert |
@@ -106,6 +106,9 @@ public: | ||
106 | 106 | return 0; |
107 | 107 | } |
108 | 108 | |
109 | + inline void* getExternalData() const { return externalData; } | |
110 | + inline void setExternalData(void* data) { externalData = data; } | |
111 | + | |
109 | 112 | protected: |
110 | 113 | Operand(OperandKind Kind, Type Ty) : Ty(Ty), Kind(Kind) { |
111 | 114 | // It is undefined behavior to have a larger value in the enum |
@@ -117,6 +120,11 @@ protected: | ||
117 | 120 | /// Vars and NumVars are initialized by the derived class. |
118 | 121 | SizeT NumVars = 0; |
119 | 122 | Variable **Vars = nullptr; |
123 | + | |
124 | + /// External data can be set by an optimizer to compute and retain any | |
125 | + /// information related to the current operand. All the memory used to | |
126 | + /// store this information must be managed by the optimizer. | |
127 | + void* externalData = nullptr; | |
120 | 128 | }; |
121 | 129 | |
122 | 130 | template <class StreamType> |
@@ -854,6 +862,9 @@ public: | ||
854 | 862 | |
855 | 863 | SizeT hashValue() const override { return std::hash<SizeT>()(getIndex()); } |
856 | 864 | |
865 | + inline void* getExternalData() const { return externalData; } | |
866 | + inline void setExternalData(void* data) { externalData = data; } | |
867 | + | |
857 | 868 | protected: |
858 | 869 | Variable(const Cfg *Func, OperandKind K, Type Ty, SizeT Index) |
859 | 870 | : Operand(K, Ty), Number(Index), |
@@ -895,6 +906,10 @@ protected: | ||
895 | 906 | /// This Variable may be "linked" to another Variable, such that if neither |
896 | 907 | /// Variable gets a register, they are guaranteed to share a stack location. |
897 | 908 | Variable *LinkedTo = nullptr; |
909 | + /// External data can be set by an optimizer to compute and retain any | |
910 | + /// information related to the current variable. All the memory used to | |
911 | + /// store this information must be managed by the optimizer. | |
912 | + void* externalData = nullptr; | |
898 | 913 | }; |
899 | 914 | |
900 | 915 | // Variable64On32 represents a 64-bit variable on a 32-bit architecture. In |