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 }