@@ -407,18 +407,25 @@ function merge_byte_gep_chains!(mod::LLVM.Module)
407407 v isa LLVM. GetElementPtrInst && length (operands (v)) == 2 &&
408408 LLVM. LLVMType (LLVM. API. LLVMGetGEPSourceElementType (v)) == i8
409409
410- for f in functions (mod)
411- isdeclaration (f) && continue
412- worklist = LLVM. Instruction[]
410+ # the first `gep i8, (gep i8, p, A), B` in `f`, or `nothing`
411+ function next_chain (f)
413412 for bb in blocks (f), inst in instructions (bb)
414- is_byte_gep (inst) && is_byte_gep (operands (inst)[1 ]) && push! (worklist, inst)
413+ is_byte_gep (inst) && is_byte_gep (operands (inst)[1 ]) && return inst
415414 end
416- # process defs-before-uses (instruction order) so longer chains collapse fully:
417- # an inner gep is rewritten before the outer one that consumes it.
418- for gep in worklist
419- src = operands (gep)[1 ]
420- (is_byte_gep (gep) && is_byte_gep (src)) || continue # may already be merged
421- base = operands (src)[1 ]
415+ return nothing
416+ end
417+
418+ for f in functions (mod)
419+ isdeclaration (f) && continue
420+ # Rescan after each merge instead of keeping a worklist: a merge can erase a *different*
421+ # chained GEP (a now-dead inner GEP whose only use was the one we just folded), and a
422+ # block layout that doesn't follow dominance can place that inner GEP after the outer one,
423+ # so stale worklist entries would be use-after-free. Rescanning only ever inspects live
424+ # instructions; each merge replaces a chained GEP with a shallower one (and shortens its
425+ # users), so the total chain depth strictly decreases and this terminates.
426+ while (gep = next_chain (f)) != = nothing
427+ src = operands (gep)[1 ]
428+ base = operands (src)[1 ]
422429 inbounds = LLVM. API. LLVMIsInBounds (gep) != 0 && LLVM. API. LLVMIsInBounds (src) != 0
423430 @dispose builder= IRBuilder () begin
424431 position! (builder, gep)
0 commit comments