github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/engine/wazevo/frontend/frontend.go (about) 1 // Package frontend implements the translation of WebAssembly to SSA IR using the ssa package. 2 package frontend 3 4 import ( 5 "bytes" 6 "math" 7 8 "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" 9 "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" 10 "github.com/tetratelabs/wazero/internal/wasm" 11 ) 12 13 // Compiler is in charge of lowering Wasm to SSA IR, and does the optimization 14 // on top of it in architecture-independent way. 15 type Compiler struct { 16 // Per-module data that is used across all functions. 17 18 m *wasm.Module 19 offset *wazevoapi.ModuleContextOffsetData 20 // ssaBuilder is a ssa.Builder used by this frontend. 21 ssaBuilder ssa.Builder 22 signatures map[*wasm.FunctionType]*ssa.Signature 23 listenerSignatures map[*wasm.FunctionType][2]*ssa.Signature 24 memoryGrowSig ssa.Signature 25 memoryWait32Sig ssa.Signature 26 memoryWait64Sig ssa.Signature 27 memoryNotifySig ssa.Signature 28 checkModuleExitCodeSig ssa.Signature 29 tableGrowSig ssa.Signature 30 refFuncSig ssa.Signature 31 memmoveSig ssa.Signature 32 ensureTermination bool 33 34 // Followings are reset by per function. 35 36 // wasmLocalToVariable maps the index (considered as wasm.Index of locals) 37 // to the corresponding ssa.Variable. 38 wasmLocalToVariable [] /* local index to */ ssa.Variable 39 wasmLocalFunctionIndex wasm.Index 40 wasmFunctionTypeIndex wasm.Index 41 wasmFunctionTyp *wasm.FunctionType 42 wasmFunctionLocalTypes []wasm.ValueType 43 wasmFunctionBody []byte 44 wasmFunctionBodyOffsetInCodeSection uint64 45 memoryBaseVariable, memoryLenVariable ssa.Variable 46 needMemory bool 47 memoryShared bool 48 globalVariables []ssa.Variable 49 globalVariablesTypes []ssa.Type 50 mutableGlobalVariablesIndexes []wasm.Index // index to ^. 51 needListener bool 52 needSourceOffsetInfo bool 53 // br is reused during lowering. 54 br *bytes.Reader 55 loweringState loweringState 56 57 knownSafeBounds [] /* ssa.ValueID to */ knownSafeBound 58 knownSafeBoundsSet []ssa.ValueID 59 60 knownSafeBoundsAtTheEndOfBlocks [] /* ssa.BlockID to */ knownSafeBoundsAtTheEndOfBlock 61 varLengthKnownSafeBoundWithIDPool wazevoapi.VarLengthPool[knownSafeBoundWithID] 62 63 execCtxPtrValue, moduleCtxPtrValue ssa.Value 64 65 // Following are reused for the known safe bounds analysis. 66 67 pointers []int 68 bounds [][]knownSafeBoundWithID 69 } 70 71 type ( 72 // knownSafeBound represents a known safe bound for a value. 73 knownSafeBound struct { 74 // bound is a constant upper bound for the value. 75 bound uint64 76 // absoluteAddr is the absolute address of the value. 77 absoluteAddr ssa.Value 78 } 79 // knownSafeBoundWithID is a knownSafeBound with the ID of the value. 80 knownSafeBoundWithID struct { 81 knownSafeBound 82 id ssa.ValueID 83 } 84 knownSafeBoundsAtTheEndOfBlock = wazevoapi.VarLength[knownSafeBoundWithID] 85 ) 86 87 var knownSafeBoundsAtTheEndOfBlockNil = wazevoapi.NewNilVarLength[knownSafeBoundWithID]() 88 89 // NewFrontendCompiler returns a frontend Compiler. 90 func NewFrontendCompiler(m *wasm.Module, ssaBuilder ssa.Builder, offset *wazevoapi.ModuleContextOffsetData, ensureTermination bool, listenerOn bool, sourceInfo bool) *Compiler { 91 c := &Compiler{ 92 m: m, 93 ssaBuilder: ssaBuilder, 94 br: bytes.NewReader(nil), 95 offset: offset, 96 ensureTermination: ensureTermination, 97 needSourceOffsetInfo: sourceInfo, 98 varLengthKnownSafeBoundWithIDPool: wazevoapi.NewVarLengthPool[knownSafeBoundWithID](), 99 } 100 c.declareSignatures(listenerOn) 101 return c 102 } 103 104 func (c *Compiler) declareSignatures(listenerOn bool) { 105 m := c.m 106 c.signatures = make(map[*wasm.FunctionType]*ssa.Signature, len(m.TypeSection)+2) 107 if listenerOn { 108 c.listenerSignatures = make(map[*wasm.FunctionType][2]*ssa.Signature, len(m.TypeSection)) 109 } 110 for i := range m.TypeSection { 111 wasmSig := &m.TypeSection[i] 112 sig := SignatureForWasmFunctionType(wasmSig) 113 sig.ID = ssa.SignatureID(i) 114 c.signatures[wasmSig] = &sig 115 c.ssaBuilder.DeclareSignature(&sig) 116 117 if listenerOn { 118 beforeSig, afterSig := SignatureForListener(wasmSig) 119 beforeSig.ID = ssa.SignatureID(i) + ssa.SignatureID(len(m.TypeSection)) 120 afterSig.ID = ssa.SignatureID(i) + ssa.SignatureID(len(m.TypeSection))*2 121 c.listenerSignatures[wasmSig] = [2]*ssa.Signature{beforeSig, afterSig} 122 c.ssaBuilder.DeclareSignature(beforeSig) 123 c.ssaBuilder.DeclareSignature(afterSig) 124 } 125 } 126 127 begin := ssa.SignatureID(len(m.TypeSection)) 128 if listenerOn { 129 begin *= 3 130 } 131 c.memoryGrowSig = ssa.Signature{ 132 ID: begin, 133 // Takes execution context and the page size to grow. 134 Params: []ssa.Type{ssa.TypeI64, ssa.TypeI32}, 135 // Returns the previous page size. 136 Results: []ssa.Type{ssa.TypeI32}, 137 } 138 c.ssaBuilder.DeclareSignature(&c.memoryGrowSig) 139 140 c.checkModuleExitCodeSig = ssa.Signature{ 141 ID: c.memoryGrowSig.ID + 1, 142 // Only takes execution context. 143 Params: []ssa.Type{ssa.TypeI64}, 144 } 145 c.ssaBuilder.DeclareSignature(&c.checkModuleExitCodeSig) 146 147 c.tableGrowSig = ssa.Signature{ 148 ID: c.checkModuleExitCodeSig.ID + 1, 149 Params: []ssa.Type{ssa.TypeI64 /* exec context */, ssa.TypeI32 /* table index */, ssa.TypeI32 /* num */, ssa.TypeI64 /* ref */}, 150 // Returns the previous size. 151 Results: []ssa.Type{ssa.TypeI32}, 152 } 153 c.ssaBuilder.DeclareSignature(&c.tableGrowSig) 154 155 c.refFuncSig = ssa.Signature{ 156 ID: c.tableGrowSig.ID + 1, 157 Params: []ssa.Type{ssa.TypeI64 /* exec context */, ssa.TypeI32 /* func index */}, 158 // Returns the function reference. 159 Results: []ssa.Type{ssa.TypeI64}, 160 } 161 c.ssaBuilder.DeclareSignature(&c.refFuncSig) 162 163 c.memmoveSig = ssa.Signature{ 164 ID: c.refFuncSig.ID + 1, 165 // dst, src, and the byte count. 166 Params: []ssa.Type{ssa.TypeI64, ssa.TypeI64, ssa.TypeI64}, 167 } 168 169 c.ssaBuilder.DeclareSignature(&c.memmoveSig) 170 171 c.memoryWait32Sig = ssa.Signature{ 172 ID: c.memmoveSig.ID + 1, 173 // exec context, timeout, expected, addr 174 Params: []ssa.Type{ssa.TypeI64, ssa.TypeI64, ssa.TypeI32, ssa.TypeI64}, 175 // Returns the status. 176 Results: []ssa.Type{ssa.TypeI32}, 177 } 178 c.ssaBuilder.DeclareSignature(&c.memoryWait32Sig) 179 180 c.memoryWait64Sig = ssa.Signature{ 181 ID: c.memoryWait32Sig.ID + 1, 182 // exec context, timeout, expected, addr 183 Params: []ssa.Type{ssa.TypeI64, ssa.TypeI64, ssa.TypeI64, ssa.TypeI64}, 184 // Returns the status. 185 Results: []ssa.Type{ssa.TypeI32}, 186 } 187 c.ssaBuilder.DeclareSignature(&c.memoryWait64Sig) 188 189 c.memoryNotifySig = ssa.Signature{ 190 ID: c.memoryWait64Sig.ID + 1, 191 // exec context, count, addr 192 Params: []ssa.Type{ssa.TypeI64, ssa.TypeI32, ssa.TypeI64}, 193 // Returns the number notified. 194 Results: []ssa.Type{ssa.TypeI32}, 195 } 196 c.ssaBuilder.DeclareSignature(&c.memoryNotifySig) 197 } 198 199 // SignatureForWasmFunctionType returns the ssa.Signature for the given wasm.FunctionType. 200 func SignatureForWasmFunctionType(typ *wasm.FunctionType) ssa.Signature { 201 sig := ssa.Signature{ 202 // +2 to pass moduleContextPtr and executionContextPtr. See the inline comment LowerToSSA. 203 Params: make([]ssa.Type, len(typ.Params)+2), 204 Results: make([]ssa.Type, len(typ.Results)), 205 } 206 sig.Params[0] = executionContextPtrTyp 207 sig.Params[1] = moduleContextPtrTyp 208 for j, typ := range typ.Params { 209 sig.Params[j+2] = WasmTypeToSSAType(typ) 210 } 211 for j, typ := range typ.Results { 212 sig.Results[j] = WasmTypeToSSAType(typ) 213 } 214 return sig 215 } 216 217 // Init initializes the state of frontendCompiler and make it ready for a next function. 218 func (c *Compiler) Init(idx, typIndex wasm.Index, typ *wasm.FunctionType, localTypes []wasm.ValueType, body []byte, needListener bool, bodyOffsetInCodeSection uint64) { 219 c.ssaBuilder.Init(c.signatures[typ]) 220 c.loweringState.reset() 221 222 c.wasmFunctionTypeIndex = typIndex 223 c.wasmLocalFunctionIndex = idx 224 c.wasmFunctionTyp = typ 225 c.wasmFunctionLocalTypes = localTypes 226 c.wasmFunctionBody = body 227 c.wasmFunctionBodyOffsetInCodeSection = bodyOffsetInCodeSection 228 c.needListener = needListener 229 c.clearSafeBounds() 230 c.varLengthKnownSafeBoundWithIDPool.Reset() 231 c.knownSafeBoundsAtTheEndOfBlocks = c.knownSafeBoundsAtTheEndOfBlocks[:0] 232 } 233 234 // Note: this assumes 64-bit platform (I believe we won't have 32-bit backend ;)). 235 const executionContextPtrTyp, moduleContextPtrTyp = ssa.TypeI64, ssa.TypeI64 236 237 // LowerToSSA lowers the current function to SSA function which will be held by ssaBuilder. 238 // After calling this, the caller will be able to access the SSA info in *Compiler.ssaBuilder. 239 // 240 // Note that this only does the naive lowering, and do not do any optimization, instead the caller is expected to do so. 241 func (c *Compiler) LowerToSSA() { 242 builder := c.ssaBuilder 243 244 // Set up the entry block. 245 entryBlock := builder.AllocateBasicBlock() 246 builder.SetCurrentBlock(entryBlock) 247 248 // Functions always take two parameters in addition to Wasm-level parameters: 249 // 250 // 1. executionContextPtr: pointer to the *executionContext in wazevo package. 251 // This will be used to exit the execution in the face of trap, plus used for host function calls. 252 // 253 // 2. moduleContextPtr: pointer to the *moduleContextOpaque in wazevo package. 254 // This will be used to access memory, etc. Also, this will be used during host function calls. 255 // 256 // Note: it's clear that sometimes a function won't need them. For example, 257 // if the function doesn't trap and doesn't make function call, then 258 // we might be able to eliminate the parameter. However, if that function 259 // can be called via call_indirect, then we cannot eliminate because the 260 // signature won't match with the expected one. 261 // TODO: maybe there's some way to do this optimization without glitches, but so far I have no clue about the feasibility. 262 // 263 // Note: In Wasmtime or many other runtimes, moduleContextPtr is called "vmContext". Also note that `moduleContextPtr` 264 // is wazero-specific since other runtimes can naturally use the OS-level signal to do this job thanks to the fact that 265 // they can use native stack vs wazero cannot use Go-routine stack and have to use Go-runtime allocated []byte as a stack. 266 c.execCtxPtrValue = entryBlock.AddParam(builder, executionContextPtrTyp) 267 c.moduleCtxPtrValue = entryBlock.AddParam(builder, moduleContextPtrTyp) 268 builder.AnnotateValue(c.execCtxPtrValue, "exec_ctx") 269 builder.AnnotateValue(c.moduleCtxPtrValue, "module_ctx") 270 271 for i, typ := range c.wasmFunctionTyp.Params { 272 st := WasmTypeToSSAType(typ) 273 variable := builder.DeclareVariable(st) 274 value := entryBlock.AddParam(builder, st) 275 builder.DefineVariable(variable, value, entryBlock) 276 c.setWasmLocalVariable(wasm.Index(i), variable) 277 } 278 c.declareWasmLocals(entryBlock) 279 c.declareNecessaryVariables() 280 281 c.lowerBody(entryBlock) 282 } 283 284 // localVariable returns the SSA variable for the given Wasm local index. 285 func (c *Compiler) localVariable(index wasm.Index) ssa.Variable { 286 return c.wasmLocalToVariable[index] 287 } 288 289 func (c *Compiler) setWasmLocalVariable(index wasm.Index, variable ssa.Variable) { 290 idx := int(index) 291 if idx >= len(c.wasmLocalToVariable) { 292 c.wasmLocalToVariable = append(c.wasmLocalToVariable, make([]ssa.Variable, idx+1-len(c.wasmLocalToVariable))...) 293 } 294 c.wasmLocalToVariable[idx] = variable 295 } 296 297 // declareWasmLocals declares the SSA variables for the Wasm locals. 298 func (c *Compiler) declareWasmLocals(entry ssa.BasicBlock) { 299 localCount := wasm.Index(len(c.wasmFunctionTyp.Params)) 300 for i, typ := range c.wasmFunctionLocalTypes { 301 st := WasmTypeToSSAType(typ) 302 variable := c.ssaBuilder.DeclareVariable(st) 303 c.setWasmLocalVariable(wasm.Index(i)+localCount, variable) 304 305 zeroInst := c.ssaBuilder.AllocateInstruction() 306 switch st { 307 case ssa.TypeI32: 308 zeroInst.AsIconst32(0) 309 case ssa.TypeI64: 310 zeroInst.AsIconst64(0) 311 case ssa.TypeF32: 312 zeroInst.AsF32const(0) 313 case ssa.TypeF64: 314 zeroInst.AsF64const(0) 315 case ssa.TypeV128: 316 zeroInst.AsVconst(0, 0) 317 default: 318 panic("TODO: " + wasm.ValueTypeName(typ)) 319 } 320 321 c.ssaBuilder.InsertInstruction(zeroInst) 322 value := zeroInst.Return() 323 c.ssaBuilder.DefineVariable(variable, value, entry) 324 } 325 } 326 327 func (c *Compiler) declareNecessaryVariables() { 328 if c.needMemory = c.m.MemorySection != nil; c.needMemory { 329 c.memoryShared = c.m.MemorySection.IsShared 330 } else if c.needMemory = c.m.ImportMemoryCount > 0; c.needMemory { 331 for _, imp := range c.m.ImportSection { 332 if imp.Type == wasm.ExternTypeMemory { 333 c.memoryShared = imp.DescMem.IsShared 334 break 335 } 336 } 337 } 338 339 if c.needMemory { 340 c.memoryBaseVariable = c.ssaBuilder.DeclareVariable(ssa.TypeI64) 341 c.memoryLenVariable = c.ssaBuilder.DeclareVariable(ssa.TypeI64) 342 } 343 344 c.globalVariables = c.globalVariables[:0] 345 c.mutableGlobalVariablesIndexes = c.mutableGlobalVariablesIndexes[:0] 346 c.globalVariablesTypes = c.globalVariablesTypes[:0] 347 for _, imp := range c.m.ImportSection { 348 if imp.Type == wasm.ExternTypeGlobal { 349 desc := imp.DescGlobal 350 c.declareWasmGlobal(desc.ValType, desc.Mutable) 351 } 352 } 353 for _, g := range c.m.GlobalSection { 354 desc := g.Type 355 c.declareWasmGlobal(desc.ValType, desc.Mutable) 356 } 357 358 // TODO: add tables. 359 } 360 361 func (c *Compiler) declareWasmGlobal(typ wasm.ValueType, mutable bool) { 362 var st ssa.Type 363 switch typ { 364 case wasm.ValueTypeI32: 365 st = ssa.TypeI32 366 case wasm.ValueTypeI64, 367 // Both externref and funcref are represented as I64 since we only support 64-bit platforms. 368 wasm.ValueTypeExternref, wasm.ValueTypeFuncref: 369 st = ssa.TypeI64 370 case wasm.ValueTypeF32: 371 st = ssa.TypeF32 372 case wasm.ValueTypeF64: 373 st = ssa.TypeF64 374 case wasm.ValueTypeV128: 375 st = ssa.TypeV128 376 default: 377 panic("TODO: " + wasm.ValueTypeName(typ)) 378 } 379 v := c.ssaBuilder.DeclareVariable(st) 380 index := wasm.Index(len(c.globalVariables)) 381 c.globalVariables = append(c.globalVariables, v) 382 c.globalVariablesTypes = append(c.globalVariablesTypes, st) 383 if mutable { 384 c.mutableGlobalVariablesIndexes = append(c.mutableGlobalVariablesIndexes, index) 385 } 386 } 387 388 // WasmTypeToSSAType converts wasm.ValueType to ssa.Type. 389 func WasmTypeToSSAType(vt wasm.ValueType) ssa.Type { 390 switch vt { 391 case wasm.ValueTypeI32: 392 return ssa.TypeI32 393 case wasm.ValueTypeI64, 394 // Both externref and funcref are represented as I64 since we only support 64-bit platforms. 395 wasm.ValueTypeExternref, wasm.ValueTypeFuncref: 396 return ssa.TypeI64 397 case wasm.ValueTypeF32: 398 return ssa.TypeF32 399 case wasm.ValueTypeF64: 400 return ssa.TypeF64 401 case wasm.ValueTypeV128: 402 return ssa.TypeV128 403 default: 404 panic("TODO: " + wasm.ValueTypeName(vt)) 405 } 406 } 407 408 // addBlockParamsFromWasmTypes adds the block parameters to the given block. 409 func (c *Compiler) addBlockParamsFromWasmTypes(tps []wasm.ValueType, blk ssa.BasicBlock) { 410 for _, typ := range tps { 411 st := WasmTypeToSSAType(typ) 412 blk.AddParam(c.ssaBuilder, st) 413 } 414 } 415 416 // formatBuilder outputs the constructed SSA function as a string with a source information. 417 func (c *Compiler) formatBuilder() string { 418 return c.ssaBuilder.Format() 419 } 420 421 // SignatureForListener returns the signatures for the listener functions. 422 func SignatureForListener(wasmSig *wasm.FunctionType) (*ssa.Signature, *ssa.Signature) { 423 beforeSig := &ssa.Signature{} 424 beforeSig.Params = make([]ssa.Type, len(wasmSig.Params)+2) 425 beforeSig.Params[0] = ssa.TypeI64 // Execution context. 426 beforeSig.Params[1] = ssa.TypeI32 // Function index. 427 for i, p := range wasmSig.Params { 428 beforeSig.Params[i+2] = WasmTypeToSSAType(p) 429 } 430 afterSig := &ssa.Signature{} 431 afterSig.Params = make([]ssa.Type, len(wasmSig.Results)+2) 432 afterSig.Params[0] = ssa.TypeI64 // Execution context. 433 afterSig.Params[1] = ssa.TypeI32 // Function index. 434 for i, p := range wasmSig.Results { 435 afterSig.Params[i+2] = WasmTypeToSSAType(p) 436 } 437 return beforeSig, afterSig 438 } 439 440 // isBoundSafe returns true if the given value is known to be safe to access up to the given bound. 441 func (c *Compiler) getKnownSafeBound(v ssa.ValueID) *knownSafeBound { 442 if int(v) >= len(c.knownSafeBounds) { 443 return nil 444 } 445 return &c.knownSafeBounds[v] 446 } 447 448 // recordKnownSafeBound records the given safe bound for the given value. 449 func (c *Compiler) recordKnownSafeBound(v ssa.ValueID, safeBound uint64, absoluteAddr ssa.Value) { 450 if int(v) >= len(c.knownSafeBounds) { 451 c.knownSafeBounds = append(c.knownSafeBounds, make([]knownSafeBound, v+1)...) 452 } 453 454 if exiting := c.knownSafeBounds[v]; exiting.bound == 0 { 455 c.knownSafeBounds[v] = knownSafeBound{ 456 bound: safeBound, 457 absoluteAddr: absoluteAddr, 458 } 459 c.knownSafeBoundsSet = append(c.knownSafeBoundsSet, v) 460 } else if safeBound > exiting.bound { 461 c.knownSafeBounds[v].bound = safeBound 462 } 463 } 464 465 // clearSafeBounds clears the known safe bounds. 466 func (c *Compiler) clearSafeBounds() { 467 for _, v := range c.knownSafeBoundsSet { 468 ptr := &c.knownSafeBounds[v] 469 ptr.bound = 0 470 ptr.absoluteAddr = ssa.ValueInvalid 471 } 472 c.knownSafeBoundsSet = c.knownSafeBoundsSet[:0] 473 } 474 475 // resetAbsoluteAddressInSafeBounds resets the absolute addresses recorded in the known safe bounds. 476 func (c *Compiler) resetAbsoluteAddressInSafeBounds() { 477 for _, v := range c.knownSafeBoundsSet { 478 ptr := &c.knownSafeBounds[v] 479 ptr.absoluteAddr = ssa.ValueInvalid 480 } 481 } 482 483 func (k *knownSafeBound) valid() bool { 484 return k != nil && k.bound > 0 485 } 486 487 func (c *Compiler) allocateVarLengthValues(_cap int, vs ...ssa.Value) ssa.Values { 488 builder := c.ssaBuilder 489 pool := builder.VarLengthPool() 490 args := pool.Allocate(_cap) 491 args = args.Append(builder.VarLengthPool(), vs...) 492 return args 493 } 494 495 func (c *Compiler) finalizeKnownSafeBoundsAtTheEndOfBlock(bID ssa.BasicBlockID) { 496 _bID := int(bID) 497 if l := len(c.knownSafeBoundsAtTheEndOfBlocks); _bID >= l { 498 c.knownSafeBoundsAtTheEndOfBlocks = append(c.knownSafeBoundsAtTheEndOfBlocks, 499 make([]knownSafeBoundsAtTheEndOfBlock, _bID+1-len(c.knownSafeBoundsAtTheEndOfBlocks))...) 500 for i := l; i < len(c.knownSafeBoundsAtTheEndOfBlocks); i++ { 501 c.knownSafeBoundsAtTheEndOfBlocks[i] = knownSafeBoundsAtTheEndOfBlockNil 502 } 503 } 504 p := &c.varLengthKnownSafeBoundWithIDPool 505 size := len(c.knownSafeBoundsSet) 506 allocated := c.varLengthKnownSafeBoundWithIDPool.Allocate(size) 507 // Sort the known safe bounds by the value ID so that we can use the intersection algorithm in initializeCurrentBlockKnownBounds. 508 sortSSAValueIDs(c.knownSafeBoundsSet) 509 for _, vID := range c.knownSafeBoundsSet { 510 kb := c.knownSafeBounds[vID] 511 allocated = allocated.Append(p, knownSafeBoundWithID{ 512 knownSafeBound: kb, 513 id: vID, 514 }) 515 } 516 c.knownSafeBoundsAtTheEndOfBlocks[bID] = allocated 517 c.clearSafeBounds() 518 } 519 520 func (c *Compiler) initializeCurrentBlockKnownBounds() { 521 currentBlk := c.ssaBuilder.CurrentBlock() 522 switch preds := currentBlk.Preds(); preds { 523 case 0: 524 case 1: 525 pred := currentBlk.Pred(0).ID() 526 for _, kb := range c.getKnownSafeBoundsAtTheEndOfBlocks(pred).View() { 527 // Unless the block is sealed, we cannot assume the absolute address is valid: 528 // later we might add another predecessor that has no visibility of that value. 529 addr := ssa.ValueInvalid 530 if currentBlk.Sealed() { 531 addr = kb.absoluteAddr 532 } 533 c.recordKnownSafeBound(kb.id, kb.bound, addr) 534 } 535 default: 536 c.pointers = c.pointers[:0] 537 c.bounds = c.bounds[:0] 538 for i := 0; i < preds; i++ { 539 c.bounds = append(c.bounds, c.getKnownSafeBoundsAtTheEndOfBlocks(currentBlk.Pred(i).ID()).View()) 540 c.pointers = append(c.pointers, 0) 541 } 542 543 // If there are multiple predecessors, we need to find the intersection of the known safe bounds. 544 545 outer: 546 for { 547 smallestID := ssa.ValueID(math.MaxUint32) 548 for i, ptr := range c.pointers { 549 if ptr >= len(c.bounds[i]) { 550 break outer 551 } 552 cb := &c.bounds[i][ptr] 553 if id := cb.id; id < smallestID { 554 smallestID = cb.id 555 } 556 } 557 558 // Check if current elements are the same across all lists. 559 same := true 560 minBound := uint64(math.MaxUint64) 561 for i := 0; i < preds; i++ { 562 cb := &c.bounds[i][c.pointers[i]] 563 if cb.id != smallestID { 564 same = false 565 break 566 } else { 567 if cb.bound < minBound { 568 minBound = cb.bound 569 } 570 } 571 } 572 573 if same { // All elements are the same. 574 // Absolute address cannot be used in the intersection since the value might be only defined in one of the predecessors. 575 c.recordKnownSafeBound(smallestID, minBound, ssa.ValueInvalid) 576 } 577 578 // Move pointer(s) for the smallest ID forward (if same, move all). 579 for i := 0; i < preds; i++ { 580 cb := &c.bounds[i][c.pointers[i]] 581 if cb.id == smallestID { 582 c.pointers[i]++ 583 } 584 } 585 } 586 } 587 } 588 589 func (c *Compiler) getKnownSafeBoundsAtTheEndOfBlocks(id ssa.BasicBlockID) knownSafeBoundsAtTheEndOfBlock { 590 if int(id) >= len(c.knownSafeBoundsAtTheEndOfBlocks) { 591 return knownSafeBoundsAtTheEndOfBlockNil 592 } 593 return c.knownSafeBoundsAtTheEndOfBlocks[id] 594 }