package ssa

import (
	
	

	
)

// passLayoutBlocks implements Builder.LayoutBlocks. This re-organizes builder.reversePostOrderedBasicBlocks.
//
// TODO: there are tons of room for improvement here. e.g. LLVM has BlockPlacementPass using BlockFrequencyInfo,
// BranchProbabilityInfo, and LoopInfo to do a much better job. Also, if we have the profiling instrumentation
// like ball-larus algorithm, then we could do profile-guided optimization. Basically all of them are trying
// to maximize the fall-through opportunities which is most efficient.
//
// Here, fallthrough happens when a block ends with jump instruction whose target is the right next block in the
// builder.reversePostOrderedBasicBlocks.
//
// Currently, we just place blocks using the DFS reverse post-order of the dominator tree with the heuristics:
//  1. a split edge trampoline towards a loop header will be placed as a fallthrough.
//  2. we invert the brz and brnz if it makes the fallthrough more likely.
//
// This heuristic is done in maybeInvertBranches function.
func passLayoutBlocks( *builder) {
	// We might end up splitting critical edges which adds more basic blocks,
	// so we store the currently existing basic blocks in nonSplitBlocks temporarily.
	// That way we can iterate over the original basic blocks while appending new ones into reversePostOrderedBasicBlocks.
	 := .blkStack[:0]
	for ,  := range .reversePostOrderedBasicBlocks {
		if !.Valid() {
			continue
		}
		 = append(, )
		if  != len(.reversePostOrderedBasicBlocks)-1 {
			_ = maybeInvertBranches(, , .reversePostOrderedBasicBlocks[+1])
		}
	}

	var  []*basicBlock

	// Reset the order slice since we update on the fly by splitting critical edges.
	.reversePostOrderedBasicBlocks = .reversePostOrderedBasicBlocks[:0]
	 := .blkStack2[:0]
	for ,  := range  {
		for  := range .preds {
			 := .preds[].blk
			if .visited == 1 || !.Valid() {
				continue
			} else if .reversePostOrder < .reversePostOrder {
				// This means the edge is critical, and this pred is the trampoline and yet to be inserted.
				// Split edge trampolines must come before the destination in reverse post-order.
				.reversePostOrderedBasicBlocks = append(.reversePostOrderedBasicBlocks, )
				.visited = 1 // mark as inserted.
			}
		}

		// Now that we've already added all the potential trampoline blocks incoming to this block,
		// we can add this block itself.
		.reversePostOrderedBasicBlocks = append(.reversePostOrderedBasicBlocks, )
		.visited = 1 // mark as inserted.

		if len(.success) < 2 {
			// There won't be critical edge originating from this block.
			continue
		} else if .currentInstr.opcode == OpcodeBrTable {
			// We don't split critical edges here, because at the construction site of BrTable, we already split the edges.
			continue
		}

		for ,  := range .success {
			if !.ReturnBlock() && // If the successor is a return block, we need to split the edge any way because we need "epilogue" to be inserted.
				// Plus if there's no multiple incoming edges to this successor, (pred, succ) is not critical.
				len(.preds) < 2 {
				continue
			}

			// Otherwise, we are sure this is a critical edge. To modify the CFG, we need to find the predecessor info
			// from the successor.
			var  *basicBlockPredecessorInfo
			for  := range .preds { // This linear search should not be a problem since the number of predecessors should almost always small.
				 := &.preds[]
				if .blk ==  {
					 = 
					break
				}
			}

			if  == nil {
				// This must be a bug in somewhere around branch manipulation.
				panic("BUG: predecessor info not found while the successor exists in successors list")
			}

			if wazevoapi.SSALoggingEnabled {
				fmt.Printf("trying to split edge from %d->%d at %s\n",
					.ID(), .ID(), .branch.Format())
			}

			 := .splitCriticalEdge(, , )
			// Update the successors slice because the target is no longer the original `succ`.
			.success[] = 

			if wazevoapi.SSAValidationEnabled {
				 = append(, )
			}

			if wazevoapi.SSALoggingEnabled {
				fmt.Printf("edge split from %d->%d at %s as %d->%d->%d \n",
					.ID(), .ID(), .branch.Format(),
					.ID(), .ID(), .ID())
			}

			 := .currentInstr
			if .opcode == OpcodeJump && BasicBlockID(.rValue) == .id {
				// This can be lowered as fallthrough at the end of the block.
				.reversePostOrderedBasicBlocks = append(.reversePostOrderedBasicBlocks, )
				.visited = 1 // mark as inserted.
			} else {
				 = append(, )
			}
		}

		for ,  := range  {
			if .success[0].reversePostOrder <= .reversePostOrder { // "<=", not "<" because the target might be itself.
				// This means the critical edge was backward, so we insert after the current block immediately.
				.reversePostOrderedBasicBlocks = append(.reversePostOrderedBasicBlocks, )
				.visited = 1 // mark as inserted.
			} // If the target is forward, we can wait to insert until the target is inserted.
		}
		 = [:0] // Reuse the stack for the next block.
	}

	if wazevoapi.SSALoggingEnabled {
		var  []string
		for ,  := range .reversePostOrderedBasicBlocks {
			 = append(, .Name())
		}
		fmt.Println("ordered blocks: ", strings.Join(, ", "))
	}

	if wazevoapi.SSAValidationEnabled {
		for ,  := range  {
			if .visited != 1 {
				panic("BUG: trampoline block not inserted: " + .formatHeader())
			}
			.validate()
		}
	}

	// Reuse the stack for the next iteration.
	.blkStack2 = [:0]
}

