wa-lang.org/wazero@v1.0.2/internal/engine/compiler/compiler_value_location.go (about) 1 package compiler 2 3 import ( 4 "fmt" 5 "strings" 6 7 "wa-lang.org/wazero/internal/asm" 8 "wa-lang.org/wazero/internal/wasm" 9 ) 10 11 var ( 12 // unreservedGeneralPurposeRegisters contains unreserved general purpose registers of integer type. 13 unreservedGeneralPurposeRegisters []asm.Register 14 15 // unreservedVectorRegisters contains unreserved vector registers. 16 unreservedVectorRegisters []asm.Register 17 ) 18 19 func isGeneralPurposeRegister(r asm.Register) bool { 20 return unreservedGeneralPurposeRegisters[0] <= r && r <= unreservedGeneralPurposeRegisters[len(unreservedGeneralPurposeRegisters)-1] 21 } 22 23 func isVectorRegister(r asm.Register) bool { 24 return unreservedVectorRegisters[0] <= r && r <= unreservedVectorRegisters[len(unreservedVectorRegisters)-1] 25 } 26 27 // runtimeValueLocation corresponds to each variable pushed onto the wazeroir (virtual) stack, 28 // and it has the information about where it exists in the physical machine. 29 // It might exist in registers, or maybe on in the non-virtual physical stack allocated in memory. 30 type runtimeValueLocation struct { 31 valueType runtimeValueType 32 // register is set to asm.NilRegister if the value is stored in the memory stack. 33 register asm.Register 34 // conditionalRegister is set to conditionalRegisterStateUnset if the value is not on the conditional register. 35 conditionalRegister asm.ConditionalRegisterState 36 // stackPointer is the location of this value in the memory stack at runtime, 37 stackPointer uint64 38 } 39 40 func (v *runtimeValueLocation) getRegisterType() (ret registerType) { 41 switch v.valueType { 42 case runtimeValueTypeI32, runtimeValueTypeI64: 43 ret = registerTypeGeneralPurpose 44 case runtimeValueTypeF32, runtimeValueTypeF64, 45 runtimeValueTypeV128Lo, runtimeValueTypeV128Hi: 46 ret = registerTypeVector 47 } 48 return 49 } 50 51 type runtimeValueType byte 52 53 const ( 54 runtimeValueTypeI32 runtimeValueType = iota 55 runtimeValueTypeI64 56 runtimeValueTypeF32 57 runtimeValueTypeF64 58 runtimeValueTypeV128Lo 59 runtimeValueTypeV128Hi 60 ) 61 62 func (r runtimeValueType) String() (ret string) { 63 switch r { 64 case runtimeValueTypeI32: 65 ret = "i32" 66 case runtimeValueTypeI64: 67 ret = "i64" 68 case runtimeValueTypeF32: 69 ret = "f32" 70 case runtimeValueTypeF64: 71 ret = "f64" 72 case runtimeValueTypeV128Lo: 73 ret = "v128.lo" 74 case runtimeValueTypeV128Hi: 75 ret = "v128.hi" 76 } 77 return 78 } 79 80 func (v *runtimeValueLocation) setRegister(reg asm.Register) { 81 v.register = reg 82 v.conditionalRegister = asm.ConditionalRegisterStateUnset 83 } 84 85 func (v *runtimeValueLocation) onRegister() bool { 86 return v.register != asm.NilRegister && v.conditionalRegister == asm.ConditionalRegisterStateUnset 87 } 88 89 func (v *runtimeValueLocation) onStack() bool { 90 return v.register == asm.NilRegister && v.conditionalRegister == asm.ConditionalRegisterStateUnset 91 } 92 93 func (v *runtimeValueLocation) onConditionalRegister() bool { 94 return v.conditionalRegister != asm.ConditionalRegisterStateUnset 95 } 96 97 func (v *runtimeValueLocation) String() string { 98 var location string 99 if v.onStack() { 100 location = fmt.Sprintf("stack(%d)", v.stackPointer) 101 } else if v.onConditionalRegister() { 102 location = fmt.Sprintf("conditional(%d)", v.conditionalRegister) 103 } else if v.onRegister() { 104 location = fmt.Sprintf("register(%s)", registerNameFn(v.register)) 105 } 106 return fmt.Sprintf("{type=%s,location=%s}", v.valueType, location) 107 } 108 109 func newRuntimeValueLocationStack() *runtimeValueLocationStack { 110 return &runtimeValueLocationStack{ 111 usedRegisters: map[asm.Register]struct{}{}, 112 unreservedVectorRegisters: unreservedVectorRegisters, 113 unreservedGeneralPurposeRegisters: unreservedGeneralPurposeRegisters, 114 } 115 } 116 117 // runtimeValueLocationStack represents the wazeroir virtual stack 118 // where each item holds the location information about where it exists 119 // on the physical machine at runtime. 120 // 121 // Notably this is only used in the compilation phase, not runtime, 122 // and we change the state of this struct at every wazeroir operation we compile. 123 // In this way, we can see where the operands of an operation (for example, 124 // two variables for wazeroir add operation.) exist and check the necessity for 125 // moving the variable to registers to perform actual CPU instruction 126 // to achieve wazeroir's add operation. 127 type runtimeValueLocationStack struct { 128 // stack holds all the variables. 129 stack []*runtimeValueLocation 130 // sp is the current stack pointer. 131 sp uint64 132 // usedRegisters stores the used registers. 133 usedRegisters map[asm.Register]struct{} 134 // stackPointerCeil tracks max(.sp) across the lifespan of this struct. 135 stackPointerCeil uint64 136 // unreservedGeneralPurposeRegisters and unreservedVectorRegisters hold 137 // architecture dependent unreserved register list. 138 unreservedGeneralPurposeRegisters, unreservedVectorRegisters []asm.Register 139 } 140 141 func (v *runtimeValueLocationStack) String() string { 142 var stackStr []string 143 for i := uint64(0); i < v.sp; i++ { 144 stackStr = append(stackStr, v.stack[i].String()) 145 } 146 var usedRegisters []string 147 for reg := range v.usedRegisters { 148 usedRegisters = append(usedRegisters, registerNameFn(reg)) 149 } 150 return fmt.Sprintf("sp=%d, stack=[%s], used_registers=[%s]", v.sp, strings.Join(stackStr, ","), strings.Join(usedRegisters, ",")) 151 } 152 153 func (v *runtimeValueLocationStack) clone() *runtimeValueLocationStack { 154 ret := &runtimeValueLocationStack{} 155 ret.sp = v.sp 156 ret.usedRegisters = make(map[asm.Register]struct{}, len(ret.usedRegisters)) 157 for r := range v.usedRegisters { 158 ret.markRegisterUsed(r) 159 } 160 ret.stack = make([]*runtimeValueLocation, len(v.stack)) 161 for i, v := range v.stack { 162 ret.stack[i] = &runtimeValueLocation{ 163 valueType: v.valueType, 164 conditionalRegister: v.conditionalRegister, 165 stackPointer: v.stackPointer, 166 register: v.register, 167 } 168 } 169 ret.stackPointerCeil = v.stackPointerCeil 170 ret.unreservedGeneralPurposeRegisters = v.unreservedGeneralPurposeRegisters 171 ret.unreservedVectorRegisters = v.unreservedVectorRegisters 172 return ret 173 } 174 175 // pushRuntimeValueLocationOnRegister creates a new runtimeValueLocation with a given register and pushes onto 176 // the location stack. 177 func (v *runtimeValueLocationStack) pushRuntimeValueLocationOnRegister(reg asm.Register, vt runtimeValueType) (loc *runtimeValueLocation) { 178 loc = &runtimeValueLocation{register: reg, conditionalRegister: asm.ConditionalRegisterStateUnset} 179 loc.valueType = vt 180 181 v.push(loc) 182 return 183 } 184 185 // pushRuntimeValueLocationOnRegister creates a new runtimeValueLocation and pushes onto the location stack. 186 func (v *runtimeValueLocationStack) pushRuntimeValueLocationOnStack() (loc *runtimeValueLocation) { 187 loc = &runtimeValueLocation{register: asm.NilRegister, conditionalRegister: asm.ConditionalRegisterStateUnset} 188 v.push(loc) 189 return 190 } 191 192 // pushRuntimeValueLocationOnRegister creates a new runtimeValueLocation with a given conditional register state 193 // and pushes onto the location stack. 194 func (v *runtimeValueLocationStack) pushRuntimeValueLocationOnConditionalRegister(state asm.ConditionalRegisterState) (loc *runtimeValueLocation) { 195 loc = &runtimeValueLocation{register: asm.NilRegister, conditionalRegister: state} 196 v.push(loc) 197 return 198 } 199 200 // push a runtimeValueLocation onto the stack. 201 func (v *runtimeValueLocationStack) push(loc *runtimeValueLocation) { 202 loc.stackPointer = v.sp 203 if v.sp >= uint64(len(v.stack)) { 204 // This case we need to grow the stack capacity by appending the item, 205 // rather than indexing. 206 v.stack = append(v.stack, loc) 207 } else { 208 v.stack[v.sp] = loc 209 } 210 v.sp++ 211 // stackPointerCeil must be set after sp is incremented since 212 // we skip the stack grow if len(stack) >= basePointer+stackPointerCeil. 213 if v.sp > v.stackPointerCeil { 214 v.stackPointerCeil = v.sp 215 } 216 } 217 218 func (v *runtimeValueLocationStack) pop() (loc *runtimeValueLocation) { 219 v.sp-- 220 loc = v.stack[v.sp] 221 return 222 } 223 224 func (v *runtimeValueLocationStack) popV128() (loc *runtimeValueLocation) { 225 v.sp -= 2 226 loc = v.stack[v.sp] 227 return 228 } 229 230 func (v *runtimeValueLocationStack) peek() (loc *runtimeValueLocation) { 231 loc = v.stack[v.sp-1] 232 return 233 } 234 235 func (v *runtimeValueLocationStack) releaseRegister(loc *runtimeValueLocation) { 236 v.markRegisterUnused(loc.register) 237 loc.register = asm.NilRegister 238 loc.conditionalRegister = asm.ConditionalRegisterStateUnset 239 } 240 241 func (v *runtimeValueLocationStack) markRegisterUnused(regs ...asm.Register) { 242 for _, reg := range regs { 243 delete(v.usedRegisters, reg) 244 } 245 } 246 247 func (v *runtimeValueLocationStack) markRegisterUsed(regs ...asm.Register) { 248 for _, reg := range regs { 249 v.usedRegisters[reg] = struct{}{} 250 } 251 } 252 253 type registerType byte 254 255 const ( 256 registerTypeGeneralPurpose registerType = iota 257 // registerTypeVector represents a vector register which can be used for either scalar float 258 // operation or SIMD vector operation depending on the instruction by which the register is used. 259 // 260 // Note: In normal assembly language, scalar float and vector register have different notations as 261 // Vn is for vectors and Qn is for scalar floats on arm64 for example. But on physical hardware, 262 // they are placed on the same locations. (Qn means the lower 64-bit of Vn vector register on arm64). 263 // 264 // In wazero, for the sake of simplicity in the register allocation, we intentionally conflate these two types 265 // and delegate the decision to the assembler which is aware of the instruction types for which these registers are used. 266 registerTypeVector 267 ) 268 269 func (tp registerType) String() (ret string) { 270 switch tp { 271 case registerTypeGeneralPurpose: 272 ret = "int" 273 case registerTypeVector: 274 ret = "vector" 275 } 276 return 277 } 278 279 // takeFreeRegister searches for unused registers. Any found are marked used and returned. 280 func (v *runtimeValueLocationStack) takeFreeRegister(tp registerType) (reg asm.Register, found bool) { 281 var targetRegs []asm.Register 282 switch tp { 283 case registerTypeVector: 284 targetRegs = v.unreservedVectorRegisters 285 case registerTypeGeneralPurpose: 286 targetRegs = v.unreservedGeneralPurposeRegisters 287 } 288 for _, candidate := range targetRegs { 289 if _, ok := v.usedRegisters[candidate]; ok { 290 continue 291 } 292 return candidate, true 293 } 294 return 0, false 295 } 296 297 func (v *runtimeValueLocationStack) takeFreeRegisters(tp registerType, num int) (regs []asm.Register, found bool) { 298 var targetRegs []asm.Register 299 switch tp { 300 case registerTypeVector: 301 targetRegs = v.unreservedVectorRegisters 302 case registerTypeGeneralPurpose: 303 targetRegs = v.unreservedGeneralPurposeRegisters 304 } 305 306 regs = make([]asm.Register, 0, num) 307 for _, candidate := range targetRegs { 308 if _, ok := v.usedRegisters[candidate]; ok { 309 continue 310 } 311 regs = append(regs, candidate) 312 if len(regs) == num { 313 found = true 314 break 315 } 316 } 317 return 318 } 319 320 // Search through the stack, and steal the register from the last used 321 // variable on the stack. 322 func (v *runtimeValueLocationStack) takeStealTargetFromUsedRegister(tp registerType) (*runtimeValueLocation, bool) { 323 for i := uint64(0); i < v.sp; i++ { 324 loc := v.stack[i] 325 if loc.onRegister() { 326 switch tp { 327 case registerTypeVector: 328 if loc.valueType == runtimeValueTypeV128Hi { 329 panic("BUG: V128Hi must be above the corresponding V128Lo") 330 } 331 if isVectorRegister(loc.register) { 332 return loc, true 333 } 334 case registerTypeGeneralPurpose: 335 if isGeneralPurposeRegister(loc.register) { 336 return loc, true 337 } 338 } 339 } 340 } 341 return nil, false 342 } 343 344 // init sets up the runtimeValueLocationStack which reflects the state of 345 // the stack at the beginning of the function. 346 // 347 // See the diagram in callEngine.stack. 348 func (v *runtimeValueLocationStack) init(sig *wasm.FunctionType) { 349 for _, t := range sig.Params { 350 loc := v.pushRuntimeValueLocationOnStack() 351 switch t { 352 case wasm.ValueTypeI32: 353 loc.valueType = runtimeValueTypeI32 354 case wasm.ValueTypeI64, wasm.ValueTypeFuncref, wasm.ValueTypeExternref: 355 loc.valueType = runtimeValueTypeI64 356 case wasm.ValueTypeF32: 357 loc.valueType = runtimeValueTypeF32 358 case wasm.ValueTypeF64: 359 loc.valueType = runtimeValueTypeF64 360 case wasm.ValueTypeV128: 361 loc.valueType = runtimeValueTypeV128Lo 362 hi := v.pushRuntimeValueLocationOnStack() 363 hi.valueType = runtimeValueTypeV128Hi 364 default: 365 panic("BUG") 366 } 367 } 368 369 // If the len(results) > len(args), the slots for all results are reserved after 370 // arguments, so we reflect that into the location stack. 371 for i := 0; i < sig.ResultNumInUint64-sig.ParamNumInUint64; i++ { 372 _ = v.pushRuntimeValueLocationOnStack() 373 } 374 375 // Then push the control frame fields. 376 for i := 0; i < callFrameDataSizeInUint64; i++ { 377 loc := v.pushRuntimeValueLocationOnStack() 378 loc.valueType = runtimeValueTypeI64 379 } 380 } 381 382 // getCallFrameLocations returns each field of callFrame's runtime location. 383 // 384 // See the diagram in callEngine.stack. 385 func (v *runtimeValueLocationStack) getCallFrameLocations(sig *wasm.FunctionType) ( 386 returnAddress, callerStackBasePointerInBytes, callerFunction *runtimeValueLocation, 387 ) { 388 offset := callFrameOffset(sig) 389 return v.stack[offset], v.stack[offset+1], v.stack[offset+2] 390 } 391 392 // pushCallFrame pushes a call frame's runtime locations onto the stack assuming that 393 // the function call parameters are already pushed there. 394 // 395 // See the diagram in callEngine.stack. 396 func (v *runtimeValueLocationStack) pushCallFrame(callTargetFunctionType *wasm.FunctionType) ( 397 returnAddress, callerStackBasePointerInBytes, callerFunction *runtimeValueLocation, 398 ) { 399 // If len(results) > len(args), we reserve the slots for the results below the call frame. 400 reservedSlotsBeforeCallFrame := callTargetFunctionType.ResultNumInUint64 - callTargetFunctionType.ParamNumInUint64 401 for i := 0; i < reservedSlotsBeforeCallFrame; i++ { 402 v.pushRuntimeValueLocationOnStack() 403 } 404 405 // Push the runtime location for each field of callFrame struct. Note that each of them has 406 // uint64 type, and therefore must be treated as runtimeValueTypeI64. 407 408 // callFrame.returnAddress 409 returnAddress = v.pushRuntimeValueLocationOnStack() 410 returnAddress.valueType = runtimeValueTypeI64 411 // callFrame.returnStackBasePointerInBytes 412 callerStackBasePointerInBytes = v.pushRuntimeValueLocationOnStack() 413 callerStackBasePointerInBytes.valueType = runtimeValueTypeI64 414 // callFrame.function 415 callerFunction = v.pushRuntimeValueLocationOnStack() 416 callerFunction.valueType = runtimeValueTypeI64 417 return 418 }