Prevent LLVM optimizations from erasing unused basic blocks

Hello LLVM Devs.

In my compiler I attach some arbitrary data to functions by creating BBs with inline assembly. However, these blocks are “unused” from LLVM point of view and get erased from the function.

To counter that I started adding checks for conditions that are guaranteed to be true or false. I ended up with calling @llvm.returnaddress(i32 0) intrinsic and comparing the result with 0. It worked well until in one function I had two such calls and SROA replaced one of checks with constant 1 and erased the BB.

I should probably stop trying to fool LLVM and “do it right”, but don’t have any idea how. Note that I can’t use global variables for a reason, so the data has to be encoded in a BB using inline assembly. All I need is just prevent optimizations from erasing it.

Thanks in advance.

A reachable inline asm won't be erased if LLVM thinks it has some side-effect. The simplest way to do this is the "sideeffect" marking (in C++, it's a parameter to InlineAsm::get()). See http://llvm.org/docs/LangRef.html#inline-assembler-expressions .

-Eli

Hello LLVM Devs.

In my compiler I attach some arbitrary data to functions by creating
BBs with inline assembly. However, these blocks are “unused” from LLVM
point of view and get erased from the function.

To counter that I started adding checks for conditions that are
guaranteed to be true or false. I ended up with calling
@llvm.returnaddress(i32 0) intrinsic and comparing the result with 0.
It worked well until in one function I had two such calls and SROA
replaced one of checks with constant 1 and erased the BB.

I should probably stop trying to fool LLVM and “do it right”, but
don’t have any idea how. Note that I can’t use global variables for a
reason, so the data has to be encoded in a BB using inline assembly.
All I need is just prevent optimizations from erasing it.

A reachable inline asm won’t be erased if LLVM thinks it has some
side-effect. The simplest way to do this is the “sideeffect” marking
(in C++, it’s a parameter to InlineAsm::get()). See
http://llvm.org/docs/LangRef.html#inline-assembler-expressions .

The problem is exactly reachability. Here is a simple example:

define void @foo() {
entry:

ret void
data:
call void asm sideeffect inteldialect “.byte 0xB2”, “~{dirflag},~{fpsr},~{flags}”()
call void asm sideeffect inteldialect “.byte 0xB9”, “~{dirflag},~{fpsr},~{flags}”()

}

To make “data” reachable I change entry’s terminator to br %tobool, label %exit, label %data, where %tobool is a result of icmp eq that is always true. However, I can’t come up with such a condition that didn’t get erased by SROA.

Hi Gleb,

Even if you manage to trick LLVM into emitting the inline asm, it won’t be in a predictable location in the emitted assembly; some LLVM transforms will rearrange the code in a function. Please take a step back and explain what you’re trying to do; there’s probably a better approach. -Eli

Hello LLVM Devs.

In my compiler I attach some arbitrary data to functions by creating
BBs with inline assembly. However, these blocks are “unused” from LLVM
point of view and get erased from the function.

To counter that I started adding checks for conditions that are
guaranteed to be true or false. I ended up with calling
@llvm.returnaddress(i32 0) intrinsic and comparing the result with 0.
It worked well until in one function I had two such calls and SROA
replaced one of checks with constant 1 and erased the BB.

I should probably stop trying to fool LLVM and “do it right”, but
don’t have any idea how. Note that I can’t use global variables for a
reason, so the data has to be encoded in a BB using inline assembly.
All I need is just prevent optimizations from erasing it.

A reachable inline asm won’t be erased if LLVM thinks it has some
side-effect. The simplest way to do this is the “sideeffect” marking
(in C++, it’s a parameter to InlineAsm::get()). See
http://llvm.org/docs/LangRef.html#inline-assembler-expressions .

The problem is exactly reachability. Here is a simple example:

define void @foo() {
entry:

ret void
data:
call void asm sideeffect inteldialect “.byte 0xB2”, “~{dirflag},~{fpsr},~{flags}”()
call void asm sideeffect inteldialect “.byte 0xB9”, “~{dirflag},~{fpsr},~{flags}”()

}

