github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/engine/wazevo/backend/executable_context.go (about)

     1  package backend
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  
     7  	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
     8  	"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
     9  )
    10  
    11  type ExecutableContext interface {
    12  	// StartLoweringFunction is called when the lowering of the given function is started.
    13  	// maximumBlockID is the maximum value of ssa.BasicBlockID existing in the function.
    14  	StartLoweringFunction(maximumBlockID ssa.BasicBlockID)
    15  
    16  	// LinkAdjacentBlocks is called after finished lowering all blocks in order to create one single instruction list.
    17  	LinkAdjacentBlocks(prev, next ssa.BasicBlock)
    18  
    19  	// StartBlock is called when the compilation of the given block is started.
    20  	// The order of this being called is the reverse post order of the ssa.BasicBlock(s) as we iterate with
    21  	// ssa.Builder BlockIteratorReversePostOrderBegin and BlockIteratorReversePostOrderEnd.
    22  	StartBlock(ssa.BasicBlock)
    23  
    24  	// EndBlock is called when the compilation of the current block is finished.
    25  	EndBlock()
    26  
    27  	// FlushPendingInstructions flushes the pending instructions to the buffer.
    28  	// This will be called after the lowering of each SSA Instruction.
    29  	FlushPendingInstructions()
    30  }
    31  
    32  type ExecutableContextT[Instr any] struct {
    33  	CurrentSSABlk ssa.BasicBlock
    34  
    35  	// InstrPool is the InstructionPool of instructions.
    36  	InstructionPool wazevoapi.Pool[Instr]
    37  	asNop           func(*Instr)
    38  	setNext         func(*Instr, *Instr)
    39  	setPrev         func(*Instr, *Instr)
    40  
    41  	// RootInstr is the root instruction of the executable.
    42  	RootInstr         *Instr
    43  	labelPositionPool wazevoapi.Pool[LabelPosition[Instr]]
    44  	NextLabel         Label
    45  	// LabelPositions maps a label to the instructions of the region which the label represents.
    46  	LabelPositions     map[Label]*LabelPosition[Instr]
    47  	OrderedBlockLabels []*LabelPosition[Instr]
    48  
    49  	// PerBlockHead and PerBlockEnd are the head and tail of the instruction list per currently-compiled ssa.BasicBlock.
    50  	PerBlockHead, PerBlockEnd *Instr
    51  	// PendingInstructions are the instructions which are not yet emitted into the instruction list.
    52  	PendingInstructions []*Instr
    53  
    54  	// SsaBlockIDToLabels maps an SSA block ID to the label.
    55  	SsaBlockIDToLabels []Label
    56  }
    57  
    58  func NewExecutableContextT[Instr any](
    59  	resetInstruction func(*Instr),
    60  	setNext func(*Instr, *Instr),
    61  	setPrev func(*Instr, *Instr),
    62  	asNop func(*Instr),
    63  ) *ExecutableContextT[Instr] {
    64  	return &ExecutableContextT[Instr]{
    65  		InstructionPool:   wazevoapi.NewPool[Instr](resetInstruction),
    66  		asNop:             asNop,
    67  		setNext:           setNext,
    68  		setPrev:           setPrev,
    69  		labelPositionPool: wazevoapi.NewPool[LabelPosition[Instr]](resetLabelPosition[Instr]),
    70  		LabelPositions:    make(map[Label]*LabelPosition[Instr]),
    71  		NextLabel:         LabelInvalid,
    72  	}
    73  }
    74  
    75  func resetLabelPosition[T any](l *LabelPosition[T]) {
    76  	*l = LabelPosition[T]{}
    77  }
    78  
    79  // StartLoweringFunction implements ExecutableContext.
    80  func (e *ExecutableContextT[Instr]) StartLoweringFunction(max ssa.BasicBlockID) {
    81  	imax := int(max)
    82  	if len(e.SsaBlockIDToLabels) <= imax {
    83  		// Eagerly allocate labels for the blocks since the underlying slice will be used for the next iteration.
    84  		e.SsaBlockIDToLabels = append(e.SsaBlockIDToLabels, make([]Label, imax+1)...)
    85  	}
    86  }
    87  
    88  func (e *ExecutableContextT[Instr]) StartBlock(blk ssa.BasicBlock) {
    89  	e.CurrentSSABlk = blk
    90  
    91  	l := e.SsaBlockIDToLabels[e.CurrentSSABlk.ID()]
    92  	if l == LabelInvalid {
    93  		l = e.AllocateLabel()
    94  		e.SsaBlockIDToLabels[blk.ID()] = l
    95  	}
    96  
    97  	end := e.allocateNop0()
    98  	e.PerBlockHead, e.PerBlockEnd = end, end
    99  
   100  	labelPos, ok := e.LabelPositions[l]
   101  	if !ok {
   102  		labelPos = e.AllocateLabelPosition(l)
   103  		e.LabelPositions[l] = labelPos
   104  	}
   105  	e.OrderedBlockLabels = append(e.OrderedBlockLabels, labelPos)
   106  	labelPos.Begin, labelPos.End = end, end
   107  	labelPos.SB = blk
   108  }
   109  
   110  // EndBlock implements ExecutableContext.
   111  func (e *ExecutableContextT[T]) EndBlock() {
   112  	// Insert nop0 as the head of the block for convenience to simplify the logic of inserting instructions.
   113  	e.insertAtPerBlockHead(e.allocateNop0())
   114  
   115  	l := e.SsaBlockIDToLabels[e.CurrentSSABlk.ID()]
   116  	e.LabelPositions[l].Begin = e.PerBlockHead
   117  
   118  	if e.CurrentSSABlk.EntryBlock() {
   119  		e.RootInstr = e.PerBlockHead
   120  	}
   121  }
   122  
   123  func (e *ExecutableContextT[T]) insertAtPerBlockHead(i *T) {
   124  	if e.PerBlockHead == nil {
   125  		e.PerBlockHead = i
   126  		e.PerBlockEnd = i
   127  		return
   128  	}
   129  	e.setNext(i, e.PerBlockHead)
   130  	e.setPrev(e.PerBlockHead, i)
   131  	e.PerBlockHead = i
   132  }
   133  
   134  // FlushPendingInstructions implements ExecutableContext.
   135  func (e *ExecutableContextT[T]) FlushPendingInstructions() {
   136  	l := len(e.PendingInstructions)
   137  	if l == 0 {
   138  		return
   139  	}
   140  	for i := l - 1; i >= 0; i-- { // reverse because we lower instructions in reverse order.
   141  		e.insertAtPerBlockHead(e.PendingInstructions[i])
   142  	}
   143  	e.PendingInstructions = e.PendingInstructions[:0]
   144  }
   145  
   146  func (e *ExecutableContextT[T]) Reset() {
   147  	e.labelPositionPool.Reset()
   148  	e.InstructionPool.Reset()
   149  	for l := Label(0); l <= e.NextLabel; l++ {
   150  		delete(e.LabelPositions, l)
   151  	}
   152  	e.PendingInstructions = e.PendingInstructions[:0]
   153  	e.OrderedBlockLabels = e.OrderedBlockLabels[:0]
   154  	e.RootInstr = nil
   155  	e.SsaBlockIDToLabels = e.SsaBlockIDToLabels[:0]
   156  	e.PerBlockHead, e.PerBlockEnd = nil, nil
   157  	e.NextLabel = LabelInvalid
   158  }
   159  
   160  // AllocateLabel allocates an unused label.
   161  func (e *ExecutableContextT[T]) AllocateLabel() Label {
   162  	e.NextLabel++
   163  	return e.NextLabel
   164  }
   165  
   166  func (e *ExecutableContextT[T]) AllocateLabelPosition(la Label) *LabelPosition[T] {
   167  	l := e.labelPositionPool.Allocate()
   168  	l.L = la
   169  	return l
   170  }
   171  
   172  func (e *ExecutableContextT[T]) GetOrAllocateSSABlockLabel(blk ssa.BasicBlock) Label {
   173  	if blk.ReturnBlock() {
   174  		return LabelReturn
   175  	}
   176  	l := e.SsaBlockIDToLabels[blk.ID()]
   177  	if l == LabelInvalid {
   178  		l = e.AllocateLabel()
   179  		e.SsaBlockIDToLabels[blk.ID()] = l
   180  	}
   181  	return l
   182  }
   183  
   184  func (e *ExecutableContextT[T]) allocateNop0() *T {
   185  	i := e.InstructionPool.Allocate()
   186  	e.asNop(i)
   187  	return i
   188  }
   189  
   190  // LinkAdjacentBlocks implements backend.Machine.
   191  func (e *ExecutableContextT[T]) LinkAdjacentBlocks(prev, next ssa.BasicBlock) {
   192  	prevLabelPos := e.LabelPositions[e.GetOrAllocateSSABlockLabel(prev)]
   193  	nextLabelPos := e.LabelPositions[e.GetOrAllocateSSABlockLabel(next)]
   194  	e.setNext(prevLabelPos.End, nextLabelPos.Begin)
   195  }
   196  
   197  // LabelPosition represents the regions of the generated code which the label represents.
   198  type LabelPosition[Instr any] struct {
   199  	SB           ssa.BasicBlock
   200  	L            Label
   201  	Begin, End   *Instr
   202  	BinaryOffset int64
   203  }
   204  
   205  // Label represents a position in the generated code which is either
   206  // a real instruction or the constant InstructionPool (e.g. jump tables).
   207  //
   208  // This is exactly the same as the traditional "label" in assembly code.
   209  type Label uint32
   210  
   211  const (
   212  	LabelInvalid Label = 0
   213  	LabelReturn  Label = math.MaxUint32
   214  )
   215  
   216  // String implements backend.Machine.
   217  func (l Label) String() string {
   218  	return fmt.Sprintf("L%d", l)
   219  }