// markFallthroughJumps finds the fallthrough jumps and marks them as such.
func ( *builder) () {
	 := len(.reversePostOrderedBasicBlocks) - 1
	for ,  := range .reversePostOrderedBasicBlocks {
		if  <  {
			 := .currentInstr
			if .opcode == OpcodeJump && BasicBlockID(.rValue) == .reversePostOrderedBasicBlocks[+1].id {
				.AsFallthroughJump()
			}
		}
	}
}

// maybeInvertBranches inverts the branch instructions if it is likely possible to the fallthrough more likely with simple heuristics.
// nextInRPO is the next block in the reverse post-order.
//
// Returns true if the branch is inverted for testing purpose.
func maybeInvertBranches( *builder,  *basicBlock,  *basicBlock) bool {
	 := .currentInstr
	if .opcode == OpcodeBrTable {
		return false
	}

	 := .prev
	if  == nil || (.opcode != OpcodeBrnz && .opcode != OpcodeBrz) {
		return false
	}

	if len(.vs.View()) != 0 || len(.vs.View()) != 0 {
		// If either one of them has arguments, we don't invert the branches.
		return false
	}

	// So this block has two branches (a conditional branch followed by an unconditional branch) at the end.
	// We can invert the condition of the branch if it makes the fallthrough more likely.

	 := .basicBlock(BasicBlockID(.rValue))
	 := .basicBlock(BasicBlockID(.rValue))

	if .loopHeader {
		// First, if the tail's target is loopHeader, we don't need to do anything here,
		// because the edge is likely to be critical edge for complex loops (e.g. loop with branches inside it).
		// That means, we will split the edge in the end of LayoutBlocks function, and insert the trampoline block
		// right after this block, which will be fallthrough in any way.
		return false
	} else if .loopHeader {
		// On the other hand, if the condBranch's target is loopHeader, we invert the condition of the branch
		// so that we could get the fallthrough to the trampoline block.
		goto 
	}

	if  ==  {
		// Also, if the tail's target is the next block in the reverse post-order, we don't need to do anything here,
		// because if this is not critical edge, we would end up placing these two blocks adjacent to each other.
		// Even if it is the critical edge, we place the trampoline block right after this block, which will be fallthrough in any way.
		return false
	} else if  ==  {
		// If the condBranch's target is the next block in the reverse post-order, we invert the condition of the branch
		// so that we could get the fallthrough to the block.
		goto 
	} else {
		return false
	}

:
	for  := range .preds {
		 := &.preds[]
		if .branch ==  {
			.branch = 
			break
		}
	}
	for  := range .preds {
		 := &.preds[]
		if .branch ==  {
			.branch = 
			break
		}
	}

	.InvertBrx()
	.rValue = Value(.ID())
	.rValue = Value(.ID())
	if wazevoapi.SSALoggingEnabled {
		fmt.Printf("inverting branches at %d->%d and %d->%d\n",
			.ID(), .ID(), .ID(), .ID())
	}

	return true
}

// splitCriticalEdge splits the critical edge between the given predecessor (`pred`) and successor (owning `predInfo`).
//
// - `pred` is the source of the critical edge,
// - `succ` is the destination of the critical edge,
// - `predInfo` is the predecessor info in the succ.preds slice which represents the critical edge.
//
// Why splitting critical edges is important? See following links:
//
//   - https://en.wikipedia.org/wiki/Control-flow_graph
//   - https://nickdesaulniers.github.io/blog/2023/01/27/critical-edge-splitting/
//
// The returned basic block is the trampoline block which is inserted to split the critical edge.
func ( *builder) (,  *basicBlock,  *basicBlockPredecessorInfo) *basicBlock {
	// In the following, we convert the following CFG:
	//
	//     pred --(originalBranch)--> succ
	//
	// to the following CFG:
	//
	//     pred --(newBranch)--> trampoline --(originalBranch)-> succ
	//
	// where trampoline is a new basic block which is created to split the critical edge.

	 := .allocateBasicBlock()
	if int(.id) >= len(.dominators) {
		.dominators = append(.dominators, make([]*basicBlock, .id+1)...)
	}
	.dominators[.id] = 

	 := .branch

	// Replace originalBranch with the newBranch.
	 := .AllocateInstruction()
	.opcode = .opcode
	.rValue = Value(.ID())
	switch .opcode {
	case OpcodeJump:
	case OpcodeBrz, OpcodeBrnz:
		.opcode = OpcodeJump // Trampoline consists of one unconditional branch.
		.v = .v
		.v = ValueInvalid
	default:
		panic("BUG: critical edge shouldn't be originated from br_table")
	}
	swapInstruction(, , )

	// Replace the original branch with the new branch.
	.rootInstr = 
	.currentInstr = 
	.success = append(.success, ) // Do not use []*basicBlock{pred} because we might have already allocated the slice.
	.preds = append(.preds,           // same as ^.
		basicBlockPredecessorInfo{blk: , branch: })
	.Seal()

	// Update the original branch to point to the trampoline.
	.blk = 
	.branch = 

	if wazevoapi.SSAValidationEnabled {
		.validate()
	}

	if len(.params.View()) > 0 {
		panic("trampoline should not have params")
	}

	// Assign the same order as the original block so that this will be placed before the actual destination.
	.reversePostOrder = .reversePostOrder
	return 
}

// swapInstruction replaces `old` in the block `blk` with `New`.
func swapInstruction( *basicBlock, ,  *Instruction) {
	if .rootInstr ==  {
		.rootInstr = 
		 := .next
		.next = 
		.prev = 
	} else {
		if .currentInstr ==  {
			.currentInstr = 
		}
		 := .prev
		.next, .prev = , 
		if  := .next;  != nil {
			.next, .prev = , 
		}
	}
	.prev, .next = nil, nil
}