Blockaddress referencing block in different function

Hi,

I want to clarify the semantics of blockaddress, especially when multiple functions are involved.

Can blockaddress reference a block in a different function?
The syntax implies that it can, otherwise the first parameter, the function, does not make much sense. We only take the address of the block, not jump to it. indirectbr from one function to another is UB.

Is it allowed to transfer the return value of blockaddress through memory?
Storing it to memory in one function and loading it in the function of the referenced block to call indirectbr. The documentation mentions that the value β€œmay be passed around as an opaque pointer sized value as long as the bits are not inspected.”

Example:

define void @foo(i1 %cond, ptr %jumpAddr){
  br i1 %cond, label %bb_true, label %bb_false

bb_true:
  store ptr blockaddress(@bar, %dest_true), ptr %jumpAddr, align 8
  ret void

bb_false:
  store ptr blockaddress(@bar, %dest_false), ptr %jumpAddr, align 8
  ret void
}

define void @bar(i1 %cond){
  %jumpAddr = alloca ptr, align 8
  call void @foo(i1 %cond, ptr %jumpAddr)
  %jumpAddrLoaded = load ptr, ptr %jumpAddr, align 8
  indirectbr ptr %jumpAddrLoaded, [label %dest_true, label %dest_false]

dest_true:
  ; do something
  br label %exit

dest_false:
  ; do some other things
  br label %exit

exit:
  ret void
}

Best regards,
Frank

I think the short answer to both question is β€œyes”. You can reference block addresses across functions, you can store them, return them, do whatever you like with them.

The main complications that blockaddress and indirectbr introduce have to do with code duplication. LLVM cannot duplicate indirectbr, even for inlining, since the existing block address references will not point to the newly created basic blocks, and everything you might imagine could go wrong.

Thank you for the answer.
Do you think an integer return value and a switch instruction in the outer function is a better option than returning a blockaddress value and jumping with indirectbr? Is there a preferred pattern?

I can understand that taking the address of a block may inhibit other optimizations but going through a jump table feels like an additional step (probably negligible).

define void @foo(i1 %cond, ptr %jumpValue){
  br i1 %cond, label %bb_true, label %bb_false

bb_true:
  store i32 0, ptr %jumpValue, align 8
  ret void

bb_false:
  store i32 1, ptr %jumpValue, align 8
  ret void
}

define void @bar(i1 %cond){
  %jumpValue = alloca i32, align 8
  call void @foo(i1 %cond, ptr %jumpValue)
  %jumpValueLoaded = load i32, ptr %jumpValue, align 8
  switch i32 %jumpValueLoaded, label %exit [ i32 0, label %dest_true
                                             i32 1, label %dest_false]

dest_true:
  ; do something
  br label %exit

dest_false:
  ; do some other things
  br label %exit

exit:
  ret void
}