Jit: use @llvm.lifetime.end to optimize away stores to globals used as temporaries

Hi all,

Is it allowed to use the @llvm.lifetime.end intrinsic to optimize away stores to global variables that are used as temporaries?

eg I want use the Jit engine to generate code for the following function ‘f’:

struct State {

int a;

int tmp;

int b;

};

void f0(State* s) { s->tmp = s->a; }

void f1(State* s) { s->b = s->tmp; }

void f(State* s)

{

f0(s);

f1(s);

}

The Jit engine generates the following code:

define void @_Z1fP5State(%struct.State* %s) #1 {

%1 = getelementptr inbounds %struct.State, %struct.State* %s, i64 0, i32 0

%2 = load i32, i32* %1, align 4, !tbaa !1

%3 = getelementptr inbounds %struct.State, %struct.State* %s, i64 0, i32 1

store i32 %2, i32* %3, align 4, !tbaa !6

%4 = getelementptr inbounds %struct.State, %struct.State* %s, i64 0, i32 2

store i32 %2, i32* %4, align 4, !tbaa !7

ret void

}

Which gives (on x86_64):

_Z1fP5State:

movl (%rdi), %eax

movl %eax, 4(%rdi)

movl %eax, 8(%rdi)

retq

The ‘tmp’ state variable is only needed during execution of the function ‘f’.

After the function has finished to value can be discarded.

How can I tell the optimizer to optimize away to stores to the ‘tmp’ variable?

Can I use the @llvm.lifetime.end intrinsic to do this?

Eg If I change the function ‘f’ as follows:

extern “C” void llvm_lifetime_end(unsigned long long, void*);

void cleanup(State* s)

{

// llvm_lifetime_end will be replaced with @llvm.lifetime.end

llvm_lifetime_end(sizeof(s->tmp), (void*)&s->tmp);

}

void f(State* s)

{

f0(s);

f1(s);

cleanup(s);

}

And then use a pass to replace the function llvm_lifetime_end with the intrinsic @llvm.lifetime.end.

I get the following code:

define void @_Z1fP5State(%struct.State* nocapture %s) #0 {

%1 = getelementptr inbounds %struct.State, %struct.State* %s, i64 0, i32 0

%2 = load i32, i32* %1, align 4, !tbaa !1

%3 = getelementptr inbounds %struct.State, %struct.State* %s, i64 0, i32 1

%4 = getelementptr inbounds %struct.State, %struct.State* %s, i64 0, i32 2

store i32 %2, i32* %4, align 4, !tbaa !7

%5 = bitcast i32* %3 to i8*

tail call void @llvm.lifetime.end(i64 4, i8* %5)

ret void

}

_Z1fP5State:

movl (%rdi), %eax

movl %eax, 8(%rdi)

retq

Is this a supported usecase of the @llvm.lifetime.end intrinsic? ie is it allowed to use the intrinsic on a global variable instead of a local variable?

Or is there a better way to get the same result?

Is it ok to only have an @llvm.lifetime.end intrinsic, without a matching @llvm.lifetime.start intrinisic?

If not is there another way to tell the optimizer to optimize away the stores to the ‘tmp’ state variable?

Thanks,

Tom

That's what you say, because you called it 'tmp' :), but the compiler sees no difference to 'a' or 'b'. As soon as s->tmp would "escape" f() the compiler will have to write it.

I think you may want to keep external state and temporal context separate

e.g.

struct State {
   int a;
   int b;
}

struct Temp {
   int tmp;
}

static void f0( State *s, Temp *t)
{
   t->tmp = s->a;
}

...

void f(State* s)

{
    Temp tmp;

    f0( s, &tmp);
    f1( s, &tmp);
}

Using @llvm.lifetime.end is IMO a deadend, since this should just signify that the alloca is gone.

Ciao
    Nat!

From: Nat! [mailto:nat@mulle-kybernetik.com]
> Hi all,
>
> The 'tmp' state variable is only needed during execution of the function
> 'f'.
>
That's what you say, because you called it 'tmp' :), but the compiler
sees no difference to 'a' or 'b'. As soon as s->tmp would "escape" f()
the compiler will have to write it.

I think you may want to keep external state and temporal context separate

e.g.

struct State {
   int a;
   int b;
}

struct Temp {
   int tmp;
}

static void f0( State *s, Temp *t)
{
   t->tmp = s->a;
}

...

void f(State* s)

{
    Temp tmp;

    f0( s, &tmp);
    f1( s, &tmp);
}

Using @llvm.lifetime.end is IMO a deadend, since this should just
signify that the alloca is gone.

In the real code, the state variables ('tmp',...) do not have trivial constructors. Constructing them as local variables in the function 'f' adds a lot of extra code that may not always be optimized away.
It will also require a lot of change to existing code.

Using the @llvm.lifetime.end intrinsic seems to do exactly what we want to achieve, ie optimize away Store instructions to the 'tmp' variables with no/minimal changes to existing code (only the runtime generated functions 'f' have to be changed to call the @llvm.lifetime.end intrinsics).
Is using the intrinsic on something else as an Alloca allowed? Or is the behavior I see just a side effect of the current implementation and may it break in the future?

Regards,
Tom