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 }