Line data Source code
1 : #ifdef __PLUMED_HAS_ASMJIT
2 : // This file is part of AsmJit project <https://asmjit.com>
3 : //
4 : // See asmjit.h or LICENSE.md for license and copyright information
5 : // SPDX-License-Identifier: Zlib
6 :
7 : #ifndef ASMJIT_CORE_RABUILDERS_P_H_INCLUDED
8 : #define ASMJIT_CORE_RABUILDERS_P_H_INCLUDED
9 :
10 : #include "api-config.h"
11 : #ifndef ASMJIT_NO_COMPILER
12 :
13 : #include "formatter.h"
14 : #include "rapass_p.h"
15 :
16 : ASMJIT_BEGIN_NAMESPACE
17 :
18 : //! \cond INTERNAL
19 : //! \addtogroup asmjit_ra
20 : //! \{
21 :
22 : template<typename This>
23 : class RACFGBuilderT {
24 : public:
25 : enum : uint32_t {
26 : kRootIndentation = 2,
27 : kCodeIndentation = 4,
28 :
29 : // NOTE: This is a bit hacky. There are some nodes which are processed twice (see `onBeforeInvoke()` and
30 : // `onBeforeRet()`) as they can insert some nodes around them. Since we don't have any flags to mark these
31 : // we just use their position that is [at that time] unassigned.
32 : kNodePositionDidOnBefore = 0xFFFFFFFFu
33 : };
34 :
35 : //! \name Members
36 : //! \{
37 :
38 : BaseRAPass* _pass = nullptr;
39 : BaseCompiler* _cc = nullptr;
40 : RABlock* _curBlock = nullptr;
41 : RABlock* _retBlock = nullptr;
42 : FuncNode* _funcNode = nullptr;
43 : RARegsStats _blockRegStats {};
44 : uint32_t _exitLabelId = Globals::kInvalidId;
45 : ZoneVector<uint32_t> _sharedAssignmentsMap {};
46 :
47 : // Only used by logging, it's fine to be here to prevent more #ifdefs...
48 : bool _hasCode = false;
49 : RABlock* _lastLoggedBlock = nullptr;
50 :
51 : #ifndef ASMJIT_NO_LOGGING
52 : Logger* _logger = nullptr;
53 : FormatOptions _formatOptions {};
54 : StringTmp<512> _sb;
55 : #endif
56 :
57 : //! \}
58 :
59 27492 : inline RACFGBuilderT(BaseRAPass* pass) noexcept
60 27492 : : _pass(pass),
61 27492 : _cc(pass->cc()) {
62 : #ifndef ASMJIT_NO_LOGGING
63 27492 : _logger = _pass->hasDiagnosticOption(DiagnosticOptions::kRADebugCFG) ? _pass->logger() : nullptr;
64 27492 : if (_logger)
65 0 : _formatOptions = _logger->options();
66 : #endif
67 27492 : }
68 :
69 0 : inline BaseCompiler* cc() const noexcept { return _cc; }
70 :
71 : //! \name Run
72 : //! \{
73 :
74 : //! Called per function by an architecture-specific CFG builder.
75 27492 : Error run() noexcept {
76 : log("[BuildCFG]\n");
77 27492 : ASMJIT_PROPAGATE(prepare());
78 :
79 27492 : logNode(_funcNode, kRootIndentation);
80 27492 : logBlock(_curBlock, kRootIndentation);
81 :
82 27492 : RABlock* entryBlock = _curBlock;
83 27492 : BaseNode* node = _funcNode->next();
84 27492 : if (ASMJIT_UNLIKELY(!node))
85 : return DebugUtils::errored(kErrorInvalidState);
86 :
87 : _curBlock->setFirst(_funcNode);
88 27492 : _curBlock->setLast(_funcNode);
89 :
90 : RAInstBuilder ib;
91 : ZoneVector<RABlock*> blocksWithUnknownJumps;
92 :
93 : for (;;) {
94 : BaseNode* next = node->next();
95 : ASMJIT_ASSERT(node->position() == 0 || node->position() == kNodePositionDidOnBefore);
96 :
97 386792 : if (node->isInst()) {
98 : // Instruction | Jump | Invoke | Return
99 : // ------------------------------------
100 :
101 : // Handle `InstNode`, `InvokeNode`, and `FuncRetNode`. All of them share the same interface that provides
102 : // operands that have read/write semantics.
103 331808 : if (ASMJIT_UNLIKELY(!_curBlock)) {
104 : // Unreachable code has to be removed, we cannot allocate registers in such code as we cannot do proper
105 : // liveness analysis in such case.
106 0 : removeNode(node);
107 : node = next;
108 0 : continue;
109 : }
110 :
111 331808 : _hasCode = true;
112 :
113 331808 : if (node->isInvoke() || node->isFuncRet()) {
114 37225 : if (node->position() != kNodePositionDidOnBefore) {
115 : // Call and Reg are complicated as they may insert some surrounding code around them. The simplest
116 : // approach is to get the previous node, call the `onBefore()` handlers and then check whether
117 : // anything changed and restart if so. By restart we mean that the current `node` would go back to
118 : // the first possible inserted node by `onBeforeInvoke()` or `onBeforeRet()`.
119 : BaseNode* prev = node->prev();
120 :
121 37131 : if (node->type() == NodeType::kInvoke)
122 9639 : ASMJIT_PROPAGATE(static_cast<This*>(this)->onBeforeInvoke(node->as<InvokeNode>()));
123 : else
124 27492 : ASMJIT_PROPAGATE(static_cast<This*>(this)->onBeforeRet(node->as<FuncRetNode>()));
125 :
126 37131 : if (prev != node->prev()) {
127 : // If this was the first node in the block and something was
128 : // inserted before it then we have to update the first block.
129 94 : if (_curBlock->first() == node)
130 : _curBlock->setFirst(prev->next());
131 :
132 : node->setPosition(kNodePositionDidOnBefore);
133 : node = prev->next();
134 :
135 : // `onBeforeInvoke()` and `onBeforeRet()` can only insert instructions.
136 : ASMJIT_ASSERT(node->isInst());
137 : }
138 :
139 : // Necessary if something was inserted after `node`, but nothing before.
140 : next = node->next();
141 : }
142 : else {
143 : // Change the position back to its original value.
144 : node->setPosition(0);
145 : }
146 : }
147 :
148 : InstNode* inst = node->as<InstNode>();
149 : logNode(inst, kCodeIndentation);
150 :
151 331808 : InstControlFlow cf = InstControlFlow::kRegular;
152 331808 : ib.reset();
153 331808 : ASMJIT_PROPAGATE(static_cast<This*>(this)->onInst(inst, cf, ib));
154 :
155 331808 : if (node->isInvoke()) {
156 9639 : ASMJIT_PROPAGATE(static_cast<This*>(this)->onInvoke(inst->as<InvokeNode>(), ib));
157 : }
158 :
159 331808 : if (node->isFuncRet()) {
160 27492 : ASMJIT_PROPAGATE(static_cast<This*>(this)->onRet(inst->as<FuncRetNode>(), ib));
161 27492 : cf = InstControlFlow::kReturn;
162 : }
163 :
164 331808 : if (cf == InstControlFlow::kJump) {
165 : uint32_t fixedRegCount = 0;
166 0 : for (RATiedReg& tiedReg : ib) {
167 0 : RAWorkReg* workReg = _pass->workRegById(tiedReg.workId());
168 0 : if (workReg->group() == RegGroup::kGp) {
169 : uint32_t useId = tiedReg.useId();
170 0 : if (useId == BaseReg::kIdBad) {
171 0 : useId = _pass->_scratchRegIndexes[fixedRegCount++];
172 : tiedReg.setUseId(useId);
173 : }
174 0 : _curBlock->addExitScratchGpRegs(Support::bitMask(useId));
175 : }
176 : }
177 : }
178 :
179 663616 : ASMJIT_PROPAGATE(_pass->assignRAInst(inst, _curBlock, ib));
180 : _blockRegStats.combineWith(ib._stats);
181 :
182 331808 : if (cf != InstControlFlow::kRegular) {
183 : // Support for conditional and unconditional jumps.
184 37131 : if (cf == InstControlFlow::kJump || cf == InstControlFlow::kBranch) {
185 0 : _curBlock->setLast(node);
186 0 : _curBlock->addFlags(RABlockFlags::kHasTerminator);
187 0 : _curBlock->makeConstructed(_blockRegStats);
188 :
189 0 : if (!inst->hasOption(InstOptions::kUnfollow)) {
190 : // Jmp/Jcc/Call/Loop/etc...
191 : uint32_t opCount = inst->opCount();
192 : const Operand* opArray = inst->operands();
193 :
194 : // Cannot jump anywhere without operands.
195 0 : if (ASMJIT_UNLIKELY(!opCount))
196 : return DebugUtils::errored(kErrorInvalidState);
197 :
198 0 : if (opArray[opCount - 1].isLabel()) {
199 : // Labels are easy for constructing the control flow.
200 : LabelNode* labelNode;
201 0 : ASMJIT_PROPAGATE(cc()->labelNodeOf(&labelNode, opArray[opCount - 1].as<Label>()));
202 :
203 0 : RABlock* targetBlock = _pass->newBlockOrExistingAt(labelNode);
204 0 : if (ASMJIT_UNLIKELY(!targetBlock))
205 : return DebugUtils::errored(kErrorOutOfMemory);
206 :
207 : targetBlock->makeTargetable();
208 0 : ASMJIT_PROPAGATE(_curBlock->appendSuccessor(targetBlock));
209 : }
210 : else {
211 : // Not a label - could be jump with reg/mem operand, which means that it can go anywhere. Such jumps
212 : // must either be annotated so the CFG can be properly constructed, otherwise we assume the worst case
213 : // - can jump to any basic block.
214 : JumpAnnotation* jumpAnnotation = nullptr;
215 0 : _curBlock->addFlags(RABlockFlags::kHasJumpTable);
216 :
217 0 : if (inst->type() == NodeType::kJump)
218 : jumpAnnotation = inst->as<JumpNode>()->annotation();
219 :
220 0 : if (jumpAnnotation) {
221 0 : uint64_t timestamp = _pass->nextTimestamp();
222 0 : for (uint32_t id : jumpAnnotation->labelIds()) {
223 : LabelNode* labelNode;
224 0 : ASMJIT_PROPAGATE(cc()->labelNodeOf(&labelNode, id));
225 :
226 0 : RABlock* targetBlock = _pass->newBlockOrExistingAt(labelNode);
227 0 : if (ASMJIT_UNLIKELY(!targetBlock))
228 : return DebugUtils::errored(kErrorOutOfMemory);
229 :
230 : // Prevents adding basic-block successors multiple times.
231 0 : if (!targetBlock->hasTimestamp(timestamp)) {
232 : targetBlock->setTimestamp(timestamp);
233 : targetBlock->makeTargetable();
234 0 : ASMJIT_PROPAGATE(_curBlock->appendSuccessor(targetBlock));
235 : }
236 : }
237 0 : ASMJIT_PROPAGATE(shareAssignmentAcrossSuccessors(_curBlock));
238 : }
239 : else {
240 0 : ASMJIT_PROPAGATE(blocksWithUnknownJumps.append(_pass->allocator(), _curBlock));
241 : }
242 : }
243 : }
244 :
245 0 : if (cf == InstControlFlow::kJump) {
246 : // Unconditional jump makes the code after the jump unreachable, which will be removed instantly during
247 : // the CFG construction; as we cannot allocate registers for instructions that are not part of any block.
248 : // Of course we can leave these instructions as they are, however, that would only postpone the problem
249 : // as assemblers can't encode instructions that use virtual registers.
250 0 : _curBlock = nullptr;
251 : }
252 : else {
253 : node = next;
254 0 : if (ASMJIT_UNLIKELY(!node))
255 : return DebugUtils::errored(kErrorInvalidState);
256 :
257 : RABlock* consecutiveBlock;
258 0 : if (node->type() == NodeType::kLabel) {
259 0 : if (node->hasPassData()) {
260 : consecutiveBlock = node->passData<RABlock>();
261 : }
262 : else {
263 0 : consecutiveBlock = _pass->newBlock(node);
264 0 : if (ASMJIT_UNLIKELY(!consecutiveBlock))
265 : return DebugUtils::errored(kErrorOutOfMemory);
266 : node->setPassData<RABlock>(consecutiveBlock);
267 : }
268 : }
269 : else {
270 0 : consecutiveBlock = _pass->newBlock(node);
271 0 : if (ASMJIT_UNLIKELY(!consecutiveBlock))
272 : return DebugUtils::errored(kErrorOutOfMemory);
273 : }
274 :
275 0 : _curBlock->addFlags(RABlockFlags::kHasConsecutive);
276 0 : ASMJIT_PROPAGATE(_curBlock->prependSuccessor(consecutiveBlock));
277 :
278 0 : _curBlock = consecutiveBlock;
279 0 : _hasCode = false;
280 : _blockRegStats.reset();
281 :
282 0 : if (_curBlock->isConstructed())
283 : break;
284 0 : ASMJIT_PROPAGATE(_pass->addBlock(consecutiveBlock));
285 :
286 0 : logBlock(_curBlock, kRootIndentation);
287 0 : continue;
288 0 : }
289 : }
290 :
291 37131 : if (cf == InstControlFlow::kReturn) {
292 27492 : _curBlock->setLast(node);
293 27492 : _curBlock->makeConstructed(_blockRegStats);
294 27492 : ASMJIT_PROPAGATE(_curBlock->appendSuccessor(_retBlock));
295 :
296 27492 : _curBlock = nullptr;
297 : }
298 : }
299 : }
300 54984 : else if (node->type() == NodeType::kLabel) {
301 : // Label - Basic-Block Management
302 : // ------------------------------
303 :
304 27492 : if (!_curBlock) {
305 : // If the current code is unreachable the label makes it reachable again. We may remove the whole block in
306 : // the future if it's not referenced though.
307 27492 : _curBlock = node->passData<RABlock>();
308 :
309 27492 : if (_curBlock) {
310 : // If the label has a block assigned we can either continue with it or skip it if the block has been
311 : // constructed already.
312 27492 : if (_curBlock->isConstructed())
313 : break;
314 : }
315 : else {
316 : // No block assigned - create a new one and assign it.
317 0 : _curBlock = _pass->newBlock(node);
318 0 : if (ASMJIT_UNLIKELY(!_curBlock))
319 : return DebugUtils::errored(kErrorOutOfMemory);
320 : node->setPassData<RABlock>(_curBlock);
321 : }
322 :
323 27492 : _curBlock->makeTargetable();
324 27492 : _hasCode = false;
325 : _blockRegStats.reset();
326 27492 : ASMJIT_PROPAGATE(_pass->addBlock(_curBlock));
327 : }
328 : else {
329 0 : if (node->hasPassData()) {
330 : RABlock* consecutive = node->passData<RABlock>();
331 : consecutive->makeTargetable();
332 :
333 0 : if (_curBlock == consecutive) {
334 : // The label currently processed is part of the current block. This is only possible for multiple labels
335 : // that are right next to each other or labels that are separated by non-code nodes like directives and
336 : // comments.
337 0 : if (ASMJIT_UNLIKELY(_hasCode))
338 : return DebugUtils::errored(kErrorInvalidState);
339 : }
340 : else {
341 : // Label makes the current block constructed. There is a chance that the Label is not used, but we don't
342 : // know that at this point. In the worst case there would be two blocks next to each other, it's just fine.
343 : ASMJIT_ASSERT(_curBlock->last() != node);
344 : _curBlock->setLast(node->prev());
345 0 : _curBlock->addFlags(RABlockFlags::kHasConsecutive);
346 0 : _curBlock->makeConstructed(_blockRegStats);
347 :
348 0 : ASMJIT_PROPAGATE(_curBlock->appendSuccessor(consecutive));
349 0 : ASMJIT_PROPAGATE(_pass->addBlock(consecutive));
350 :
351 0 : _curBlock = consecutive;
352 0 : _hasCode = false;
353 : _blockRegStats.reset();
354 : }
355 : }
356 : else {
357 : // First time we see this label.
358 0 : if (_hasCode || _curBlock == entryBlock) {
359 : // Cannot continue the current block if it already contains some code or it's a block entry. We need to
360 : // create a new block and make it a successor.
361 : ASMJIT_ASSERT(_curBlock->last() != node);
362 : _curBlock->setLast(node->prev());
363 0 : _curBlock->addFlags(RABlockFlags::kHasConsecutive);
364 0 : _curBlock->makeConstructed(_blockRegStats);
365 :
366 0 : RABlock* consecutive = _pass->newBlock(node);
367 0 : if (ASMJIT_UNLIKELY(!consecutive))
368 : return DebugUtils::errored(kErrorOutOfMemory);
369 : consecutive->makeTargetable();
370 :
371 0 : ASMJIT_PROPAGATE(_curBlock->appendSuccessor(consecutive));
372 0 : ASMJIT_PROPAGATE(_pass->addBlock(consecutive));
373 :
374 0 : _curBlock = consecutive;
375 0 : _hasCode = false;
376 : _blockRegStats.reset();
377 : }
378 :
379 0 : node->setPassData<RABlock>(_curBlock);
380 : }
381 : }
382 :
383 27492 : if (_curBlock && _curBlock != _lastLoggedBlock)
384 : logBlock(_curBlock, kRootIndentation);
385 : logNode(node, kRootIndentation);
386 :
387 : // Unlikely: Assume that the exit label is reached only once per function.
388 27492 : if (ASMJIT_UNLIKELY(node->as<LabelNode>()->labelId() == _exitLabelId)) {
389 27492 : _curBlock->setLast(node);
390 27492 : _curBlock->makeConstructed(_blockRegStats);
391 27492 : ASMJIT_PROPAGATE(_pass->addExitBlock(_curBlock));
392 :
393 27492 : _curBlock = nullptr;
394 : }
395 : }
396 : else {
397 : // Other Nodes | Function Exit
398 : // ---------------------------
399 :
400 : logNode(node, kCodeIndentation);
401 :
402 27492 : if (node->type() == NodeType::kSentinel) {
403 27492 : if (node == _funcNode->endNode()) {
404 : // Make sure we didn't flow here if this is the end of the function sentinel.
405 27492 : if (ASMJIT_UNLIKELY(_curBlock))
406 : return DebugUtils::errored(kErrorInvalidState);
407 : break;
408 : }
409 : }
410 0 : else if (node->type() == NodeType::kFunc) {
411 : // RAPass can only compile a single function at a time. If we
412 : // encountered a function it must be the current one, bail if not.
413 0 : if (ASMJIT_UNLIKELY(node != _funcNode))
414 : return DebugUtils::errored(kErrorInvalidState);
415 : // PASS if this is the first node.
416 : }
417 : else {
418 : // PASS if this is a non-interesting or unknown node.
419 : }
420 : }
421 :
422 : // Advance to the next node.
423 : node = next;
424 :
425 : // NOTE: We cannot encounter a NULL node, because every function must be terminated by a sentinel (`stop`)
426 : // node. If we encountered a NULL node it means that something went wrong and this node list is corrupted;
427 : // bail in such case.
428 359300 : if (ASMJIT_UNLIKELY(!node))
429 : return DebugUtils::errored(kErrorInvalidState);
430 : }
431 :
432 27492 : if (_pass->hasDanglingBlocks())
433 : return DebugUtils::errored(kErrorInvalidState);
434 :
435 27492 : for (RABlock* block : blocksWithUnknownJumps)
436 0 : handleBlockWithUnknownJump(block);
437 :
438 27492 : return _pass->initSharedAssignments(_sharedAssignmentsMap);
439 : }
440 :
441 : //! \}
442 :
443 : //! \name Prepare
444 : //! \{
445 :
446 : //! Prepares the CFG builder of the current function.
447 27492 : Error prepare() noexcept {
448 27492 : FuncNode* func = _pass->func();
449 27492 : BaseNode* node = nullptr;
450 :
451 : // Create entry and exit blocks.
452 27492 : _funcNode = func;
453 27492 : _retBlock = _pass->newBlockOrExistingAt(func->exitNode(), &node);
454 :
455 27492 : if (ASMJIT_UNLIKELY(!_retBlock))
456 : return DebugUtils::errored(kErrorOutOfMemory);
457 :
458 : _retBlock->makeTargetable();
459 27492 : ASMJIT_PROPAGATE(_pass->addExitBlock(_retBlock));
460 :
461 27492 : if (node != func) {
462 27492 : _curBlock = _pass->newBlock();
463 27492 : if (ASMJIT_UNLIKELY(!_curBlock))
464 : return DebugUtils::errored(kErrorOutOfMemory);
465 : }
466 : else {
467 : // Function that has no code at all.
468 0 : _curBlock = _retBlock;
469 : }
470 :
471 : // Reset everything we may need.
472 : _blockRegStats.reset();
473 27492 : _exitLabelId = func->exitNode()->labelId();
474 :
475 : // Initially we assume there is no code in the function body.
476 27492 : _hasCode = false;
477 :
478 27492 : return _pass->addBlock(_curBlock);
479 : }
480 :
481 : //! \}
482 :
483 : //! \name Utilities
484 : //! \{
485 :
486 : //! Called when a `node` is removed, e.g. because of a dead code elimination.
487 0 : void removeNode(BaseNode* node) noexcept {
488 : logNode(node, kRootIndentation, "<Removed>");
489 0 : cc()->removeNode(node);
490 0 : }
491 :
492 : //! Handles block with unknown jump, which could be a jump to a jump table.
493 : //!
494 : //! If we encounter such block we basically insert all existing blocks as successors except the function entry
495 : //! block and a natural successor, if such block exists.
496 0 : Error handleBlockWithUnknownJump(RABlock* block) noexcept {
497 0 : RABlocks& blocks = _pass->blocks();
498 0 : size_t blockCount = blocks.size();
499 :
500 : // NOTE: Iterate from `1` as the first block is the entry block, we don't
501 : // allow the entry to be a successor of any block.
502 : RABlock* consecutive = block->consecutive();
503 0 : for (size_t i = 1; i < blockCount; i++) {
504 0 : RABlock* candidate = blocks[i];
505 0 : if (candidate == consecutive || !candidate->isTargetable())
506 0 : continue;
507 0 : block->appendSuccessor(candidate);
508 : }
509 :
510 0 : return shareAssignmentAcrossSuccessors(block);
511 : }
512 :
513 0 : Error shareAssignmentAcrossSuccessors(RABlock* block) noexcept {
514 0 : if (block->successors().size() <= 1)
515 : return kErrorOk;
516 :
517 : RABlock* consecutive = block->consecutive();
518 0 : uint32_t sharedAssignmentId = Globals::kInvalidId;
519 :
520 0 : for (RABlock* successor : block->successors()) {
521 0 : if (successor == consecutive)
522 0 : continue;
523 :
524 0 : if (successor->hasSharedAssignmentId()) {
525 0 : if (sharedAssignmentId == Globals::kInvalidId)
526 0 : sharedAssignmentId = successor->sharedAssignmentId();
527 : else
528 0 : _sharedAssignmentsMap[successor->sharedAssignmentId()] = sharedAssignmentId;
529 : }
530 : else {
531 0 : if (sharedAssignmentId == Globals::kInvalidId)
532 0 : ASMJIT_PROPAGATE(newSharedAssignmentId(&sharedAssignmentId));
533 0 : successor->setSharedAssignmentId(sharedAssignmentId);
534 : }
535 : }
536 : return kErrorOk;
537 : }
538 :
539 0 : Error newSharedAssignmentId(uint32_t* out) noexcept {
540 0 : uint32_t id = _sharedAssignmentsMap.size();
541 0 : ASMJIT_PROPAGATE(_sharedAssignmentsMap.append(_pass->allocator(), id));
542 :
543 0 : *out = id;
544 0 : return kErrorOk;
545 : }
546 :
547 : //! \}
548 :
549 : //! \name Logging
550 : //! \{
551 :
552 : #ifndef ASMJIT_NO_LOGGING
553 : template<typename... Args>
554 : inline void log(const char* fmt, Args&&... args) noexcept {
555 27492 : if (_logger)
556 0 : _logger->logf(fmt, std::forward<Args>(args)...);
557 : }
558 :
559 : inline void logBlock(RABlock* block, uint32_t indentation = 0) noexcept {
560 54984 : if (_logger)
561 0 : _logBlock(block, indentation);
562 : }
563 :
564 : inline void logNode(BaseNode* node, uint32_t indentation = 0, const char* action = nullptr) noexcept {
565 414284 : if (_logger)
566 0 : _logNode(node, indentation, action);
567 : }
568 :
569 0 : void _logBlock(RABlock* block, uint32_t indentation) noexcept {
570 0 : _sb.clear();
571 0 : _sb.appendChars(' ', indentation);
572 : _sb.appendFormat("{#%u}\n", block->blockId());
573 0 : _logger->log(_sb);
574 0 : _lastLoggedBlock = block;
575 0 : }
576 :
577 0 : void _logNode(BaseNode* node, uint32_t indentation, const char* action) noexcept {
578 0 : _sb.clear();
579 0 : _sb.appendChars(' ', indentation);
580 0 : if (action) {
581 : _sb.append(action);
582 : _sb.append(' ');
583 : }
584 0 : Formatter::formatNode(_sb, _formatOptions, cc(), node);
585 : _sb.append('\n');
586 0 : _logger->log(_sb);
587 0 : }
588 : #else
589 : template<typename... Args>
590 : inline void log(const char* fmt, Args&&... args) noexcept {
591 : DebugUtils::unused(fmt);
592 : DebugUtils::unused(std::forward<Args>(args)...);
593 : }
594 :
595 : inline void logBlock(RABlock* block, uint32_t indentation = 0) noexcept {
596 : DebugUtils::unused(block, indentation);
597 : }
598 :
599 : inline void logNode(BaseNode* node, uint32_t indentation = 0, const char* action = nullptr) noexcept {
600 : DebugUtils::unused(node, indentation, action);
601 : }
602 : #endif
603 :
604 : //! \}
605 : };
606 :
607 : //! \}
608 : //! \endcond
609 :
610 : ASMJIT_END_NAMESPACE
611 :
612 : #endif // !ASMJIT_NO_COMPILER
613 : #endif // ASMJIT_CORE_RABUILDERS_P_H_INCLUDED
614 :
615 : #endif // __PLUMED_HAS_ASMJIT
|