llvmlite icon indicating copy to clipboard operation
llvmlite copied to clipboard

Split context manager for if-then into two new blocks

Open Zelatrix opened this issue 3 years ago • 7 comments

I am trying to use LLVM lite to implement a while loop construct. I know the IR for this, but I am having trouble implementing this in Python. I believe that the reason for this is because the current implementation of the if-then context manager contains the following basic blocks:

entry:
    instructions
entry.if:
    instrs
entry.endif
    instrs

I believe that in order to be able to use this context manager, unless there is another way to do it with the current implementation, it should have the entry block split in two, like so:

entry:
    instructions
entry.cond:
    instrs
entry.if:
    instrs
entry.endif
    instrs

This way, users would be able to jump specifically back into the conditional basic block and avoid reinitialising any variables defined outside of the if statement, allowing for code such as:

int main() {
    int x = 10;
    START: if(x > 0) {
    printf("%d\n", x);
    x -= 1;
    goto START;
  }
}

Zelatrix avatar Jul 04 '21 18:07 Zelatrix

Could you implement this by adding a basic block to the current function with append_basic_block, then using the if-then context manager in that new block?

gmarkall avatar Jul 05 '21 09:07 gmarkall

How would I tell LLVM to add the instructions for initializing the variables to the entry block and to add the condition to the conditional block? Because presumably that would change between programs? This would be the kind of thing I'm after:

entry:
  %".2" = bitcast [6 x i8]* @"fstr" to i64*
  %".3" = alloca double
  store double 0x4024000000000000, double* %".3"
  br label %"cond"

cond:
  %".5" = load double, double* %".3"
  %".6" = fcmp ogt double %".5",              0x0
  %".7" = uitofp i1 %".6" to double
  %".8" = fptosi double %".7" to i1
  br i1 %".8", label %"entry.if", label %"entry.endif"

entry.if:
  %".10" = load double, double* %".3"
  %".11" = call i32 (i64*, ...) @"printf"(i64* %".2", double %".10")
  %".12" = load double, double* %".3"
  %".13" = fsub double %".12", 0x3ff0000000000000
  store double %".13", double* %".3"
  br label %"cond"
entry.endif:
  ret void
}

Zelatrix avatar Jul 05 '21 10:07 Zelatrix

The following:

from llvmlite import ir


# Create a module, function, basic block, and builder on the block
mod = ir.Module()
fnty = ir.FunctionType(ir.VoidType(), [])
fn = ir.Function(mod, fnty, 'fn')
block = fn.append_basic_block('init')
builder = ir.IRBuilder(block)

# Initialize a variable
z = ir.Constant(ir.IntType(1), 0)
a = builder.add(z, z, 'a')

# Create a new block with an if-then
it_block = block.function.append_basic_block('it')
it_builder = ir.IRBuilder(it_block)
with it_builder.if_then(a) as bbend:
    it_builder.add(z, z, 'b')
    it_builder.branch(it_block)

# The initialization block should branch to the if-then block
builder.branch(it_block)

print(fn)

produces:

define void @"fn"() 
{
init:
  %"a" = add i1 0, 0
  br label %"it"
it:
  br i1 %"a", label %"it.if", label %"it.endif"
it.if:
  %"b" = add i1 0, 0
  br label %"it"
it.endif:
}

The function itself is as bit nonsensical, but does this illustrate how to get toward the block structure / control flow you're aiming for?

gmarkall avatar Jul 05 '21 11:07 gmarkall

I kind of have it, except that what should be in the entry basic block isn't in a basic block and is instead stored inside self.builder.block. My original plan was to loop over the basic block and add the instructions to the entry block, but I'm not sure how to do that dynamically without hardcoding the instructions:

# This is the result of printing out self.builder.block
%"entry" = entry:
  %".2" = bitcast [6 x i8]* @"fstr" to i64*
  %".3" = alloca double
  store double 0x4024000000000000, double* %".3"

# This is the main function with the entry block in it
define i32 @"main_fn"()
{
entry:
  br label %"entry.cond"
entry.cond:
  %".3" = load double, double* %".3"
  %".4" = fcmp ogt double %".3",              0x0
  %".5" = uitofp i1 %".4" to double
  %".6" = fptosi double %".5" to i1
  br i1 %".6", label %"entry.if", label %"entry.endif"
entry.if:
  %".8" = load double, double* %".3"
  %".9" = call i32 (i64*, ...) @"printf"(i64* %".2", double %".8")
  %".10" = load double, double* %".3"
  %".11" = fsub double %".10", 0x3ff0000000000000
  store double %".11", double* %".3"
  br label %"entry.cond"
entry.endif:
  ret void
}

Zelatrix avatar Jul 05 '21 14:07 Zelatrix

Can you share the code you have that produced that? (or a small reproducer that produces something with a similar problem) - I'm finding it a bit hard to see what to suggest from just the output.

gmarkall avatar Jul 05 '21 20:07 gmarkall

The code I wrote that produces the above is at this link:

https://pastebin.com/Bmynpy66

I am also having some trouble understanding and getting the getelementptr instruction to work, as I'm not entirely sure what it needs to be given, but I think I should make a separate post for that.

Zelatrix avatar Jul 05 '21 20:07 Zelatrix

Been having a similar issue, the following code reproduces it:

builder = ir.IRBuilder()
module = ir.Module(name=__file__)
integer = ir.IntType(64)

fcopy = ir.FunctionType(ir.PointerType(integer), [ir.PointerType(integer), ir.PointerType(integer), integer])
copy = ir.Function(module, fcopy, name="copy")
copy_entry = copy.append_basic_block("copy_entry")
builder.position_at_end(copy_entry)

src, dst, cur_list_len = copy.args
src = builder.bitcast(src, ir.PointerType(integer))
dst = builder.bitcast(dst, ir.PointerType(integer))

cnt = integer(0)
cnt_ptr = builder.alloca(integer)
builder.store(cnt, cnt_ptr)


copy_loop = builder.append_basic_block("copy_loop")
builder.position_at_end(copy_loop)

with builder.if_then(
    builder.icmp_signed("<", builder.load(cnt_ptr), cur_list_len),
) as then:
    cnt = builder.load(cnt_ptr)    
    builder.store(  # Increment ptrs and copy from src to dst
        builder.load(builder.inttoptr(builder.add(builder.ptrtoint(src, integer), cnt), ir.PointerType(integer))),
        builder.inttoptr(builder.add(builder.ptrtoint(dst, integer), cnt), ir.PointerType(integer))
    )
    builder.store(builder.add(cnt, integer(1)), cnt_ptr)
    builder.branch(copy_loop)

builder.ret(dst)

MaxTurchin avatar Oct 03 '22 12:10 MaxTurchin