To make “data” reachable I change entry’s terminator to br %tobool, label %exit, label %data, where %tobool is a result of icmp eq that is always true. However, I can’t come up with such a condition that didn’t get erased by SROA.

Even if you manage to trick LLVM into emitting the inline asm, it won’t be in a predictable location in the emitted assembly; some LLVM transforms will rearrange the code in a function.

Won’t @llvm.returnaddress() always get me correct location of my inline asm block?

I’m very confused… how could you possibly use @llvm.returnaddress to return the address of a block of code that’s never executed? -Eli

Why would it? That's the address of the callee and there's no reason
it should correlate with anything whatsoever in the current function.
Before now it sounded like you were using that as some way to trick
LLVM into keeping your block around, which would have no effect on its
position.

Cheers.

Tim.

Hello LLVM Devs.

In my compiler I attach some arbitrary data to functions by creating
BBs with inline assembly. However, these blocks are “unused” from LLVM
point of view and get erased from the function.

To counter that I started adding checks for conditions that are
guaranteed to be true or false. I ended up with calling
@llvm.returnaddress(i32 0) intrinsic and comparing the result with 0.
It worked well until in one function I had two such calls and SROA
replaced one of checks with constant 1 and erased the BB.

I should probably stop trying to fool LLVM and “do it right”, but
don’t have any idea how. Note that I can’t use global variables for a
reason, so the data has to be encoded in a BB using inline assembly.
All I need is just prevent optimizations from erasing it.

A reachable inline asm won’t be erased if LLVM thinks it has some
side-effect. The simplest way to do this is the “sideeffect” marking
(in C++, it’s a parameter to InlineAsm::get()). See
http://llvm.org/docs/LangRef.html#inline-assembler-expressions .

The problem is exactly reachability. Here is a simple example:

define void @foo() {
entry:

ret void
data:
call void asm sideeffect inteldialect “.byte 0xB2”, “~{dirflag},~{fpsr},~{flags}”()
call void asm sideeffect inteldialect “.byte 0xB9”, “~{dirflag},~{fpsr},~{flags}”()

}

To make “data” reachable I change entry’s terminator to br %tobool, label %exit, label %data, where %tobool is a result of icmp eq that is always true. However, I can’t come up with such a condition that didn’t get erased by SROA.

Even if you manage to trick LLVM into emitting the inline asm, it won’t be in a predictable location in the emitted assembly; some LLVM transforms will rearrange the code in a function.

Won’t @llvm.returnaddress() always get me correct location of my inline asm block?

I’m very confused… how could you possibly use @llvm.returnaddress to return the address of a block of code that’s never executed?

Sorry, it is a typo. I meant blockaddress constant, not @llvm.returnaddress intrinsic.

Won’t @llvm.returnaddress() always get me correct location of my inline asm block?

Why would it? That’s the address of the callee and there’s no reason
it should correlate with anything whatsoever in the current function.

Right, I just confused it with blockaddress constant.

Before now it sounded like you were using that as some way to trick
LLVM into keeping your block around, which would have no effect on its
position.

That’s correct, I used returnaddress to trick optimizer and blockaddress to actually get a pointer to my data inside the function. Sorry for mixing it up.

Can you elaborate on why you’ve discarded this idea? There are tons of ways to put metadata in a global and then associate it with the function. The inline assembly snippets you pasted suggest that the contents are just data in some non-text section, they don’t use any values from the function. What you’re doing sounds a lot like coverage mapping data, instrprof data, asan global metadata, or exception handling data. Those features all have different ways to put stuff in global variables on the side and get them emitted into different sections as appropriate.

Oh, that makes more sense. Still, there isn’t any rule that prohibits LLVM from inserting code there; you’re basically just hoping to get lucky. (blockaddress is designed to be used with indirectbr; any other use is probably wrong.) And given you’re explicitly storing the address of the data anyway, I’m not sure what you’re trying to accomplish by embedding the data using inline asm; there are simpler ways to ensure your data is placed in the same section. -Eli