1// Package frontend implements the translation of WebAssembly to SSA IR using the ssa package.
2package frontend
3
4import (
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.
15type 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
71type (
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
87var knownSafeBoundsAtTheEndOfBlockNil = wazevoapi.NewNilVarLength[knownSafeBoundWithID]()
88
89// NewFrontendCompiler returns a frontend Compiler.
90func 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
104func (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.
200func 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.
218func (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 ;)).
235const 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.
241func (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()
279 c.declareNecessaryVariables()
280
281 c.lowerBody(entryBlock)
282}
283
284// localVariable returns the SSA variable for the given Wasm local index.
285func (c *Compiler) localVariable(index wasm.Index) ssa.Variable {
286 return c.wasmLocalToVariable[index]
287}
288
289func (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.
298func (c *Compiler) declareWasmLocals() {
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 c.ssaBuilder.InsertZeroValue(st)
305 }
306}
307
308func (c *Compiler) declareNecessaryVariables() {
309 if c.needMemory = c.m.MemorySection != nil; c.needMemory {
310 c.memoryShared = c.m.MemorySection.IsShared
311 } else if c.needMemory = c.m.ImportMemoryCount > 0; c.needMemory {
312 for _, imp := range c.m.ImportSection {
313 if imp.Type == wasm.ExternTypeMemory {
314 c.memoryShared = imp.DescMem.IsShared
315 break
316 }
317 }
318 }
319
320 if c.needMemory {
321 c.memoryBaseVariable = c.ssaBuilder.DeclareVariable(ssa.TypeI64)
322 c.memoryLenVariable = c.ssaBuilder.DeclareVariable(ssa.TypeI64)
323 }
324
325 c.globalVariables = c.globalVariables[:0]
326 c.mutableGlobalVariablesIndexes = c.mutableGlobalVariablesIndexes[:0]
327 c.globalVariablesTypes = c.globalVariablesTypes[:0]
328 for _, imp := range c.m.ImportSection {
329 if imp.Type == wasm.ExternTypeGlobal {
330 desc := imp.DescGlobal
331 c.declareWasmGlobal(desc.ValType, desc.Mutable)
332 }
333 }
334 for _, g := range c.m.GlobalSection {
335 desc := g.Type
336 c.declareWasmGlobal(desc.ValType, desc.Mutable)
337 }
338
339 // TODO: add tables.
340}
341
342func (c *Compiler) declareWasmGlobal(typ wasm.ValueType, mutable bool) {
343 var st ssa.Type
344 switch typ {
345 case wasm.ValueTypeI32:
346 st = ssa.TypeI32
347 case wasm.ValueTypeI64,
348 // Both externref and funcref are represented as I64 since we only support 64-bit platforms.
349 wasm.ValueTypeExternref, wasm.ValueTypeFuncref:
350 st = ssa.TypeI64
351 case wasm.ValueTypeF32:
352 st = ssa.TypeF32
353 case wasm.ValueTypeF64:
354 st = ssa.TypeF64
355 case wasm.ValueTypeV128:
356 st = ssa.TypeV128
357 default:
358 panic("TODO: " + wasm.ValueTypeName(typ))
359 }
360 v := c.ssaBuilder.DeclareVariable(st)
361 index := wasm.Index(len(c.globalVariables))
362 c.globalVariables = append(c.globalVariables, v)
363 c.globalVariablesTypes = append(c.globalVariablesTypes, st)
364 if mutable {
365 c.mutableGlobalVariablesIndexes = append(c.mutableGlobalVariablesIndexes, index)
366 }
367}
368
369// WasmTypeToSSAType converts wasm.ValueType to ssa.Type.
370func WasmTypeToSSAType(vt wasm.ValueType) ssa.Type {
371 switch vt {
372 case wasm.ValueTypeI32:
373 return ssa.TypeI32
374 case wasm.ValueTypeI64,
375 // Both externref and funcref are represented as I64 since we only support 64-bit platforms.
376 wasm.ValueTypeExternref, wasm.ValueTypeFuncref:
377 return ssa.TypeI64
378 case wasm.ValueTypeF32:
379 return ssa.TypeF32
380 case wasm.ValueTypeF64:
381 return ssa.TypeF64
382 case wasm.ValueTypeV128:
383 return ssa.TypeV128
384 default:
385 panic("TODO: " + wasm.ValueTypeName(vt))
386 }
387}
388
389// addBlockParamsFromWasmTypes adds the block parameters to the given block.
390func (c *Compiler) addBlockParamsFromWasmTypes(tps []wasm.ValueType, blk ssa.BasicBlock) {
391 for _, typ := range tps {
392 st := WasmTypeToSSAType(typ)
393 blk.AddParam(c.ssaBuilder, st)
394 }
395}
396
397// formatBuilder outputs the constructed SSA function as a string with a source information.
398func (c *Compiler) formatBuilder() string {
399 return c.ssaBuilder.Format()
400}
401
402// SignatureForListener returns the signatures for the listener functions.
403func SignatureForListener(wasmSig *wasm.FunctionType) (*ssa.Signature, *ssa.Signature) {
404 beforeSig := &ssa.Signature{}
405 beforeSig.Params = make([]ssa.Type, len(wasmSig.Params)+2)
406 beforeSig.Params[0] = ssa.TypeI64 // Execution context.
407 beforeSig.Params[1] = ssa.TypeI32 // Function index.
408 for i, p := range wasmSig.Params {
409 beforeSig.Params[i+2] = WasmTypeToSSAType(p)
410 }
411 afterSig := &ssa.Signature{}
412 afterSig.Params = make([]ssa.Type, len(wasmSig.Results)+2)
413 afterSig.Params[0] = ssa.TypeI64 // Execution context.
414 afterSig.Params[1] = ssa.TypeI32 // Function index.
415 for i, p := range wasmSig.Results {
416 afterSig.Params[i+2] = WasmTypeToSSAType(p)
417 }
418 return beforeSig, afterSig
419}
420
421// isBoundSafe returns true if the given value is known to be safe to access up to the given bound.
422func (c *Compiler) getKnownSafeBound(v ssa.ValueID) *knownSafeBound {
423 if int(v) >= len(c.knownSafeBounds) {
424 return nil
425 }
426 return &c.knownSafeBounds[v]
427}
428
429// recordKnownSafeBound records the given safe bound for the given value.
430func (c *Compiler) recordKnownSafeBound(v ssa.ValueID, safeBound uint64, absoluteAddr ssa.Value) {
431 if int(v) >= len(c.knownSafeBounds) {
432 c.knownSafeBounds = append(c.knownSafeBounds, make([]knownSafeBound, v+1)...)
433 }
434
435 if exiting := c.knownSafeBounds[v]; exiting.bound == 0 {
436 c.knownSafeBounds[v] = knownSafeBound{
437 bound: safeBound,
438 absoluteAddr: absoluteAddr,
439 }
440 c.knownSafeBoundsSet = append(c.knownSafeBoundsSet, v)
441 } else if safeBound > exiting.bound {
442 c.knownSafeBounds[v].bound = safeBound
443 }
444}
445
446// clearSafeBounds clears the known safe bounds.
447func (c *Compiler) clearSafeBounds() {
448 for _, v := range c.knownSafeBoundsSet {
449 ptr := &c.knownSafeBounds[v]
450 ptr.bound = 0
451 ptr.absoluteAddr = ssa.ValueInvalid
452 }
453 c.knownSafeBoundsSet = c.knownSafeBoundsSet[:0]
454}
455
456// resetAbsoluteAddressInSafeBounds resets the absolute addresses recorded in the known safe bounds.
457func (c *Compiler) resetAbsoluteAddressInSafeBounds() {
458 for _, v := range c.knownSafeBoundsSet {
459 ptr := &c.knownSafeBounds[v]
460 ptr.absoluteAddr = ssa.ValueInvalid
461 }
462}
463
464func (k *knownSafeBound) valid() bool {
465 return k != nil && k.bound > 0
466}
467
468func (c *Compiler) allocateVarLengthValues(_cap int, vs ...ssa.Value) ssa.Values {
469 builder := c.ssaBuilder
470 pool := builder.VarLengthPool()
471 args := pool.Allocate(_cap)
472 args = args.Append(builder.VarLengthPool(), vs...)
473 return args
474}
475
476func (c *Compiler) finalizeKnownSafeBoundsAtTheEndOfBlock(bID ssa.BasicBlockID) {
477 _bID := int(bID)
478 if l := len(c.knownSafeBoundsAtTheEndOfBlocks); _bID >= l {
479 c.knownSafeBoundsAtTheEndOfBlocks = append(c.knownSafeBoundsAtTheEndOfBlocks,
480 make([]knownSafeBoundsAtTheEndOfBlock, _bID+1-len(c.knownSafeBoundsAtTheEndOfBlocks))...)
481 for i := l; i < len(c.knownSafeBoundsAtTheEndOfBlocks); i++ {
482 c.knownSafeBoundsAtTheEndOfBlocks[i] = knownSafeBoundsAtTheEndOfBlockNil
483 }
484 }
485 p := &c.varLengthKnownSafeBoundWithIDPool
486 size := len(c.knownSafeBoundsSet)
487 allocated := c.varLengthKnownSafeBoundWithIDPool.Allocate(size)
488 // Sort the known safe bounds by the value ID so that we can use the intersection algorithm in initializeCurrentBlockKnownBounds.
489 sortSSAValueIDs(c.knownSafeBoundsSet)
490 for _, vID := range c.knownSafeBoundsSet {
491 kb := c.knownSafeBounds[vID]
492 allocated = allocated.Append(p, knownSafeBoundWithID{
493 knownSafeBound: kb,
494 id: vID,
495 })
496 }
497 c.knownSafeBoundsAtTheEndOfBlocks[bID] = allocated
498 c.clearSafeBounds()
499}
500
501func (c *Compiler) initializeCurrentBlockKnownBounds() {
502 currentBlk := c.ssaBuilder.CurrentBlock()
503 switch preds := currentBlk.Preds(); preds {
504 case 0:
505 case 1:
506 pred := currentBlk.Pred(0).ID()
507 for _, kb := range c.getKnownSafeBoundsAtTheEndOfBlocks(pred).View() {
508 // Unless the block is sealed, we cannot assume the absolute address is valid:
509 // later we might add another predecessor that has no visibility of that value.
510 addr := ssa.ValueInvalid
511 if currentBlk.Sealed() {
512 addr = kb.absoluteAddr
513 }
514 c.recordKnownSafeBound(kb.id, kb.bound, addr)
515 }
516 default:
517 c.pointers = c.pointers[:0]
518 c.bounds = c.bounds[:0]
519 for i := 0; i < preds; i++ {
520 c.bounds = append(c.bounds, c.getKnownSafeBoundsAtTheEndOfBlocks(currentBlk.Pred(i).ID()).View())
521 c.pointers = append(c.pointers, 0)
522 }
523
524 // If there are multiple predecessors, we need to find the intersection of the known safe bounds.
525
526 outer:
527 for {
528 smallestID := ssa.ValueID(math.MaxUint32)
529 for i, ptr := range c.pointers {
530 if ptr >= len(c.bounds[i]) {
531 break outer
532 }
533 cb := &c.bounds[i][ptr]
534 if id := cb.id; id < smallestID {
535 smallestID = cb.id
536 }
537 }
538
539 // Check if current elements are the same across all lists.
540 same := true
541 minBound := uint64(math.MaxUint64)
542 for i := 0; i < preds; i++ {
543 cb := &c.bounds[i][c.pointers[i]]
544 if cb.id != smallestID {
545 same = false
546 } else {
547 if cb.bound < minBound {
548 minBound = cb.bound
549 }
550 c.pointers[i]++
551 }
552 }
553
554 if same { // All elements are the same.
555 // Absolute address cannot be used in the intersection since the value might be only defined in one of the predecessors.
556 c.recordKnownSafeBound(smallestID, minBound, ssa.ValueInvalid)
557 }
558 }
559 }
560}
561
562func (c *Compiler) getKnownSafeBoundsAtTheEndOfBlocks(id ssa.BasicBlockID) knownSafeBoundsAtTheEndOfBlock {
563 if int(id) >= len(c.knownSafeBoundsAtTheEndOfBlocks) {
564 return knownSafeBoundsAtTheEndOfBlockNil
565 }
566 return c.knownSafeBoundsAtTheEndOfBlocks[id]
567}