672 lines
21 KiB
MLIR
672 lines
21 KiB
MLIR
|
// RUN: mlir-opt %s -inline -split-input-file | FileCheck %s
|
||
|
|
||
|
#file = #llvm.di_file<"foo.mlir" in "/foo/">
|
||
|
#variable = #llvm.di_local_variable<scope = #file>
|
||
|
#variableAddr = #llvm.di_local_variable<scope = #file>
|
||
|
#label = #llvm.di_label<scope = #file>
|
||
|
|
||
|
func.func @inner_func_inlinable(%ptr : !llvm.ptr) -> i32 {
|
||
|
%0 = llvm.mlir.constant(42 : i32) : i32
|
||
|
%stack = llvm.intr.stacksave : !llvm.ptr
|
||
|
llvm.store %0, %ptr { alignment = 8 } : i32, !llvm.ptr
|
||
|
%1 = llvm.load %ptr { alignment = 8 } : !llvm.ptr -> i32
|
||
|
llvm.intr.dbg.value #variable = %0 : i32
|
||
|
llvm.intr.dbg.declare #variableAddr = %ptr : !llvm.ptr
|
||
|
llvm.intr.dbg.label #label
|
||
|
%byte = llvm.mlir.constant(43 : i8) : i8
|
||
|
%true = llvm.mlir.constant(1 : i1) : i1
|
||
|
"llvm.intr.memset"(%ptr, %byte, %0) <{isVolatile = true}> : (!llvm.ptr, i8, i32) -> ()
|
||
|
"llvm.intr.memmove"(%ptr, %ptr, %0) <{isVolatile = true}> : (!llvm.ptr, !llvm.ptr, i32) -> ()
|
||
|
"llvm.intr.memcpy"(%ptr, %ptr, %0) <{isVolatile = true}> : (!llvm.ptr, !llvm.ptr, i32) -> ()
|
||
|
"llvm.intr.assume"(%true) : (i1) -> ()
|
||
|
llvm.fence release
|
||
|
%2 = llvm.atomicrmw add %ptr, %0 monotonic : !llvm.ptr, i32
|
||
|
%3 = llvm.cmpxchg %ptr, %0, %1 acq_rel monotonic : !llvm.ptr, i32
|
||
|
llvm.inline_asm has_side_effects "foo", "bar" : () -> ()
|
||
|
llvm.cond_br %true, ^bb1, ^bb2
|
||
|
^bb1:
|
||
|
llvm.unreachable
|
||
|
^bb2:
|
||
|
llvm.intr.stackrestore %stack : !llvm.ptr
|
||
|
llvm.call_intrinsic "llvm.x86.sse41.round.ss"() : () -> (vector<8xf32>)
|
||
|
return %1 : i32
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: func.func @test_inline(
|
||
|
// CHECK-SAME: %[[PTR:[a-zA-Z0-9_]+]]
|
||
|
// CHECK: %[[CST:.*]] = llvm.mlir.constant(42
|
||
|
// CHECK: %[[STACK:.+]] = llvm.intr.stacksave
|
||
|
// CHECK: llvm.store %[[CST]], %[[PTR]]
|
||
|
// CHECK: %[[RES:.+]] = llvm.load %[[PTR]]
|
||
|
// CHECK: llvm.intr.dbg.value #{{.+}} = %[[CST]]
|
||
|
// CHECK: llvm.intr.dbg.declare #{{.+}} = %[[PTR]]
|
||
|
// CHECK: llvm.intr.dbg.label #{{.+}}
|
||
|
// CHECK: "llvm.intr.memset"(%[[PTR]]
|
||
|
// CHECK: "llvm.intr.memmove"(%[[PTR]], %[[PTR]]
|
||
|
// CHECK: "llvm.intr.memcpy"(%[[PTR]], %[[PTR]]
|
||
|
// CHECK: "llvm.intr.assume"
|
||
|
// CHECK: llvm.fence release
|
||
|
// CHECK: llvm.atomicrmw add %[[PTR]], %[[CST]] monotonic
|
||
|
// CHECK: llvm.cmpxchg %[[PTR]], %[[CST]], %[[RES]] acq_rel monotonic
|
||
|
// CHECK: llvm.inline_asm has_side_effects "foo", "bar"
|
||
|
// CHECK: llvm.unreachable
|
||
|
// CHECK: llvm.intr.stackrestore %[[STACK]]
|
||
|
// CHECK: llvm.call_intrinsic "llvm.x86.sse41.round.ss"(
|
||
|
func.func @test_inline(%ptr : !llvm.ptr) -> i32 {
|
||
|
%0 = call @inner_func_inlinable(%ptr) : (!llvm.ptr) -> i32
|
||
|
return %0 : i32
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
// Check that llvm.return is correctly handled
|
||
|
|
||
|
func.func @func(%arg0 : i32) -> i32 {
|
||
|
llvm.return %arg0 : i32
|
||
|
}
|
||
|
// CHECK-LABEL: @llvm_ret
|
||
|
// CHECK-NOT: call
|
||
|
// CHECK: return %arg0
|
||
|
func.func @llvm_ret(%arg0 : i32) -> i32 {
|
||
|
%res = call @func(%arg0) : (i32) -> (i32)
|
||
|
return %res : i32
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
// Include all function attributes that don't prevent inlining
|
||
|
llvm.func internal fastcc @callee() -> (i32) attributes { function_entry_count = 42 : i64, dso_local } {
|
||
|
%0 = llvm.mlir.constant(42 : i32) : i32
|
||
|
llvm.return %0 : i32
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: llvm.func @caller
|
||
|
// CHECK-NEXT: %[[CST:.+]] = llvm.mlir.constant
|
||
|
// CHECK-NEXT: llvm.return %[[CST]]
|
||
|
llvm.func @caller() -> (i32) {
|
||
|
// Include all call attributes that don't prevent inlining.
|
||
|
%0 = llvm.call fastcc @callee() { fastmathFlags = #llvm.fastmath<nnan, ninf>, branch_weights = dense<42> : vector<1xi32> } : () -> (i32)
|
||
|
llvm.return %0 : i32
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
llvm.func @foo() -> (i32) attributes { passthrough = ["noinline"] } {
|
||
|
%0 = llvm.mlir.constant(0 : i32) : i32
|
||
|
llvm.return %0 : i32
|
||
|
}
|
||
|
|
||
|
llvm.func @bar() -> (i32) attributes { passthrough = ["noinline"] } {
|
||
|
%0 = llvm.mlir.constant(1 : i32) : i32
|
||
|
llvm.return %0 : i32
|
||
|
}
|
||
|
|
||
|
llvm.func @callee_with_multiple_blocks(%cond: i1) -> (i32) {
|
||
|
llvm.cond_br %cond, ^bb1, ^bb2
|
||
|
^bb1:
|
||
|
%0 = llvm.call @foo() : () -> (i32)
|
||
|
llvm.br ^bb3(%0: i32)
|
||
|
^bb2:
|
||
|
%1 = llvm.call @bar() : () -> (i32)
|
||
|
llvm.br ^bb3(%1: i32)
|
||
|
^bb3(%arg: i32):
|
||
|
llvm.return %arg : i32
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: llvm.func @caller
|
||
|
// CHECK-NEXT: llvm.cond_br {{.+}}, ^[[BB1:.+]], ^[[BB2:.+]]
|
||
|
// CHECK-NEXT: ^[[BB1]]:
|
||
|
// CHECK-NEXT: llvm.call @foo
|
||
|
// CHECK-NEXT: llvm.br ^[[BB3:[a-zA-Z0-9_]+]]
|
||
|
// CHECK-NEXT: ^[[BB2]]:
|
||
|
// CHECK-NEXT: llvm.call @bar
|
||
|
// CHECK-NEXT: llvm.br ^[[BB3]]
|
||
|
// CHECK-NEXT: ^[[BB3]]
|
||
|
// CHECK-NEXT: llvm.br ^[[BB4:[a-zA-Z0-9_]+]]
|
||
|
// CHECK-NEXT: ^[[BB4]]
|
||
|
// CHECK-NEXT: llvm.return
|
||
|
llvm.func @caller(%cond: i1) -> (i32) {
|
||
|
%0 = llvm.call @callee_with_multiple_blocks(%cond) : (i1) -> (i32)
|
||
|
llvm.return %0 : i32
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
llvm.func @personality() -> i32
|
||
|
|
||
|
llvm.func @callee() -> (i32) attributes { personality = @personality } {
|
||
|
%0 = llvm.mlir.constant(42 : i32) : i32
|
||
|
llvm.return %0 : i32
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: llvm.func @caller
|
||
|
// CHECK-NEXT: llvm.call @callee
|
||
|
// CHECK-NEXT: return
|
||
|
llvm.func @caller() -> (i32) {
|
||
|
%0 = llvm.call @callee() : () -> (i32)
|
||
|
llvm.return %0 : i32
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
llvm.func @callee() attributes { passthrough = ["foo", "bar"] } {
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: llvm.func @caller
|
||
|
// CHECK-NEXT: llvm.return
|
||
|
llvm.func @caller() {
|
||
|
llvm.call @callee() : () -> ()
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
llvm.func @callee_noinline() attributes { passthrough = ["noinline"] } {
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
llvm.func @callee_optnone() attributes { passthrough = ["optnone"] } {
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
llvm.func @callee_noduplicate() attributes { passthrough = ["noduplicate"] } {
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
llvm.func @callee_presplitcoroutine() attributes { passthrough = ["presplitcoroutine"] } {
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
llvm.func @callee_returns_twice() attributes { passthrough = ["returns_twice"] } {
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
llvm.func @callee_strictfp() attributes { passthrough = ["strictfp"] } {
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: llvm.func @caller
|
||
|
// CHECK-NEXT: llvm.call @callee_noinline
|
||
|
// CHECK-NEXT: llvm.call @callee_optnone
|
||
|
// CHECK-NEXT: llvm.call @callee_noduplicate
|
||
|
// CHECK-NEXT: llvm.call @callee_presplitcoroutine
|
||
|
// CHECK-NEXT: llvm.call @callee_returns_twice
|
||
|
// CHECK-NEXT: llvm.call @callee_strictfp
|
||
|
// CHECK-NEXT: llvm.return
|
||
|
llvm.func @caller() {
|
||
|
llvm.call @callee_noinline() : () -> ()
|
||
|
llvm.call @callee_optnone() : () -> ()
|
||
|
llvm.call @callee_noduplicate() : () -> ()
|
||
|
llvm.call @callee_presplitcoroutine() : () -> ()
|
||
|
llvm.call @callee_returns_twice() : () -> ()
|
||
|
llvm.call @callee_strictfp() : () -> ()
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
llvm.func @static_alloca() -> f32 {
|
||
|
%0 = llvm.mlir.constant(4 : i32) : i32
|
||
|
%1 = llvm.alloca %0 x f32 : (i32) -> !llvm.ptr
|
||
|
%2 = llvm.load %1 : !llvm.ptr -> f32
|
||
|
llvm.return %2 : f32
|
||
|
}
|
||
|
|
||
|
llvm.func @dynamic_alloca(%size : i32) -> f32 {
|
||
|
%0 = llvm.add %size, %size : i32
|
||
|
%1 = llvm.alloca %0 x f32 : (i32) -> !llvm.ptr
|
||
|
%2 = llvm.load %1 : !llvm.ptr -> f32
|
||
|
llvm.return %2 : f32
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: llvm.func @test_inline
|
||
|
llvm.func @test_inline(%cond : i1, %size : i32) -> f32 {
|
||
|
// Check that the static alloca was moved to the entry block after inlining
|
||
|
// with its size defined by a constant.
|
||
|
// CHECK-NOT: ^{{.+}}:
|
||
|
// CHECK-NEXT: llvm.mlir.constant
|
||
|
// CHECK-NEXT: llvm.alloca
|
||
|
// CHECK: llvm.cond_br
|
||
|
llvm.cond_br %cond, ^bb1, ^bb2
|
||
|
// CHECK: ^{{.+}}:
|
||
|
^bb1:
|
||
|
// CHECK-NOT: llvm.call @static_alloca
|
||
|
// CHECK: llvm.intr.lifetime.start
|
||
|
%0 = llvm.call @static_alloca() : () -> f32
|
||
|
// CHECK: llvm.intr.lifetime.end
|
||
|
// CHECK: llvm.br ^[[BB3:[a-zA-Z0-9_]+]]
|
||
|
llvm.br ^bb3(%0: f32)
|
||
|
// CHECK: ^{{.+}}:
|
||
|
^bb2:
|
||
|
// Check that the dynamic alloca was inlined, but that it was not moved to the
|
||
|
// entry block.
|
||
|
// CHECK: %[[STACK:[a-zA-Z0-9_]+]] = llvm.intr.stacksave
|
||
|
// CHECK: llvm.add
|
||
|
// CHECK: llvm.alloca
|
||
|
// CHECK: llvm.intr.stackrestore %[[STACK]]
|
||
|
// CHECK-NOT: llvm.call @dynamic_alloca
|
||
|
%1 = llvm.call @dynamic_alloca(%size) : (i32) -> f32
|
||
|
// CHECK: llvm.br ^[[BB3]]
|
||
|
llvm.br ^bb3(%1: f32)
|
||
|
// CHECK: ^[[BB3]]
|
||
|
^bb3(%arg : f32):
|
||
|
// CHECK-NEXT: return
|
||
|
llvm.return %arg : f32
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
llvm.func @static_alloca_not_in_entry(%cond : i1) -> f32 {
|
||
|
llvm.cond_br %cond, ^bb1, ^bb2
|
||
|
^bb1:
|
||
|
%0 = llvm.mlir.constant(4 : i32) : i32
|
||
|
%1 = llvm.alloca %0 x f32 : (i32) -> !llvm.ptr
|
||
|
llvm.br ^bb3(%1: !llvm.ptr)
|
||
|
^bb2:
|
||
|
%2 = llvm.mlir.constant(8 : i32) : i32
|
||
|
%3 = llvm.alloca %2 x f32 : (i32) -> !llvm.ptr
|
||
|
llvm.br ^bb3(%3: !llvm.ptr)
|
||
|
^bb3(%ptr : !llvm.ptr):
|
||
|
%4 = llvm.load %ptr : !llvm.ptr -> f32
|
||
|
llvm.return %4 : f32
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: llvm.func @test_inline
|
||
|
llvm.func @test_inline(%cond : i1) -> f32 {
|
||
|
// Make sure the alloca was not moved to the entry block.
|
||
|
// CHECK-NOT: llvm.alloca
|
||
|
// CHECK: llvm.cond_br
|
||
|
// CHECK: llvm.alloca
|
||
|
%0 = llvm.call @static_alloca_not_in_entry(%cond) : (i1) -> f32
|
||
|
llvm.return %0 : f32
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
llvm.func @static_alloca(%cond: i1) -> f32 {
|
||
|
%0 = llvm.mlir.constant(4 : i32) : i32
|
||
|
%1 = llvm.alloca %0 x f32 : (i32) -> !llvm.ptr
|
||
|
llvm.cond_br %cond, ^bb1, ^bb2
|
||
|
^bb1:
|
||
|
%2 = llvm.load %1 : !llvm.ptr -> f32
|
||
|
llvm.return %2 : f32
|
||
|
^bb2:
|
||
|
%3 = llvm.mlir.constant(3.14192 : f32) : f32
|
||
|
llvm.return %3 : f32
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: llvm.func @test_inline
|
||
|
llvm.func @test_inline(%cond0 : i1, %cond1 : i1, %funcArg : f32) -> f32 {
|
||
|
// CHECK-NOT: llvm.cond_br
|
||
|
// CHECK: %[[PTR:.+]] = llvm.alloca
|
||
|
// CHECK: llvm.cond_br %{{.+}}, ^[[BB1:.+]], ^{{.+}}
|
||
|
llvm.cond_br %cond0, ^bb1, ^bb2
|
||
|
// CHECK: ^[[BB1]]
|
||
|
^bb1:
|
||
|
// Make sure the lifetime begin intrinsic has been inserted where the call
|
||
|
// used to be, even though the alloca has been moved to the entry block.
|
||
|
// CHECK-NEXT: llvm.intr.lifetime.start 4, %[[PTR]]
|
||
|
%0 = llvm.call @static_alloca(%cond1) : (i1) -> f32
|
||
|
// CHECK: llvm.cond_br %{{.+}}, ^[[BB2:.+]], ^[[BB3:.+]]
|
||
|
llvm.br ^bb3(%0: f32)
|
||
|
// Make sure the lifetime end intrinsic has been inserted at both former
|
||
|
// return sites of the callee.
|
||
|
// CHECK: ^[[BB2]]:
|
||
|
// CHECK-NEXT: llvm.load
|
||
|
// CHECK-NEXT: llvm.intr.lifetime.end 4, %[[PTR]]
|
||
|
// CHECK: ^[[BB3]]:
|
||
|
// CHECK-NEXT: llvm.intr.lifetime.end 4, %[[PTR]]
|
||
|
^bb2:
|
||
|
llvm.br ^bb3(%funcArg: f32)
|
||
|
^bb3(%blockArg: f32):
|
||
|
llvm.return %blockArg : f32
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
llvm.func @static_alloca() -> f32 {
|
||
|
%0 = llvm.mlir.constant(4 : i32) : i32
|
||
|
%1 = llvm.alloca %0 x f32 : (i32) -> !llvm.ptr
|
||
|
%2 = llvm.load %1 : !llvm.ptr -> f32
|
||
|
llvm.return %2 : f32
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: llvm.func @test_inline
|
||
|
llvm.func @test_inline(%cond0 : i1) {
|
||
|
// Verify the alloca is relocated to the entry block of the parent function
|
||
|
// if the region operation is neither marked as isolated from above or
|
||
|
// automatic allocation scope.
|
||
|
// CHECK: %[[ALLOCA:.+]] = llvm.alloca
|
||
|
// CHECK: "test.one_region_op"() ({
|
||
|
"test.one_region_op"() ({
|
||
|
%0 = llvm.call @static_alloca() : () -> f32
|
||
|
// CHECK-NEXT: llvm.intr.lifetime.start 4, %[[ALLOCA]]
|
||
|
// CHECK-NEXT: %[[RES:.+]] = llvm.load %[[ALLOCA]]
|
||
|
// CHECK-NEXT: llvm.intr.lifetime.end 4, %[[ALLOCA]]
|
||
|
// CHECK-NEXT: test.region_yield %[[RES]]
|
||
|
test.region_yield %0 : f32
|
||
|
}) : () -> ()
|
||
|
// Verify the alloca is not relocated out of operations that are marked as
|
||
|
// isolated from above.
|
||
|
// CHECK-NOT: llvm.alloca
|
||
|
// CHECK: test.isolated_regions
|
||
|
test.isolated_regions {
|
||
|
// CHECK: %[[ALLOCA:.+]] = llvm.alloca
|
||
|
%0 = llvm.call @static_alloca() : () -> f32
|
||
|
// CHECK: test.region_yield
|
||
|
test.region_yield %0 : f32
|
||
|
}
|
||
|
// Verify the alloca is not relocated out of operations that are marked as
|
||
|
// automatic allocation scope.
|
||
|
// CHECK-NOT: llvm.alloca
|
||
|
// CHECK: test.alloca_scope_region
|
||
|
test.alloca_scope_region {
|
||
|
// CHECK: %[[ALLOCA:.+]] = llvm.alloca
|
||
|
%0 = llvm.call @static_alloca() : () -> f32
|
||
|
// CHECK: test.region_yield
|
||
|
test.region_yield %0 : f32
|
||
|
}
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
llvm.func @alloca_with_lifetime(%cond: i1) -> f32 {
|
||
|
%0 = llvm.mlir.constant(4 : i32) : i32
|
||
|
%1 = llvm.alloca %0 x f32 : (i32) -> !llvm.ptr
|
||
|
llvm.intr.lifetime.start 4, %1 : !llvm.ptr
|
||
|
%2 = llvm.load %1 : !llvm.ptr -> f32
|
||
|
llvm.intr.lifetime.end 4, %1 : !llvm.ptr
|
||
|
%3 = llvm.fadd %2, %2 : f32
|
||
|
llvm.return %3 : f32
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: llvm.func @test_inline
|
||
|
llvm.func @test_inline(%cond0 : i1, %cond1 : i1, %funcArg : f32) -> f32 {
|
||
|
// CHECK-NOT: llvm.cond_br
|
||
|
// CHECK: %[[PTR:.+]] = llvm.alloca
|
||
|
// CHECK: llvm.cond_br %{{.+}}, ^[[BB1:.+]], ^{{.+}}
|
||
|
llvm.cond_br %cond0, ^bb1, ^bb2
|
||
|
// CHECK: ^[[BB1]]
|
||
|
^bb1:
|
||
|
// Make sure the original lifetime intrinsic has been preserved, rather than
|
||
|
// inserting a new one with a larger scope.
|
||
|
// CHECK: llvm.intr.lifetime.start 4, %[[PTR]]
|
||
|
// CHECK-NEXT: llvm.load %[[PTR]]
|
||
|
// CHECK-NEXT: llvm.intr.lifetime.end 4, %[[PTR]]
|
||
|
// CHECK: llvm.fadd
|
||
|
// CHECK-NOT: llvm.intr.lifetime.end
|
||
|
%0 = llvm.call @alloca_with_lifetime(%cond1) : (i1) -> f32
|
||
|
llvm.br ^bb3(%0: f32)
|
||
|
^bb2:
|
||
|
llvm.br ^bb3(%funcArg: f32)
|
||
|
^bb3(%blockArg: f32):
|
||
|
llvm.return %blockArg : f32
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
llvm.func @with_byval_arg(%ptr : !llvm.ptr { llvm.byval = f64 }) {
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: llvm.func @test_byval
|
||
|
// CHECK-SAME: %[[PTR:[a-zA-Z0-9_]+]]: !llvm.ptr
|
||
|
llvm.func @test_byval(%ptr : !llvm.ptr) {
|
||
|
// Make sure the new static alloca goes to the entry block.
|
||
|
// CHECK: %[[ALLOCA:.+]] = llvm.alloca %{{.+}} x f64
|
||
|
// CHECK: llvm.br ^[[BB1:[a-zA-Z0-9_]+]]
|
||
|
llvm.br ^bb1
|
||
|
// CHECK: ^[[BB1]]
|
||
|
^bb1:
|
||
|
// CHECK: "llvm.intr.memcpy"(%[[ALLOCA]], %[[PTR]]
|
||
|
llvm.call @with_byval_arg(%ptr) : (!llvm.ptr) -> ()
|
||
|
llvm.br ^bb2
|
||
|
^bb2:
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
llvm.func @with_byval_arg(%ptr : !llvm.ptr { llvm.byval = f64 }) attributes {memory = #llvm.memory_effects<other = readwrite, argMem = read, inaccessibleMem = readwrite>} {
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: llvm.func @test_byval_read_only
|
||
|
// CHECK-NOT: llvm.call
|
||
|
// CHECK-NEXT: llvm.return
|
||
|
llvm.func @test_byval_read_only(%ptr : !llvm.ptr) {
|
||
|
llvm.call @with_byval_arg(%ptr) : (!llvm.ptr) -> ()
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
llvm.func @with_byval_arg(%ptr : !llvm.ptr { llvm.byval = f64 }) attributes {memory = #llvm.memory_effects<other = readwrite, argMem = write, inaccessibleMem = readwrite>} {
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: llvm.func @test_byval_write_only
|
||
|
// CHECK-SAME: %[[PTR:[a-zA-Z0-9_]+]]: !llvm.ptr
|
||
|
// CHECK: %[[ALLOCA:.+]] = llvm.alloca %{{.+}} x f64
|
||
|
// CHECK: "llvm.intr.memcpy"(%[[ALLOCA]], %[[PTR]]
|
||
|
llvm.func @test_byval_write_only(%ptr : !llvm.ptr) {
|
||
|
llvm.call @with_byval_arg(%ptr) : (!llvm.ptr) -> ()
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
llvm.func @aligned_byval_arg(%ptr : !llvm.ptr { llvm.byval = i16, llvm.align = 16 }) attributes {memory = #llvm.memory_effects<other = read, argMem = read, inaccessibleMem = read>} {
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: llvm.func @test_byval_input_aligned
|
||
|
// CHECK-SAME: %[[UNALIGNED:[a-zA-Z0-9_]+]]: !llvm.ptr
|
||
|
// CHECK-SAME: %[[ALIGNED:[a-zA-Z0-9_]+]]: !llvm.ptr
|
||
|
llvm.func @test_byval_input_aligned(%unaligned : !llvm.ptr, %aligned : !llvm.ptr { llvm.align = 16 }) {
|
||
|
// Make sure only the unaligned input triggers a memcpy.
|
||
|
// CHECK: %[[ALLOCA:.+]] = llvm.alloca %{{.+}} x i16 {alignment = 16
|
||
|
// CHECK: "llvm.intr.memcpy"(%[[ALLOCA]], %[[UNALIGNED]]
|
||
|
llvm.call @aligned_byval_arg(%unaligned) : (!llvm.ptr) -> ()
|
||
|
// CHECK-NOT: memcpy
|
||
|
llvm.call @aligned_byval_arg(%aligned) : (!llvm.ptr) -> ()
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
llvm.func @func_that_uses_ptr(%ptr : !llvm.ptr)
|
||
|
|
||
|
llvm.func @aligned_byval_arg(%ptr : !llvm.ptr { llvm.byval = i16, llvm.align = 16 }) attributes {memory = #llvm.memory_effects<other = read, argMem = read, inaccessibleMem = read>} {
|
||
|
llvm.call @func_that_uses_ptr(%ptr) : (!llvm.ptr) -> ()
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: llvm.func @test_byval_realign_alloca
|
||
|
llvm.func @test_byval_realign_alloca() {
|
||
|
%size = llvm.mlir.constant(4 : i64) : i64
|
||
|
// CHECK-NOT: llvm.alloca{{.+}}alignment = 1
|
||
|
// CHECK: llvm.alloca {{.+}}alignment = 16 : i64
|
||
|
// CHECK-NOT: llvm.intr.memcpy
|
||
|
%unaligned = llvm.alloca %size x i16 { alignment = 1 } : (i64) -> !llvm.ptr
|
||
|
llvm.call @aligned_byval_arg(%unaligned) : (!llvm.ptr) -> ()
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
module attributes {
|
||
|
dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<"dlti.stack_alignment", 32 : i32>>
|
||
|
} {
|
||
|
|
||
|
llvm.func @func_that_uses_ptr(%ptr : !llvm.ptr)
|
||
|
|
||
|
llvm.func @aligned_byval_arg(%ptr : !llvm.ptr { llvm.byval = i16, llvm.align = 16 }) attributes {memory = #llvm.memory_effects<other = read, argMem = read, inaccessibleMem = read>} {
|
||
|
llvm.call @func_that_uses_ptr(%ptr) : (!llvm.ptr) -> ()
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: llvm.func @test_exceeds_natural_stack_alignment
|
||
|
llvm.func @test_exceeds_natural_stack_alignment() {
|
||
|
%size = llvm.mlir.constant(4 : i64) : i64
|
||
|
// Natural stack alignment is exceeded, so prefer a copy instead of
|
||
|
// triggering a dynamic stack realignment.
|
||
|
// CHECK-DAG: %[[SRC:[a-zA-Z0-9_]+]] = llvm.alloca{{.+}}alignment = 2
|
||
|
// CHECK-DAG: %[[DST:[a-zA-Z0-9_]+]] = llvm.alloca{{.+}}alignment = 16
|
||
|
// CHECK: "llvm.intr.memcpy"(%[[DST]], %[[SRC]]
|
||
|
%unaligned = llvm.alloca %size x i16 { alignment = 2 } : (i64) -> !llvm.ptr
|
||
|
llvm.call @aligned_byval_arg(%unaligned) : (!llvm.ptr) -> ()
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
module attributes {
|
||
|
dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<"dlti.stack_alignment", 32 : i32>>
|
||
|
} {
|
||
|
|
||
|
llvm.func @func_that_uses_ptr(%ptr : !llvm.ptr)
|
||
|
|
||
|
llvm.func @aligned_byval_arg(%ptr : !llvm.ptr { llvm.byval = i16, llvm.align = 16 }) attributes {memory = #llvm.memory_effects<other = read, argMem = read, inaccessibleMem = read>} {
|
||
|
llvm.call @func_that_uses_ptr(%ptr) : (!llvm.ptr) -> ()
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: llvm.func @test_alignment_exceeded_anyway
|
||
|
llvm.func @test_alignment_exceeded_anyway() {
|
||
|
%size = llvm.mlir.constant(4 : i64) : i64
|
||
|
// Natural stack alignment is lower than the target alignment, but the
|
||
|
// alloca's existing alignment already exceeds it, so we might as well avoid
|
||
|
// the copy.
|
||
|
// CHECK-NOT: llvm.alloca{{.+}}alignment = 1
|
||
|
// CHECK: llvm.alloca {{.+}}alignment = 16 : i64
|
||
|
// CHECK-NOT: llvm.intr.memcpy
|
||
|
%unaligned = llvm.alloca %size x i16 { alignment = 8 } : (i64) -> !llvm.ptr
|
||
|
llvm.call @aligned_byval_arg(%unaligned) : (!llvm.ptr) -> ()
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
llvm.mlir.global private @unaligned_global(42 : i64) : i64
|
||
|
llvm.mlir.global private @aligned_global(42 : i64) { alignment = 64 } : i64
|
||
|
|
||
|
llvm.func @aligned_byval_arg(%ptr : !llvm.ptr { llvm.byval = i16, llvm.align = 16 }) attributes {memory = #llvm.memory_effects<other = read, argMem = read, inaccessibleMem = read>} {
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: llvm.func @test_byval_global
|
||
|
llvm.func @test_byval_global() {
|
||
|
// Make sure only the unaligned global triggers a memcpy.
|
||
|
// CHECK-DAG: %[[UNALIGNED:.+]] = llvm.mlir.addressof @unaligned_global
|
||
|
// CHECK-DAG: %[[ALLOCA:.+]] = llvm.alloca
|
||
|
// CHECK: "llvm.intr.memcpy"(%[[ALLOCA]], %[[UNALIGNED]]
|
||
|
// CHECK-NOT: llvm.alloca
|
||
|
%unaligned = llvm.mlir.addressof @unaligned_global : !llvm.ptr
|
||
|
llvm.call @aligned_byval_arg(%unaligned) : (!llvm.ptr) -> ()
|
||
|
%aligned = llvm.mlir.addressof @aligned_global : !llvm.ptr
|
||
|
llvm.call @aligned_byval_arg(%aligned) : (!llvm.ptr) -> ()
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
llvm.func @ignored_attrs(%ptr : !llvm.ptr { llvm.inreg, llvm.nocapture, llvm.nofree, llvm.preallocated = i32, llvm.returned, llvm.alignstack = 32 : i64, llvm.writeonly, llvm.noundef, llvm.nonnull }, %x : i32 { llvm.zeroext }) -> (!llvm.ptr { llvm.noundef, llvm.inreg, llvm.nonnull }) {
|
||
|
llvm.return %ptr : !llvm.ptr
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: @test_ignored_attrs
|
||
|
// CHECK-NOT: llvm.call
|
||
|
// CHECK-NEXT: llvm.return
|
||
|
llvm.func @test_ignored_attrs(%ptr : !llvm.ptr, %x : i32) {
|
||
|
llvm.call @ignored_attrs(%ptr, %x) : (!llvm.ptr, i32) -> (!llvm.ptr)
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
llvm.func @disallowed_arg_attr(%ptr : !llvm.ptr { llvm.inalloca = i64 }) {
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: @test_disallow_arg_attr
|
||
|
// CHECK-NEXT: llvm.call
|
||
|
llvm.func @test_disallow_arg_attr(%ptr : !llvm.ptr) {
|
||
|
llvm.call @disallowed_arg_attr(%ptr) : (!llvm.ptr) -> ()
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
#callee = #llvm.access_group<id = distinct[0]<>>
|
||
|
#caller = #llvm.access_group<id = distinct[1]<>>
|
||
|
|
||
|
llvm.func @inlinee(%ptr : !llvm.ptr) -> i32 {
|
||
|
%0 = llvm.load %ptr { access_groups = [#callee] } : !llvm.ptr -> i32
|
||
|
llvm.return %0 : i32
|
||
|
}
|
||
|
|
||
|
// CHECK-DAG: #[[$CALLEE:.*]] = #llvm.access_group<id = {{.*}}>
|
||
|
// CHECK-DAG: #[[$CALLER:.*]] = #llvm.access_group<id = {{.*}}>
|
||
|
|
||
|
// CHECK-LABEL: func @caller
|
||
|
// CHECK: llvm.load
|
||
|
// CHECK-SAME: access_groups = [#[[$CALLEE]], #[[$CALLER]]]
|
||
|
llvm.func @caller(%ptr : !llvm.ptr) -> i32 {
|
||
|
%0 = llvm.call @inlinee(%ptr) { access_groups = [#caller] } : (!llvm.ptr) -> (i32)
|
||
|
llvm.return %0 : i32
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
#caller = #llvm.access_group<id = distinct[1]<>>
|
||
|
|
||
|
llvm.func @inlinee(%ptr : !llvm.ptr) -> i32 {
|
||
|
%0 = llvm.load %ptr : !llvm.ptr -> i32
|
||
|
llvm.return %0 : i32
|
||
|
}
|
||
|
|
||
|
// CHECK-DAG: #[[$CALLER:.*]] = #llvm.access_group<id = {{.*}}>
|
||
|
|
||
|
// CHECK-LABEL: func @caller
|
||
|
// CHECK: llvm.load
|
||
|
// CHECK-SAME: access_groups = [#[[$CALLER]]]
|
||
|
// CHECK: llvm.store
|
||
|
// CHECK-SAME: access_groups = [#[[$CALLER]]]
|
||
|
llvm.func @caller(%ptr : !llvm.ptr) -> i32 {
|
||
|
%c5 = llvm.mlir.constant(5 : i32) : i32
|
||
|
%0 = llvm.call @inlinee(%ptr) { access_groups = [#caller] } : (!llvm.ptr) -> (i32)
|
||
|
llvm.store %c5, %ptr { access_groups = [#caller] } : i32, !llvm.ptr
|
||
|
llvm.return %0 : i32
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
llvm.func @vararg_func(...) {
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
llvm.func @vararg_intrinrics() {
|
||
|
%0 = llvm.mlir.constant(1 : i32) : i32
|
||
|
%list = llvm.alloca %0 x !llvm.struct<"struct.va_list_opaque", (ptr)> : (i32) -> !llvm.ptr
|
||
|
// The vararg intinriscs should normally be part of a variadic function.
|
||
|
// However, this test uses a non-variadic function to ensure the presence of
|
||
|
// the intrinsic alone suffices to prevent inlining.
|
||
|
llvm.intr.vastart %list : !llvm.ptr
|
||
|
llvm.return
|
||
|
}
|
||
|
|
||
|
// CHECK-LABEL: func @caller
|
||
|
llvm.func @caller() {
|
||
|
// CHECK-NEXT: llvm.call @vararg_func()
|
||
|
llvm.call @vararg_func() vararg(!llvm.func<void (...)>) : () -> ()
|
||
|
// CHECK-NEXT: llvm.call @vararg_intrinrics()
|
||
|
llvm.call @vararg_intrinrics() : () -> ()
|
||
|
llvm.return
|
||
|
}
|