github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/engine/wazevo/backend/executable_context.go (about)

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