Skip to content

Conversation

@gbaraldi
Copy link
Member

This is very vibe coded, but also has lots of tests and I have gone through it and haven't seen anything at that glance.

This fixes some of the issues we have in code like by sinking the stores into the error path

 array = rand(5,5,5,5,5)
 @code_llvm raw=true dump_module=true array[1,2,3,4,5]

new

  store i64 %"i1::Int64", ptr %"new::Tuple1", align 8, !dbg !43
  %12 = getelementptr inbounds nuw i8, ptr %"new::Tuple1", i64 8, !dbg !43
  store i64 %"i2::Int64", ptr %12, align 8, !dbg !43
  %13 = getelementptr inbounds nuw i8, ptr %"new::Tuple1", i64 16, !dbg !43
  store i64 %"I[1]::Int64", ptr %13, align 8, !dbg !43, !tbaa !83, !alias.scope !85, !noalias !86
  %14 = getelementptr inbounds nuw i8, ptr %"new::Tuple1", i64 24, !dbg !43
  store i64 %"I[2]::Int64", ptr %14, align 8, !dbg !43, !tbaa !83, !alias.scope !85, !noalias !86
  %15 = getelementptr inbounds nuw i8, ptr %"new::Tuple1", i64 32, !dbg !43
  store i64 %"I[3]::Int64", ptr %15, align 8, !dbg !43, !tbaa !83, !alias.scope !85, !noalias !86
; ┌ @ abstractarray.jl:702 within `checkbounds`
   call swiftcc void @j_throw_boundserror_3105(ptr nonnull swiftself "gcstack" %pgcstack_arg, ptr nonnull %"A::Array", ptr nocapture nonnull readonly %"new::Tuple1") #10, !dbg !42
   unreachable, !dbg !42

old

  %"new::Tuple1" = alloca [5 x i64], align 8
  %ptls_field = getelementptr inbounds nuw i8, ptr %pgcstack_arg, i64 16
  %ptls_load = load ptr, ptr %ptls_field, align 8, !tbaa !25
  %0 = getelementptr inbounds nuw i8, ptr %ptls_load, i64 16
  %safepoint = load atomic ptr, ptr %0 monotonic, align 8, !tbaa !29, !invariant.load !0
  fence syncscope("singlethread") seq_cst
  %1 = load volatile i64, ptr %safepoint, align 8
  fence syncscope("singlethread") seq_cst
    #dbg_declare(ptr %"A::Array", !19, !DIExpression(), !31)
    #dbg_value(i64 %"i1::Int64", !20, !DIExpression(), !31)
    #dbg_value(i64 %"i2::Int64", !21, !DIExpression(), !31)
    #dbg_value(i64 %"I[1]::Int64", !22, !DIExpression(DW_OP_LLVM_fragment, 0, 64), !32)
    #dbg_value(i64 %"I[2]::Int64", !22, !DIExpression(DW_OP_LLVM_fragment, 64, 64), !32)
    #dbg_value(i64 %"I[3]::Int64", !22, !DIExpression(DW_OP_LLVM_fragment, 128, 64), !32)
;  @ array.jl:962 within `getindex`
  store i64 %"i1::Int64", ptr %"new::Tuple1", align 8, !dbg !33
  %2 = getelementptr inbounds nuw i8, ptr %"new::Tuple1", i64 8, !dbg !33
  store i64 %"i2::Int64", ptr %2, align 8, !dbg !33
  %3 = getelementptr inbounds nuw i8, ptr %"new::Tuple1", i64 16, !dbg !33
  store i64 %"I[1]::Int64", ptr %3, align 8, !dbg !33, !tbaa !34, !alias.scope !36, !noalias !39
  %4 = getelementptr inbounds nuw i8, ptr %"new::Tuple1", i64 24, !dbg !33
  store i64 %"I[2]::Int64", ptr %4, align 8, !dbg !33, !tbaa !34, !alias.scope !36, !noalias !39
  %5 = getelementptr inbounds nuw i8, ptr %"new::Tuple1", i64 32, !dbg !33
  store i64 %"I[3]::Int64", ptr %5, align 8, !dbg !33, !tbaa !34, !alias.scope !36, !noalias !39

Adds a new LLVM pass that sinks instructions to paths where they are actually needed. This is useful for Julia's bounds checking where error message data is computed eagerly but only needed when bounds checks fail.

The pass uses MemorySSA to track memory dependencies and verify that sinking writes doesn't change observable behavior. It handles:

  • Sinking to single-predecessor successors (D136218-style)
  • Sinking to noreturn/error blocks
  • Memory intrinsics (memset, memcpy, memmove)
  • Pointer captures in dominated blocks
  • Loop boundaries (won't sink into loops)

All transformations validated with alive-tv.

@gbaraldi gbaraldi force-pushed the gb/newsink branch 3 times, most recently from 3699514 to 543d575 Compare January 30, 2026 02:02
Comment on lines +63 to +85
entry:
%loc = alloca i64, align 8
store i64 1, ptr %loc, align 8
br i1 %c.1, label %if.then, label %if.end

if.then:
call void @throw(ptr %loc)
unreachable

if.end:
store i64 0, ptr %loc, align 8
ret void
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing the actuall checks? Also in other tests.

@KristofferC KristofferC added the needs pkgeval Tests for all registered packages should be run with this change label Jan 30, 2026
Adds a new LLVM pass that sinks instructions to paths where they are
actually needed. This is useful for Julia's bounds checking where error
message data is computed eagerly but only needed when bounds checks fail.

The pass uses MemorySSA to track memory dependencies and verify that
sinking writes doesn't change observable behavior. It handles:

- Sinking to single-predecessor successors (D136218-style)
- Sinking to noreturn/error blocks
- Memory intrinsics (memset, memcpy, memmove)
- Pointer captures in dominated blocks

Loop handling uses LoopInfo and PostDominatorTree:
- Won't sink into a different loop
- In loops, requires target to post-dominate source (ensures all
  iterations execute the store, avoiding stale values)
- Noreturn targets exempt from loop checks (no future iterations)

This addresses the limitation that MemorySSA doesn't model cross-iteration
effects in loops.

All transformations validated with alive-tv.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@giordano giordano added the compiler:codegen Generation of LLVM IR and native code label Jan 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

compiler:codegen Generation of LLVM IR and native code needs pkgeval Tests for all registered packages should be run with this change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants