Skip to content

Commit

Permalink
codegen: don't rely on LLVM unrolling our loops
Browse files Browse the repository at this point in the history
LLVM doesn't always unroll loops, and since the BPF verifier doesn't
allow loops, this can lead to invalid BPF code. Instead of relying on
LLVM unrolling loops, we unroll them ourselves.

Fixes: #632
  • Loading branch information
mmarchini authored and danobi committed Jun 6, 2019
1 parent 13c2e2e commit 702145c
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 142 deletions.
28 changes: 5 additions & 23 deletions src/ast/codegen_llvm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1190,30 +1190,12 @@ void CodegenLLVM::visit(If &if_block)

void CodegenLLVM::visit(Unroll &unroll)
{
Function *parent = b_.GetInsertBlock()->getParent();
BasicBlock *loop = BasicBlock::Create(module_->getContext(), "loop", parent);
BasicBlock *done = BasicBlock::Create(module_->getContext(), "done", parent);

AllocaInst *val = b_.CreateAllocaBPF(SizedType(Type::integer, 8), "loop_count");
b_.CreateStore(b_.getInt64(unroll.var), val);

b_.CreateCondBr(b_.CreateICmpNE(b_.getInt64(unroll.var), b_.getInt64(0), "true_cond"), loop, done);

b_.SetInsertPoint(loop);

for (Statement *stmt : *unroll.stmts)
{
stmt->accept(*this);
for (int i=0; i < unroll.var; i++) {
for (Statement *stmt : *unroll.stmts)
{
stmt->accept(*this);
}
}

Value *var = b_.CreateLoad(val);
Value *newValue = b_.CreateSub(var, b_.getInt64(1), "subtmp");
b_.CreateStore(newValue, val);

b_.CreateCondBr(b_.CreateICmpNE(newValue, b_.getInt64(0), "loop_cond"), loop, done);

b_.SetInsertPoint(done);
b_.CreateLifetimeEnd(val);
}

void CodegenLLVM::visit(Predicate &pred)
Expand Down
10 changes: 6 additions & 4 deletions src/ast/semantic_analyser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -761,14 +761,16 @@ void SemanticAnalyser::visit(Unroll &unroll)
{
err_ << "unroll maximum value is 20.\n" << std::endl;
}
else if (unroll.var == 0)
else if (unroll.var < 1)
{
err_ << "unroll minimum value is 1.\n" << std::endl;
}

for (Statement *stmt : *unroll.stmts)
{
stmt->accept(*this);
for (int i=0; i < unroll.var; i++) {
for (Statement *stmt : *unroll.stmts)
{
stmt->accept(*this);
}
}
}

Expand Down
273 changes: 158 additions & 115 deletions tests/codegen/unroll.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,139 +6,182 @@ namespace codegen {

TEST(codegen, unroll)
{
test("kprobe:f { $i = 0; unroll(5) { printf(\"i: %d\\n\", $i); $i = $i + 1; } }",
test("BEGIN { @i = 0; unroll(5) { @i++ } }",

#if LLVM_VERSION_MAJOR > 6
R"EXPECTED(%printf_t = type { i64, i64 }
; Function Attrs: nounwind
R"EXPECTED(; Function Attrs: nounwind
declare i64 @llvm.bpf.pseudo(i64, i64) #0
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1
define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" {
define i64 @BEGIN(i8* nocapture readnone) local_unnamed_addr section "s_BEGIN_1" {
entry:
%printf_args = alloca %printf_t, align 8
%1 = bitcast %printf_t* %printf_args to i8*
%2 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 1
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
%3 = bitcast %printf_t* %printf_args to i8*
call void @llvm.memset.p0i8.i64(i8* nonnull align 8 %3, i8 0, i64 16, i1 false)
%pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%get_cpu_id = call i64 inttoptr (i64 8 to i64 ()*)()
%perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i8*, i64, i8*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, %printf_t* nonnull %printf_args, i64 16)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
%4 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 0
store i64 0, i64* %4, align 8
store i64 1, i64* %2, align 8
%pseudo.1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%get_cpu_id.1 = call i64 inttoptr (i64 8 to i64 ()*)()
%perf_event_output.1 = call i64 inttoptr (i64 25 to i64 (i8*, i8*, i64, i8*, i64)*)(i8* %0, i64 %pseudo.1, i64 %get_cpu_id.1, %printf_t* nonnull %printf_args, i64 16)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
%"@i_val52" = alloca i64, align 8
%"@i_key51" = alloca i64, align 8
%"@i_key43" = alloca i64, align 8
%"@i_val40" = alloca i64, align 8
%"@i_key39" = alloca i64, align 8
%"@i_key31" = alloca i64, align 8
%"@i_val28" = alloca i64, align 8
%"@i_key27" = alloca i64, align 8
%"@i_key19" = alloca i64, align 8
%"@i_val16" = alloca i64, align 8
%"@i_key15" = alloca i64, align 8
%"@i_key7" = alloca i64, align 8
%"@i_val4" = alloca i64, align 8
%"@i_key3" = alloca i64, align 8
%"@i_key1" = alloca i64, align 8
%"@i_val" = alloca i64, align 8
%"@i_key" = alloca i64, align 8
%1 = bitcast i64* %"@i_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
%5 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 0
store i64 0, i64* %5, align 8
store i64 2, i64* %2, align 8
%pseudo.2 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%get_cpu_id.2 = call i64 inttoptr (i64 8 to i64 ()*)()
%perf_event_output.2 = call i64 inttoptr (i64 25 to i64 (i8*, i8*, i64, i8*, i64)*)(i8* %0, i64 %pseudo.2, i64 %get_cpu_id.2, %printf_t* nonnull %printf_args, i64 16)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
%6 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 0
store i64 0, i64* %6, align 8
store i64 3, i64* %2, align 8
%pseudo.3 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%get_cpu_id.3 = call i64 inttoptr (i64 8 to i64 ()*)()
%perf_event_output.3 = call i64 inttoptr (i64 25 to i64 (i8*, i8*, i64, i8*, i64)*)(i8* %0, i64 %pseudo.3, i64 %get_cpu_id.3, %printf_t* nonnull %printf_args, i64 16)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
%7 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 0
store i64 0, i64* %7, align 8
store i64 4, i64* %2, align 8
%pseudo.4 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%get_cpu_id.4 = call i64 inttoptr (i64 8 to i64 ()*)()
%perf_event_output.4 = call i64 inttoptr (i64 25 to i64 (i8*, i8*, i64, i8*, i64)*)(i8* %0, i64 %pseudo.4, i64 %get_cpu_id.4, %printf_t* nonnull %printf_args, i64 16)
store i64 0, i64* %"@i_key", align 8
%2 = bitcast i64* %"@i_val" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2)
store i64 0, i64* %"@i_val", align 8
%pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%update_elem = call i64 inttoptr (i64 2 to i64 (i8*, i8*, i8*, i64)*)(i64 %pseudo, i64* nonnull %"@i_key", i64* nonnull %"@i_val", i64 0)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2)
%3 = bitcast i64* %"@i_key1" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3)
store i64 0, i64* %"@i_key1", align 8
%pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%lookup_elem = call i8* inttoptr (i64 1 to i8* (i8*, i8*)*)(i64 %pseudo2, i64* nonnull %"@i_key1")
%map_lookup_cond = icmp eq i8* %lookup_elem, null
br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success
lookup_success: ; preds = %entry
%4 = load i64, i8* %lookup_elem, align 8
%phitmp = add i64 %4, 1
br label %lookup_merge
lookup_merge: ; preds = %entry, %lookup_success
%lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ]
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3)
%5 = bitcast i64* %"@i_key3" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5)
store i64 0, i64* %"@i_key3", align 8
%6 = bitcast i64* %"@i_val4" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6)
store i64 %lookup_elem_val.0, i64* %"@i_val4", align 8
%pseudo5 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%update_elem6 = call i64 inttoptr (i64 2 to i64 (i8*, i8*, i8*, i64)*)(i64 %pseudo5, i64* nonnull %"@i_key3", i64* nonnull %"@i_val4", i64 0)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6)
%7 = bitcast i64* %"@i_key7" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7)
store i64 0, i64* %"@i_key7", align 8
%pseudo8 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%lookup_elem9 = call i8* inttoptr (i64 1 to i8* (i8*, i8*)*)(i64 %pseudo8, i64* nonnull %"@i_key7")
%map_lookup_cond14 = icmp eq i8* %lookup_elem9, null
br i1 %map_lookup_cond14, label %lookup_merge12, label %lookup_success10
lookup_success10: ; preds = %lookup_merge
%8 = load i64, i8* %lookup_elem9, align 8
%phitmp55 = add i64 %8, 1
br label %lookup_merge12
lookup_merge12: ; preds = %lookup_merge, %lookup_success10
%lookup_elem_val13.0 = phi i64 [ %phitmp55, %lookup_success10 ], [ 1, %lookup_merge ]
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7)
%9 = bitcast i64* %"@i_key15" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %9)
store i64 0, i64* %"@i_key15", align 8
%10 = bitcast i64* %"@i_val16" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %10)
store i64 %lookup_elem_val13.0, i64* %"@i_val16", align 8
%pseudo17 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%update_elem18 = call i64 inttoptr (i64 2 to i64 (i8*, i8*, i8*, i64)*)(i64 %pseudo17, i64* nonnull %"@i_key15", i64* nonnull %"@i_val16", i64 0)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %9)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %10)
%11 = bitcast i64* %"@i_key19" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %11)
store i64 0, i64* %"@i_key19", align 8
%pseudo20 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%lookup_elem21 = call i8* inttoptr (i64 1 to i8* (i8*, i8*)*)(i64 %pseudo20, i64* nonnull %"@i_key19")
%map_lookup_cond26 = icmp eq i8* %lookup_elem21, null
br i1 %map_lookup_cond26, label %lookup_merge24, label %lookup_success22
lookup_success22: ; preds = %lookup_merge12
%12 = load i64, i8* %lookup_elem21, align 8
%phitmp56 = add i64 %12, 1
br label %lookup_merge24
lookup_merge24: ; preds = %lookup_merge12, %lookup_success22
%lookup_elem_val25.0 = phi i64 [ %phitmp56, %lookup_success22 ], [ 1, %lookup_merge12 ]
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %11)
%13 = bitcast i64* %"@i_key27" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %13)
store i64 0, i64* %"@i_key27", align 8
%14 = bitcast i64* %"@i_val28" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %14)
store i64 %lookup_elem_val25.0, i64* %"@i_val28", align 8
%pseudo29 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%update_elem30 = call i64 inttoptr (i64 2 to i64 (i8*, i8*, i8*, i64)*)(i64 %pseudo29, i64* nonnull %"@i_key27", i64* nonnull %"@i_val28", i64 0)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %13)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %14)
%15 = bitcast i64* %"@i_key31" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %15)
store i64 0, i64* %"@i_key31", align 8
%pseudo32 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%lookup_elem33 = call i8* inttoptr (i64 1 to i8* (i8*, i8*)*)(i64 %pseudo32, i64* nonnull %"@i_key31")
%map_lookup_cond38 = icmp eq i8* %lookup_elem33, null
br i1 %map_lookup_cond38, label %lookup_merge36, label %lookup_success34
lookup_success34: ; preds = %lookup_merge24
%16 = load i64, i8* %lookup_elem33, align 8
%phitmp57 = add i64 %16, 1
br label %lookup_merge36
lookup_merge36: ; preds = %lookup_merge24, %lookup_success34
%lookup_elem_val37.0 = phi i64 [ %phitmp57, %lookup_success34 ], [ 1, %lookup_merge24 ]
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %15)
%17 = bitcast i64* %"@i_key39" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %17)
store i64 0, i64* %"@i_key39", align 8
%18 = bitcast i64* %"@i_val40" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %18)
store i64 %lookup_elem_val37.0, i64* %"@i_val40", align 8
%pseudo41 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%update_elem42 = call i64 inttoptr (i64 2 to i64 (i8*, i8*, i8*, i64)*)(i64 %pseudo41, i64* nonnull %"@i_key39", i64* nonnull %"@i_val40", i64 0)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %17)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %18)
%19 = bitcast i64* %"@i_key43" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %19)
store i64 0, i64* %"@i_key43", align 8
%pseudo44 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%lookup_elem45 = call i8* inttoptr (i64 1 to i8* (i8*, i8*)*)(i64 %pseudo44, i64* nonnull %"@i_key43")
%map_lookup_cond50 = icmp eq i8* %lookup_elem45, null
br i1 %map_lookup_cond50, label %lookup_merge48, label %lookup_success46
lookup_success46: ; preds = %lookup_merge36
%20 = load i64, i8* %lookup_elem45, align 8
%phitmp58 = add i64 %20, 1
br label %lookup_merge48
lookup_merge48: ; preds = %lookup_merge36, %lookup_success46
%lookup_elem_val49.0 = phi i64 [ %phitmp58, %lookup_success46 ], [ 1, %lookup_merge36 ]
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %19)
%21 = bitcast i64* %"@i_key51" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %21)
store i64 0, i64* %"@i_key51", align 8
%22 = bitcast i64* %"@i_val52" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %22)
store i64 %lookup_elem_val49.0, i64* %"@i_val52", align 8
%pseudo53 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%update_elem54 = call i64 inttoptr (i64 2 to i64 (i8*, i8*, i8*, i64)*)(i64 %pseudo53, i64* nonnull %"@i_key51", i64* nonnull %"@i_val52", i64 0)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %21)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %22)
ret i64 0
}
; Function Attrs: argmemonly nounwind
declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1
attributes #0 = { nounwind }
attributes #1 = { argmemonly nounwind }
)EXPECTED");
#else
R"EXPECTED(%printf_t = type { i64, i64 }
; Function Attrs: nounwind
declare i64 @llvm.bpf.pseudo(i64, i64) #0
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1
define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" {
entry:
%printf_args = alloca %printf_t, align 8
%1 = bitcast %printf_t* %printf_args to i8*
%2 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 1
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
%3 = bitcast %printf_t* %printf_args to i8*
call void @llvm.memset.p0i8.i64(i8* nonnull %3, i8 0, i64 16, i32 8, i1 false)
%pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%get_cpu_id = call i64 inttoptr (i64 8 to i64 ()*)()
%perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i8*, i64, i8*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, %printf_t* nonnull %printf_args, i64 16)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
%4 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 0
store i64 0, i64* %4, align 8
store i64 1, i64* %2, align 8
%pseudo.1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%get_cpu_id.1 = call i64 inttoptr (i64 8 to i64 ()*)()
%perf_event_output.1 = call i64 inttoptr (i64 25 to i64 (i8*, i8*, i64, i8*, i64)*)(i8* %0, i64 %pseudo.1, i64 %get_cpu_id.1, %printf_t* nonnull %printf_args, i64 16)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
%5 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 0
store i64 0, i64* %5, align 8
store i64 2, i64* %2, align 8
%pseudo.2 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%get_cpu_id.2 = call i64 inttoptr (i64 8 to i64 ()*)()
%perf_event_output.2 = call i64 inttoptr (i64 25 to i64 (i8*, i8*, i64, i8*, i64)*)(i8* %0, i64 %pseudo.2, i64 %get_cpu_id.2, %printf_t* nonnull %printf_args, i64 16)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
%6 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 0
store i64 0, i64* %6, align 8
store i64 3, i64* %2, align 8
%pseudo.3 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%get_cpu_id.3 = call i64 inttoptr (i64 8 to i64 ()*)()
%perf_event_output.3 = call i64 inttoptr (i64 25 to i64 (i8*, i8*, i64, i8*, i64)*)(i8* %0, i64 %pseudo.3, i64 %get_cpu_id.3, %printf_t* nonnull %printf_args, i64 16)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
%7 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 0
store i64 0, i64* %7, align 8
store i64 4, i64* %2, align 8
%pseudo.4 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%get_cpu_id.4 = call i64 inttoptr (i64 8 to i64 ()*)()
%perf_event_output.4 = call i64 inttoptr (i64 25 to i64 (i8*, i8*, i64, i8*, i64)*)(i8* %0, i64 %pseudo.4, i64 %get_cpu_id.4, %printf_t* nonnull %printf_args, i64 16)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
ret i64 0
}
; Function Attrs: argmemonly nounwind
declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1
attributes #0 = { nounwind }
attributes #1 = { argmemonly nounwind }
)EXPECTED");
#endif
}

} // namespace codegen
Expand Down

0 comments on commit 702145c

Please sign in to comment.