From c187cddacb3b9b9f0ab2364ca4046708fd81d134 Mon Sep 17 00:00:00 2001 From: "Aman Khalid (from Dev Box)" Date: Mon, 23 Sep 2024 11:33:54 -0400 Subject: [PATCH 01/10] Use linear block order in MinOpts --- src/coreclr/jit/lsra.cpp | 56 ++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/src/coreclr/jit/lsra.cpp b/src/coreclr/jit/lsra.cpp index c57ae36038e8d8..7bd26e74dfd913 100644 --- a/src/coreclr/jit/lsra.cpp +++ b/src/coreclr/jit/lsra.cpp @@ -951,18 +951,44 @@ void LinearScan::setBlockSequence() // Initialize the "visited" blocks set. bbVisitedSet = BlockSetOps::MakeEmpty(compiler); - assert((blockSequence == nullptr) && (bbSeqCount == 0)); - FlowGraphDfsTree* const dfsTree = compiler->fgComputeDfs(); - blockSequence = dfsTree->GetPostOrder(); - bbNumMaxBeforeResolution = compiler->fgBBNumMax; - blockInfo = new (compiler, CMK_LSRA) LsraBlockInfo[bbNumMaxBeforeResolution + 1]; + // If optimizations are enabled, allocate blocks in reverse post-order. + // This ensures each block's predecessors are visited first. + auto getRpoSequence = [this]() -> BasicBlock** { + FlowGraphDfsTree* const dfsTree = compiler->fgComputeDfs(); + BasicBlock** const postOrder = dfsTree->GetPostOrder(); + bbSeqCount = dfsTree->GetPostOrderCount(); - // Flip the DFS traversal to get the reverse post-order traversal - // (this is the order in which blocks will be allocated) - for (unsigned left = 0, right = dfsTree->GetPostOrderCount() - 1; left < right; left++, right--) - { - std::swap(blockSequence[left], blockSequence[right]); - } + // Flip the DFS traversal to get the reverse post-order traversal + // (this is the order in which blocks will be allocated) + for (unsigned left = 0, right = bbSeqCount - 1; left < right; left++, right--) + { + std::swap(postOrder[left], postOrder[right]); + } + + return postOrder; + }; + + // If we aren't optimizing, we won't have any cross-block live registers, + // so the order of blocks allocated shouldn't matter. + // Just use the linear order. + auto getTraversalSequence = [this]() -> BasicBlock** { + BasicBlock** const traversalOrder = new (compiler, CMK_LSRA) BasicBlock*[compiler->fgBBcount]; + bbSeqCount = compiler->fgBBcount; + + unsigned i = 0; + for (BasicBlock* const block : compiler->Blocks()) + { + traversalOrder[i++] = block; + } + + assert(i == bbSeqCount); + return traversalOrder; + }; + + assert((blockSequence == nullptr) && (bbSeqCount == 0)); + blockSequence = compiler->opts.OptimizationEnabled() ? getRpoSequence() : getTraversalSequence(); + bbNumMaxBeforeResolution = compiler->fgBBNumMax; + blockInfo = new (compiler, CMK_LSRA) LsraBlockInfo[bbNumMaxBeforeResolution + 1]; hasCriticalEdges = false; // We use a bbNum of 0 for entry RefPositions. @@ -1061,16 +1087,18 @@ void LinearScan::setBlockSequence() }; JITDUMP("Start LSRA Block Sequence: \n"); - for (unsigned i = 0; i < dfsTree->GetPostOrderCount(); i++) + for (unsigned i = 0; i < bbSeqCount; i++) { visitBlock(blockSequence[i]); } // If the DFS didn't visit any blocks, add them to the end of blockSequence - if (dfsTree->GetPostOrderCount() < compiler->fgBBcount) + if (bbSeqCount < compiler->fgBBcount) { + assert(compiler->opts.OptimizationEnabled()); + // Unvisited blocks are more likely to be at the back of the list, so iterate backwards - unsigned i = dfsTree->GetPostOrderCount(); + unsigned i = bbSeqCount; for (BasicBlock* block = compiler->fgLastBB; i < compiler->fgBBcount; block = block->Prev()) { assert(block != nullptr); From c727ff6189479d2f9614af136bea30716dd652be Mon Sep 17 00:00:00 2001 From: "Aman Khalid (from Dev Box)" Date: Fri, 27 Sep 2024 15:37:40 -0400 Subject: [PATCH 02/10] Use lexical order all the time --- src/coreclr/jit/lsra.cpp | 83 ++++++---------------------------------- 1 file changed, 11 insertions(+), 72 deletions(-) diff --git a/src/coreclr/jit/lsra.cpp b/src/coreclr/jit/lsra.cpp index 7bd26e74dfd913..549dab2b115c16 100644 --- a/src/coreclr/jit/lsra.cpp +++ b/src/coreclr/jit/lsra.cpp @@ -935,7 +935,7 @@ LinearScan::LinearScan(Compiler* theCompiler) // None // // Notes: -// On return, the blockSequence array contains the blocks in reverse post-order. +// On return, the blockSequence array contains the blocks in lexical order, post-layout optimization. // This method clears the bbVisitedSet on LinearScan, and when it returns the set // contains all the bbNums for the block. // @@ -951,42 +951,8 @@ void LinearScan::setBlockSequence() // Initialize the "visited" blocks set. bbVisitedSet = BlockSetOps::MakeEmpty(compiler); - // If optimizations are enabled, allocate blocks in reverse post-order. - // This ensures each block's predecessors are visited first. - auto getRpoSequence = [this]() -> BasicBlock** { - FlowGraphDfsTree* const dfsTree = compiler->fgComputeDfs(); - BasicBlock** const postOrder = dfsTree->GetPostOrder(); - bbSeqCount = dfsTree->GetPostOrderCount(); - - // Flip the DFS traversal to get the reverse post-order traversal - // (this is the order in which blocks will be allocated) - for (unsigned left = 0, right = bbSeqCount - 1; left < right; left++, right--) - { - std::swap(postOrder[left], postOrder[right]); - } - - return postOrder; - }; - - // If we aren't optimizing, we won't have any cross-block live registers, - // so the order of blocks allocated shouldn't matter. - // Just use the linear order. - auto getTraversalSequence = [this]() -> BasicBlock** { - BasicBlock** const traversalOrder = new (compiler, CMK_LSRA) BasicBlock*[compiler->fgBBcount]; - bbSeqCount = compiler->fgBBcount; - - unsigned i = 0; - for (BasicBlock* const block : compiler->Blocks()) - { - traversalOrder[i++] = block; - } - - assert(i == bbSeqCount); - return traversalOrder; - }; - assert((blockSequence == nullptr) && (bbSeqCount == 0)); - blockSequence = compiler->opts.OptimizationEnabled() ? getRpoSequence() : getTraversalSequence(); + blockSequence = new (compiler, CMK_LSRA) BasicBlock*[compiler->fgBBcount]; bbNumMaxBeforeResolution = compiler->fgBBNumMax; blockInfo = new (compiler, CMK_LSRA) LsraBlockInfo[bbNumMaxBeforeResolution + 1]; @@ -1001,9 +967,13 @@ void LinearScan::setBlockSequence() } #endif // TRACK_LSRA_STATS - auto visitBlock = [this](BasicBlock* block) { + JITDUMP("Start LSRA Block Sequence: \n"); + for (BasicBlock* const block : compiler->Blocks()) + { JITDUMP("Current block: " FMT_BB "\n", block->bbNum); markBlockVisited(block); + assert(bbSeqCount < compiler->fgBBcount); + blockSequence[bbSeqCount++] = block; // Initialize the blockInfo. // predBBNum will be set later. @@ -1063,10 +1033,7 @@ void LinearScan::setBlockSequence() } } - // Determine which block to schedule next. - - // First, update the NORMAL successors of the current block, adding them to the worklist - // according to the desired order. We will handle the EH successors below. + // Check for critical successor edges const unsigned numSuccs = block->NumSucc(compiler); bool checkForCriticalOutEdge = (numSuccs > 1); if (!checkForCriticalOutEdge && block->KindIs(BBJ_SWITCH)) @@ -1084,45 +1051,17 @@ void LinearScan::setBlockSequence() break; } } - }; - - JITDUMP("Start LSRA Block Sequence: \n"); - for (unsigned i = 0; i < bbSeqCount; i++) - { - visitBlock(blockSequence[i]); } - // If the DFS didn't visit any blocks, add them to the end of blockSequence - if (bbSeqCount < compiler->fgBBcount) - { - assert(compiler->opts.OptimizationEnabled()); - - // Unvisited blocks are more likely to be at the back of the list, so iterate backwards - unsigned i = bbSeqCount; - for (BasicBlock* block = compiler->fgLastBB; i < compiler->fgBBcount; block = block->Prev()) - { - assert(block != nullptr); - if (!isBlockVisited(block)) - { - visitBlock(block); - blockSequence[i++] = block; - } - } - } - - bbSeqCount = compiler->fgBBcount; + assert(bbSeqCount == compiler->fgBBcount); blockSequencingDone = true; #ifdef DEBUG - // Make sure that we've visited all the blocks. - for (BasicBlock* const block : compiler->Blocks()) - { - assert(isBlockVisited(block)); - } - JITDUMP("Final LSRA Block Sequence:\n"); for (BasicBlock* block = startBlockSequence(); block != nullptr; block = moveToNextBlock()) { + // Make sure we've visited every block. + assert(isBlockVisited(block)); JITDUMP(FMT_BB, block->bbNum); const LsraBlockInfo& bi = blockInfo[block->bbNum]; From 7a475a939b16580e09886114549aca6d85a0bb21 Mon Sep 17 00:00:00 2001 From: "Aman Khalid (from Dev Box)" Date: Fri, 27 Sep 2024 16:13:08 -0400 Subject: [PATCH 03/10] Remove redundant visit check --- src/coreclr/jit/lsra.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/coreclr/jit/lsra.cpp b/src/coreclr/jit/lsra.cpp index 549dab2b115c16..ce2312f1b65f46 100644 --- a/src/coreclr/jit/lsra.cpp +++ b/src/coreclr/jit/lsra.cpp @@ -1060,8 +1060,6 @@ void LinearScan::setBlockSequence() JITDUMP("Final LSRA Block Sequence:\n"); for (BasicBlock* block = startBlockSequence(); block != nullptr; block = moveToNextBlock()) { - // Make sure we've visited every block. - assert(isBlockVisited(block)); JITDUMP(FMT_BB, block->bbNum); const LsraBlockInfo& bi = blockInfo[block->bbNum]; From e69d0c77813060a12fc2cdc6722068c16800b614 Mon Sep 17 00:00:00 2001 From: "Aman Khalid (from Dev Box)" Date: Mon, 30 Sep 2024 17:11:31 -0400 Subject: [PATCH 04/10] Revert "Remove redundant visit check" This reverts commit 7a475a939b16580e09886114549aca6d85a0bb21. --- src/coreclr/jit/lsra.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/coreclr/jit/lsra.cpp b/src/coreclr/jit/lsra.cpp index ce2312f1b65f46..549dab2b115c16 100644 --- a/src/coreclr/jit/lsra.cpp +++ b/src/coreclr/jit/lsra.cpp @@ -1060,6 +1060,8 @@ void LinearScan::setBlockSequence() JITDUMP("Final LSRA Block Sequence:\n"); for (BasicBlock* block = startBlockSequence(); block != nullptr; block = moveToNextBlock()) { + // Make sure we've visited every block. + assert(isBlockVisited(block)); JITDUMP(FMT_BB, block->bbNum); const LsraBlockInfo& bi = blockInfo[block->bbNum]; From 32dcc3a3dfe90573e8c50aeafbf23c12ebab7012 Mon Sep 17 00:00:00 2001 From: "Aman Khalid (from Dev Box)" Date: Mon, 30 Sep 2024 17:11:53 -0400 Subject: [PATCH 05/10] Revert "Use lexical order all the time" This reverts commit c727ff6189479d2f9614af136bea30716dd652be. --- src/coreclr/jit/lsra.cpp | 83 ++++++++++++++++++++++++++++++++++------ 1 file changed, 72 insertions(+), 11 deletions(-) diff --git a/src/coreclr/jit/lsra.cpp b/src/coreclr/jit/lsra.cpp index 549dab2b115c16..7bd26e74dfd913 100644 --- a/src/coreclr/jit/lsra.cpp +++ b/src/coreclr/jit/lsra.cpp @@ -935,7 +935,7 @@ LinearScan::LinearScan(Compiler* theCompiler) // None // // Notes: -// On return, the blockSequence array contains the blocks in lexical order, post-layout optimization. +// On return, the blockSequence array contains the blocks in reverse post-order. // This method clears the bbVisitedSet on LinearScan, and when it returns the set // contains all the bbNums for the block. // @@ -951,8 +951,42 @@ void LinearScan::setBlockSequence() // Initialize the "visited" blocks set. bbVisitedSet = BlockSetOps::MakeEmpty(compiler); + // If optimizations are enabled, allocate blocks in reverse post-order. + // This ensures each block's predecessors are visited first. + auto getRpoSequence = [this]() -> BasicBlock** { + FlowGraphDfsTree* const dfsTree = compiler->fgComputeDfs(); + BasicBlock** const postOrder = dfsTree->GetPostOrder(); + bbSeqCount = dfsTree->GetPostOrderCount(); + + // Flip the DFS traversal to get the reverse post-order traversal + // (this is the order in which blocks will be allocated) + for (unsigned left = 0, right = bbSeqCount - 1; left < right; left++, right--) + { + std::swap(postOrder[left], postOrder[right]); + } + + return postOrder; + }; + + // If we aren't optimizing, we won't have any cross-block live registers, + // so the order of blocks allocated shouldn't matter. + // Just use the linear order. + auto getTraversalSequence = [this]() -> BasicBlock** { + BasicBlock** const traversalOrder = new (compiler, CMK_LSRA) BasicBlock*[compiler->fgBBcount]; + bbSeqCount = compiler->fgBBcount; + + unsigned i = 0; + for (BasicBlock* const block : compiler->Blocks()) + { + traversalOrder[i++] = block; + } + + assert(i == bbSeqCount); + return traversalOrder; + }; + assert((blockSequence == nullptr) && (bbSeqCount == 0)); - blockSequence = new (compiler, CMK_LSRA) BasicBlock*[compiler->fgBBcount]; + blockSequence = compiler->opts.OptimizationEnabled() ? getRpoSequence() : getTraversalSequence(); bbNumMaxBeforeResolution = compiler->fgBBNumMax; blockInfo = new (compiler, CMK_LSRA) LsraBlockInfo[bbNumMaxBeforeResolution + 1]; @@ -967,13 +1001,9 @@ void LinearScan::setBlockSequence() } #endif // TRACK_LSRA_STATS - JITDUMP("Start LSRA Block Sequence: \n"); - for (BasicBlock* const block : compiler->Blocks()) - { + auto visitBlock = [this](BasicBlock* block) { JITDUMP("Current block: " FMT_BB "\n", block->bbNum); markBlockVisited(block); - assert(bbSeqCount < compiler->fgBBcount); - blockSequence[bbSeqCount++] = block; // Initialize the blockInfo. // predBBNum will be set later. @@ -1033,7 +1063,10 @@ void LinearScan::setBlockSequence() } } - // Check for critical successor edges + // Determine which block to schedule next. + + // First, update the NORMAL successors of the current block, adding them to the worklist + // according to the desired order. We will handle the EH successors below. const unsigned numSuccs = block->NumSucc(compiler); bool checkForCriticalOutEdge = (numSuccs > 1); if (!checkForCriticalOutEdge && block->KindIs(BBJ_SWITCH)) @@ -1051,17 +1084,45 @@ void LinearScan::setBlockSequence() break; } } + }; + + JITDUMP("Start LSRA Block Sequence: \n"); + for (unsigned i = 0; i < bbSeqCount; i++) + { + visitBlock(blockSequence[i]); } - assert(bbSeqCount == compiler->fgBBcount); + // If the DFS didn't visit any blocks, add them to the end of blockSequence + if (bbSeqCount < compiler->fgBBcount) + { + assert(compiler->opts.OptimizationEnabled()); + + // Unvisited blocks are more likely to be at the back of the list, so iterate backwards + unsigned i = bbSeqCount; + for (BasicBlock* block = compiler->fgLastBB; i < compiler->fgBBcount; block = block->Prev()) + { + assert(block != nullptr); + if (!isBlockVisited(block)) + { + visitBlock(block); + blockSequence[i++] = block; + } + } + } + + bbSeqCount = compiler->fgBBcount; blockSequencingDone = true; #ifdef DEBUG + // Make sure that we've visited all the blocks. + for (BasicBlock* const block : compiler->Blocks()) + { + assert(isBlockVisited(block)); + } + JITDUMP("Final LSRA Block Sequence:\n"); for (BasicBlock* block = startBlockSequence(); block != nullptr; block = moveToNextBlock()) { - // Make sure we've visited every block. - assert(isBlockVisited(block)); JITDUMP(FMT_BB, block->bbNum); const LsraBlockInfo& bi = blockInfo[block->bbNum]; From a6a022b01965637070b4ae636615c22e4ed4fd79 Mon Sep 17 00:00:00 2001 From: "Aman Khalid (from Dev Box)" Date: Tue, 3 Dec 2024 16:57:59 -0500 Subject: [PATCH 06/10] Fix merge --- src/coreclr/jit/lsra.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/coreclr/jit/lsra.cpp b/src/coreclr/jit/lsra.cpp index 8180451415c43e..35b0949202f8eb 100644 --- a/src/coreclr/jit/lsra.cpp +++ b/src/coreclr/jit/lsra.cpp @@ -13910,16 +13910,6 @@ SingleTypeRegSet LinearScan::RegisterSelection::selectMinimal( } } - assert(found && isSingleRegister(candidates)); - return candidates; -} - refPosition->bbNum)); -#endif // TRACK_LSRA_STATS - *registerScore = RegisterScore::REG_NUM; -#endif // DEBUG - } - } - assert(found && isSingleRegister(candidates)); return candidates; } From ada96c007f969117ad72ec96afa162d14d531b7e Mon Sep 17 00:00:00 2001 From: "Aman Khalid (from Dev Box)" Date: Tue, 3 Dec 2024 17:42:38 -0500 Subject: [PATCH 07/10] Fix lambda capture --- src/coreclr/jit/lsra.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/lsra.cpp b/src/coreclr/jit/lsra.cpp index 35b0949202f8eb..c44be77f91d256 100644 --- a/src/coreclr/jit/lsra.cpp +++ b/src/coreclr/jit/lsra.cpp @@ -960,7 +960,7 @@ void LinearScan::setBlockSequence() FlowGraphNaturalLoops* const loops = FlowGraphNaturalLoops::Find(dfsTree); unsigned index = 0; - auto addToSequence = [this, sequence, &index](BasicBlock* block) { + auto addToSequence = [sequence, &index](BasicBlock* block) { sequence[index++] = block; }; From 7d54a9bb17b4647e9b102bc53d60d2dad8322df9 Mon Sep 17 00:00:00 2001 From: "Aman Khalid (from Dev Box)" Date: Mon, 17 Feb 2025 21:26:12 -0500 Subject: [PATCH 08/10] Skip FP kills and finding live-in blocks in MinOpts --- src/coreclr/jit/lsrabuild.cpp | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/coreclr/jit/lsrabuild.cpp b/src/coreclr/jit/lsrabuild.cpp index a3a0ad88b80f4a..cbff1f9dd403a1 100644 --- a/src/coreclr/jit/lsrabuild.cpp +++ b/src/coreclr/jit/lsrabuild.cpp @@ -840,8 +840,8 @@ regMaskTP LinearScan::getKillSetForCall(GenTreeCall* call) killMask = compiler->compHelperCallKillSet(helpFunc); } - // if there is no FP used, we can ignore the FP kills - if (!compiler->compFloatingPointUsed) + // if there is no FP used, or if we don't have cross-block live registers, we can ignore the FP kills + if (!enregisterLocalVars || !compiler->compFloatingPointUsed) { #if defined(TARGET_XARCH) @@ -2321,7 +2321,6 @@ void LinearScan::buildIntervals() numPlacedArgLocals = 0; placedArgRegs = RBM_NONE; - BasicBlock* predBlock = nullptr; BasicBlock* prevBlock = nullptr; // Initialize currentLiveVars to the empty set. We will set it to the current @@ -2334,19 +2333,18 @@ void LinearScan::buildIntervals() JITDUMP("\nNEW BLOCK " FMT_BB "\n", block->bbNum); compiler->compCurBB = block; - bool predBlockIsAllocated = false; - predBlock = findPredBlockForLiveIn(block, prevBlock DEBUGARG(&predBlockIsAllocated)); - if (predBlock != nullptr) - { - JITDUMP("\n\nSetting " FMT_BB " as the predecessor for determining incoming variable registers of " FMT_BB - "\n", - predBlock->bbNum, block->bbNum); - assert(predBlock->bbNum <= bbNumMaxBeforeResolution); - blockInfo[block->bbNum].predBBNum = predBlock->bbNum; - } - if (localVarsEnregistered) { + bool predBlockIsAllocated = false; + BasicBlock* const predBlock = findPredBlockForLiveIn(block, prevBlock DEBUGARG(&predBlockIsAllocated)); + if (predBlock != nullptr) + { + JITDUMP("\n\nSetting " FMT_BB + " as the predecessor for determining incoming variable registers of " FMT_BB "\n", + predBlock->bbNum, block->bbNum); + assert(predBlock->bbNum <= bbNumMaxBeforeResolution); + blockInfo[block->bbNum].predBBNum = predBlock->bbNum; + } VarSetOps::AssignNoCopy(compiler, currentLiveVars, VarSetOps::Intersection(compiler, registerCandidateVars, block->bbLiveIn)); From fc2e35175cd2f874befccb4c1fde831b967a0af9 Mon Sep 17 00:00:00 2001 From: "Aman Khalid (from Dev Box)" Date: Tue, 18 Feb 2025 00:16:36 -0500 Subject: [PATCH 09/10] Revert --- src/coreclr/jit/lsrabuild.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/lsrabuild.cpp b/src/coreclr/jit/lsrabuild.cpp index cbff1f9dd403a1..1d3598210f3444 100644 --- a/src/coreclr/jit/lsrabuild.cpp +++ b/src/coreclr/jit/lsrabuild.cpp @@ -840,8 +840,8 @@ regMaskTP LinearScan::getKillSetForCall(GenTreeCall* call) killMask = compiler->compHelperCallKillSet(helpFunc); } - // if there is no FP used, or if we don't have cross-block live registers, we can ignore the FP kills - if (!enregisterLocalVars || !compiler->compFloatingPointUsed) + // if there is no FP used, we can ignore the FP kills + if (!compiler->compFloatingPointUsed) { #if defined(TARGET_XARCH) From 8dc1df2aa131b8c8b5da4f55a8ecb0a88617ff3d Mon Sep 17 00:00:00 2001 From: "Aman Khalid (from Dev Box)" Date: Tue, 18 Feb 2025 00:44:58 -0500 Subject: [PATCH 10/10] Try resetting compFloatingPointUsed between blocks --- src/coreclr/jit/lsrabuild.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/coreclr/jit/lsrabuild.cpp b/src/coreclr/jit/lsrabuild.cpp index 1d3598210f3444..7380e6c2ef0b5b 100644 --- a/src/coreclr/jit/lsrabuild.cpp +++ b/src/coreclr/jit/lsrabuild.cpp @@ -2327,6 +2327,7 @@ void LinearScan::buildIntervals() // live-in at the entry to each block (this will include the incoming args on // the first block). VarSetOps::AssignNoCopy(compiler, currentLiveVars, VarSetOps::MakeEmpty(compiler)); + const bool floatingPointUsed = compiler->compFloatingPointUsed; for (block = startBlockSequence(); block != nullptr; block = moveToNextBlock()) { @@ -2404,6 +2405,11 @@ void LinearScan::buildIntervals() } } } + else + { + // If state isn't live across blocks, then reset any global Compiler state. + compiler->compFloatingPointUsed = floatingPointUsed; + } // Add a dummy RefPosition to mark the block boundary. // Note that we do this AFTER adding the exposed uses above, because the