From 253912d62149e1ec54d29c0c3b2de1e11208b082 Mon Sep 17 00:00:00 2001 From: Alex Richardson Date: Mon, 25 Jan 2021 14:24:50 +0000 Subject: [PATCH] Add a baseline test for non-tag-preserving copy This shows that we don't currently inline memcpy/memmove even for instrinsics that have been tagged with the no_preserve_cheri_tags attribute. --- .../Inputs/memcpy-no-preserve-tags-attr.ll | 62 ++++++++ .../MIPS/memcpy-no-preserve-tags-attr.ll | 142 ++++++++++++++++++ .../RISCV32/memcpy-no-preserve-tags-attr.ll | 132 ++++++++++++++++ .../RISCV64/memcpy-no-preserve-tags-attr.ll | 126 ++++++++++++++++ 4 files changed, 462 insertions(+) create mode 100644 llvm/test/CodeGen/CHERI-Generic/Inputs/memcpy-no-preserve-tags-attr.ll create mode 100644 llvm/test/CodeGen/CHERI-Generic/MIPS/memcpy-no-preserve-tags-attr.ll create mode 100644 llvm/test/CodeGen/CHERI-Generic/RISCV32/memcpy-no-preserve-tags-attr.ll create mode 100644 llvm/test/CodeGen/CHERI-Generic/RISCV64/memcpy-no-preserve-tags-attr.ll diff --git a/llvm/test/CodeGen/CHERI-Generic/Inputs/memcpy-no-preserve-tags-attr.ll b/llvm/test/CodeGen/CHERI-Generic/Inputs/memcpy-no-preserve-tags-attr.ll new file mode 100644 index 000000000000..0fecc865d04c --- /dev/null +++ b/llvm/test/CodeGen/CHERI-Generic/Inputs/memcpy-no-preserve-tags-attr.ll @@ -0,0 +1,62 @@ +; Check that the no_preserve_tags annotation on memcpy/memmove intrinsics allows +; use to inline struct copies >= capability size. +; RUN: llc @PURECAP_HARDFLOAT_ARGS@ -o - < %s | FileCheck %s + +%struct.pair = type { iCAPRANGE, iCAPRANGE } + +; Function Attrs: argmemonly nounwind +declare void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* nocapture writeonly, i8 addrspace(200)* nocapture readonly, i64, i1) +declare void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* nocapture writeonly, i8 addrspace(200)* nocapture readonly, i64, i1) + +; Without a no_preserve_tags attribute we always call memcpy. In this case we +; don't know whether the type might actually contain capabilities (e.g. unions). +define void @memcpy_no_attr(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align @CAP_RANGE_BYTES@ %a_i8, i8 addrspace(200)* align @CAP_RANGE_BYTES@ %b_i8, i64 16, i1 false) + ret void +} + +define void @memmove_no_attr(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align @CAP_RANGE_BYTES@ %a_i8, i8 addrspace(200)* align @CAP_RANGE_BYTES@ %b_i8, i64 16, i1 false) + ret void +} + +; We have to emit a call if the intrinsic has must_preserve_cheri_tags: +define void @memcpy_must_preserve(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align @CAP_RANGE_BYTES@ %a_i8, i8 addrspace(200)* align @CAP_RANGE_BYTES@ %b_i8, i64 16, i1 false) must_preserve_cheri_tags + ret void +} + +define void @memmove_must_preserve(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align @CAP_RANGE_BYTES@ %a_i8, i8 addrspace(200)* align @CAP_RANGE_BYTES@ %b_i8, i64 16, i1 false) must_preserve_cheri_tags + ret void +} + +; We should be able to inline the call memcpy/memmove if the intrinsic has no_preserve_cheri_tags: +; TODO: we should be able to elide this memcpy call +define void @memcpy_no_preserve(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align @CAP_RANGE_BYTES@ %a_i8, i8 addrspace(200)* align @CAP_RANGE_BYTES@ %b_i8, i64 16, i1 false) no_preserve_cheri_tags + ret void +} + +define void @memmove_no_preserve(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align @CAP_RANGE_BYTES@ %a_i8, i8 addrspace(200)* align @CAP_RANGE_BYTES@ %b_i8, i64 16, i1 false) no_preserve_cheri_tags + ret void +} diff --git a/llvm/test/CodeGen/CHERI-Generic/MIPS/memcpy-no-preserve-tags-attr.ll b/llvm/test/CodeGen/CHERI-Generic/MIPS/memcpy-no-preserve-tags-attr.ll new file mode 100644 index 000000000000..dfde319f8d3e --- /dev/null +++ b/llvm/test/CodeGen/CHERI-Generic/MIPS/memcpy-no-preserve-tags-attr.ll @@ -0,0 +1,142 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes --force-update +; DO NOT EDIT -- This file was generated from test/CodeGen/CHERI-Generic/Inputs/memcpy-no-preserve-tags-attr.ll +; Check that the no_preserve_tags annotation on memcpy/memmove intrinsics allows +; use to inline struct copies >= capability size. +; RUN: llc -mtriple=mips64 -mcpu=cheri128 -mattr=+cheri128 --relocation-model=pic -target-abi purecap -o - < %s | FileCheck %s + +%struct.pair = type { i64, i64 } + +; Function Attrs: argmemonly nounwind +declare void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* nocapture writeonly, i8 addrspace(200)* nocapture readonly, i64, i1) +declare void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* nocapture writeonly, i8 addrspace(200)* nocapture readonly, i64, i1) + +; Without a no_preserve_tags attribute we always call memcpy. In this case we +; don't know whether the type might actually contain capabilities (e.g. unions). +define void @memcpy_no_attr(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +; CHECK-LABEL: memcpy_no_attr: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: cincoffset $c11, $c11, -16 +; CHECK-NEXT: csc $c17, $zero, 0($c11) # 16-byte Folded Spill +; CHECK-NEXT: lui $1, %pcrel_hi(_CHERI_CAPABILITY_TABLE_-8) +; CHECK-NEXT: daddiu $1, $1, %pcrel_lo(_CHERI_CAPABILITY_TABLE_-4) +; CHECK-NEXT: cgetpccincoffset $c1, $1 +; CHECK-NEXT: clcbi $c12, %capcall20(memcpy)($c1) +; CHECK-NEXT: cjalr $c12, $c17 +; CHECK-NEXT: daddiu $4, $zero, 16 +; CHECK-NEXT: clc $c17, $zero, 0($c11) # 16-byte Folded Reload +; CHECK-NEXT: cjr $c17 +; CHECK-NEXT: cincoffset $c11, $c11, 16 +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 %a_i8, i8 addrspace(200)* align 8 %b_i8, i64 16, i1 false) + ret void +} + +define void @memmove_no_attr(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +; CHECK-LABEL: memmove_no_attr: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: cincoffset $c11, $c11, -16 +; CHECK-NEXT: csc $c17, $zero, 0($c11) # 16-byte Folded Spill +; CHECK-NEXT: lui $1, %pcrel_hi(_CHERI_CAPABILITY_TABLE_-8) +; CHECK-NEXT: daddiu $1, $1, %pcrel_lo(_CHERI_CAPABILITY_TABLE_-4) +; CHECK-NEXT: cgetpccincoffset $c1, $1 +; CHECK-NEXT: clcbi $c12, %capcall20(memmove)($c1) +; CHECK-NEXT: cjalr $c12, $c17 +; CHECK-NEXT: daddiu $4, $zero, 16 +; CHECK-NEXT: clc $c17, $zero, 0($c11) # 16-byte Folded Reload +; CHECK-NEXT: cjr $c17 +; CHECK-NEXT: cincoffset $c11, $c11, 16 +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 8 %a_i8, i8 addrspace(200)* align 8 %b_i8, i64 16, i1 false) + ret void +} + +; We have to emit a call if the intrinsic has must_preserve_cheri_tags: +define void @memcpy_must_preserve(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +; CHECK-LABEL: memcpy_must_preserve: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: cincoffset $c11, $c11, -16 +; CHECK-NEXT: csc $c17, $zero, 0($c11) # 16-byte Folded Spill +; CHECK-NEXT: lui $1, %pcrel_hi(_CHERI_CAPABILITY_TABLE_-8) +; CHECK-NEXT: daddiu $1, $1, %pcrel_lo(_CHERI_CAPABILITY_TABLE_-4) +; CHECK-NEXT: cgetpccincoffset $c1, $1 +; CHECK-NEXT: clcbi $c12, %capcall20(memcpy)($c1) +; CHECK-NEXT: cjalr $c12, $c17 +; CHECK-NEXT: daddiu $4, $zero, 16 +; CHECK-NEXT: clc $c17, $zero, 0($c11) # 16-byte Folded Reload +; CHECK-NEXT: cjr $c17 +; CHECK-NEXT: cincoffset $c11, $c11, 16 +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 %a_i8, i8 addrspace(200)* align 8 %b_i8, i64 16, i1 false) must_preserve_cheri_tags + ret void +} + +define void @memmove_must_preserve(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +; CHECK-LABEL: memmove_must_preserve: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: cincoffset $c11, $c11, -16 +; CHECK-NEXT: csc $c17, $zero, 0($c11) # 16-byte Folded Spill +; CHECK-NEXT: lui $1, %pcrel_hi(_CHERI_CAPABILITY_TABLE_-8) +; CHECK-NEXT: daddiu $1, $1, %pcrel_lo(_CHERI_CAPABILITY_TABLE_-4) +; CHECK-NEXT: cgetpccincoffset $c1, $1 +; CHECK-NEXT: clcbi $c12, %capcall20(memmove)($c1) +; CHECK-NEXT: cjalr $c12, $c17 +; CHECK-NEXT: daddiu $4, $zero, 16 +; CHECK-NEXT: clc $c17, $zero, 0($c11) # 16-byte Folded Reload +; CHECK-NEXT: cjr $c17 +; CHECK-NEXT: cincoffset $c11, $c11, 16 +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 8 %a_i8, i8 addrspace(200)* align 8 %b_i8, i64 16, i1 false) must_preserve_cheri_tags + ret void +} + +; We should be able to inline the call memcpy/memmove if the intrinsic has no_preserve_cheri_tags: +; TODO: we should be able to elide this memcpy call +define void @memcpy_no_preserve(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +; CHECK-LABEL: memcpy_no_preserve: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: cincoffset $c11, $c11, -16 +; CHECK-NEXT: csc $c17, $zero, 0($c11) # 16-byte Folded Spill +; CHECK-NEXT: lui $1, %pcrel_hi(_CHERI_CAPABILITY_TABLE_-8) +; CHECK-NEXT: daddiu $1, $1, %pcrel_lo(_CHERI_CAPABILITY_TABLE_-4) +; CHECK-NEXT: cgetpccincoffset $c1, $1 +; CHECK-NEXT: clcbi $c12, %capcall20(memcpy)($c1) +; CHECK-NEXT: cjalr $c12, $c17 +; CHECK-NEXT: daddiu $4, $zero, 16 +; CHECK-NEXT: clc $c17, $zero, 0($c11) # 16-byte Folded Reload +; CHECK-NEXT: cjr $c17 +; CHECK-NEXT: cincoffset $c11, $c11, 16 +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 %a_i8, i8 addrspace(200)* align 8 %b_i8, i64 16, i1 false) no_preserve_cheri_tags + ret void +} + +define void @memmove_no_preserve(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +; CHECK-LABEL: memmove_no_preserve: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: cincoffset $c11, $c11, -16 +; CHECK-NEXT: csc $c17, $zero, 0($c11) # 16-byte Folded Spill +; CHECK-NEXT: lui $1, %pcrel_hi(_CHERI_CAPABILITY_TABLE_-8) +; CHECK-NEXT: daddiu $1, $1, %pcrel_lo(_CHERI_CAPABILITY_TABLE_-4) +; CHECK-NEXT: cgetpccincoffset $c1, $1 +; CHECK-NEXT: clcbi $c12, %capcall20(memmove)($c1) +; CHECK-NEXT: cjalr $c12, $c17 +; CHECK-NEXT: daddiu $4, $zero, 16 +; CHECK-NEXT: clc $c17, $zero, 0($c11) # 16-byte Folded Reload +; CHECK-NEXT: cjr $c17 +; CHECK-NEXT: cincoffset $c11, $c11, 16 +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 8 %a_i8, i8 addrspace(200)* align 8 %b_i8, i64 16, i1 false) no_preserve_cheri_tags + ret void +} diff --git a/llvm/test/CodeGen/CHERI-Generic/RISCV32/memcpy-no-preserve-tags-attr.ll b/llvm/test/CodeGen/CHERI-Generic/RISCV32/memcpy-no-preserve-tags-attr.ll new file mode 100644 index 000000000000..c1bac8b9770f --- /dev/null +++ b/llvm/test/CodeGen/CHERI-Generic/RISCV32/memcpy-no-preserve-tags-attr.ll @@ -0,0 +1,132 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes --force-update +; DO NOT EDIT -- This file was generated from test/CodeGen/CHERI-Generic/Inputs/memcpy-no-preserve-tags-attr.ll +; Check that the no_preserve_tags annotation on memcpy/memmove intrinsics allows +; use to inline struct copies >= capability size. +; RUN: llc -mtriple=riscv32 --relocation-model=pic -target-abi il32pc64f -mattr=+xcheri,+cap-mode,+f -o - < %s | FileCheck %s + +%struct.pair = type { i32, i32 } + +; Function Attrs: argmemonly nounwind +declare void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* nocapture writeonly, i8 addrspace(200)* nocapture readonly, i64, i1) +declare void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* nocapture writeonly, i8 addrspace(200)* nocapture readonly, i64, i1) + +; Without a no_preserve_tags attribute we always call memcpy. In this case we +; don't know whether the type might actually contain capabilities (e.g. unions). +define void @memcpy_no_attr(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +; CHECK-LABEL: memcpy_no_attr: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: cincoffset csp, csp, -16 +; CHECK-NEXT: csc cra, 8(csp) # 8-byte Folded Spill +; CHECK-NEXT: addi a2, zero, 16 +; CHECK-NEXT: mv a3, zero +; CHECK-NEXT: ccall memcpy +; CHECK-NEXT: clc cra, 8(csp) # 8-byte Folded Reload +; CHECK-NEXT: cincoffset csp, csp, 16 +; CHECK-NEXT: cret +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 4 %a_i8, i8 addrspace(200)* align 4 %b_i8, i64 16, i1 false) + ret void +} + +define void @memmove_no_attr(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +; CHECK-LABEL: memmove_no_attr: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: cincoffset csp, csp, -16 +; CHECK-NEXT: csc cra, 8(csp) # 8-byte Folded Spill +; CHECK-NEXT: addi a2, zero, 16 +; CHECK-NEXT: mv a3, zero +; CHECK-NEXT: ccall memmove +; CHECK-NEXT: clc cra, 8(csp) # 8-byte Folded Reload +; CHECK-NEXT: cincoffset csp, csp, 16 +; CHECK-NEXT: cret +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 4 %a_i8, i8 addrspace(200)* align 4 %b_i8, i64 16, i1 false) + ret void +} + +; We have to emit a call if the intrinsic has must_preserve_cheri_tags: +define void @memcpy_must_preserve(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +; CHECK-LABEL: memcpy_must_preserve: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: cincoffset csp, csp, -16 +; CHECK-NEXT: csc cra, 8(csp) # 8-byte Folded Spill +; CHECK-NEXT: addi a2, zero, 16 +; CHECK-NEXT: mv a3, zero +; CHECK-NEXT: ccall memcpy +; CHECK-NEXT: clc cra, 8(csp) # 8-byte Folded Reload +; CHECK-NEXT: cincoffset csp, csp, 16 +; CHECK-NEXT: cret +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 4 %a_i8, i8 addrspace(200)* align 4 %b_i8, i64 16, i1 false) must_preserve_cheri_tags + ret void +} + +define void @memmove_must_preserve(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +; CHECK-LABEL: memmove_must_preserve: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: cincoffset csp, csp, -16 +; CHECK-NEXT: csc cra, 8(csp) # 8-byte Folded Spill +; CHECK-NEXT: addi a2, zero, 16 +; CHECK-NEXT: mv a3, zero +; CHECK-NEXT: ccall memmove +; CHECK-NEXT: clc cra, 8(csp) # 8-byte Folded Reload +; CHECK-NEXT: cincoffset csp, csp, 16 +; CHECK-NEXT: cret +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 4 %a_i8, i8 addrspace(200)* align 4 %b_i8, i64 16, i1 false) must_preserve_cheri_tags + ret void +} + +; We should be able to inline the call memcpy/memmove if the intrinsic has no_preserve_cheri_tags: +; TODO: we should be able to elide this memcpy call +define void @memcpy_no_preserve(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +; CHECK-LABEL: memcpy_no_preserve: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: cincoffset csp, csp, -16 +; CHECK-NEXT: csc cra, 8(csp) +; CHECK-NEXT: .LBB4_1: # %entry +; CHECK-NEXT: # Label of block must be emitted +; CHECK-NEXT: auipcc ca4, %captab_pcrel_hi(memcpy) +; CHECK-NEXT: clc ca4, %pcrel_lo(.LBB4_1)(ca4) +; CHECK-NEXT: addi a2, zero, 16 +; CHECK-NEXT: mv a3, zero +; CHECK-NEXT: cjalr ca4 +; CHECK-NEXT: clc cra, 8(csp) +; CHECK-NEXT: cincoffset csp, csp, 16 +; CHECK-NEXT: cret +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 4 %a_i8, i8 addrspace(200)* align 4 %b_i8, i64 16, i1 false) no_preserve_cheri_tags + ret void +} + +define void @memmove_no_preserve(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +; CHECK-LABEL: memmove_no_preserve: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: cincoffset csp, csp, -16 +; CHECK-NEXT: csc cra, 8(csp) +; CHECK-NEXT: .LBB5_1: # %entry +; CHECK-NEXT: # Label of block must be emitted +; CHECK-NEXT: auipcc ca4, %captab_pcrel_hi(memmove) +; CHECK-NEXT: clc ca4, %pcrel_lo(.LBB5_1)(ca4) +; CHECK-NEXT: addi a2, zero, 16 +; CHECK-NEXT: mv a3, zero +; CHECK-NEXT: cjalr ca4 +; CHECK-NEXT: clc cra, 8(csp) +; CHECK-NEXT: cincoffset csp, csp, 16 +; CHECK-NEXT: cret +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 4 %a_i8, i8 addrspace(200)* align 4 %b_i8, i64 16, i1 false) no_preserve_cheri_tags + ret void +} diff --git a/llvm/test/CodeGen/CHERI-Generic/RISCV64/memcpy-no-preserve-tags-attr.ll b/llvm/test/CodeGen/CHERI-Generic/RISCV64/memcpy-no-preserve-tags-attr.ll new file mode 100644 index 000000000000..29994eb97dd3 --- /dev/null +++ b/llvm/test/CodeGen/CHERI-Generic/RISCV64/memcpy-no-preserve-tags-attr.ll @@ -0,0 +1,126 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes --force-update +; DO NOT EDIT -- This file was generated from test/CodeGen/CHERI-Generic/Inputs/memcpy-no-preserve-tags-attr.ll +; Check that the no_preserve_tags annotation on memcpy/memmove intrinsics allows +; use to inline struct copies >= capability size. +; RUN: llc -mtriple=riscv64 --relocation-model=pic -target-abi l64pc128d -mattr=+xcheri,+cap-mode,+f,+d -o - < %s | FileCheck %s + +%struct.pair = type { i64, i64 } + +; Function Attrs: argmemonly nounwind +declare void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* nocapture writeonly, i8 addrspace(200)* nocapture readonly, i64, i1) +declare void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* nocapture writeonly, i8 addrspace(200)* nocapture readonly, i64, i1) + +; Without a no_preserve_tags attribute we always call memcpy. In this case we +; don't know whether the type might actually contain capabilities (e.g. unions). +define void @memcpy_no_attr(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +; CHECK-LABEL: memcpy_no_attr: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: cincoffset csp, csp, -16 +; CHECK-NEXT: csc cra, 0(csp) # 16-byte Folded Spill +; CHECK-NEXT: addi a2, zero, 16 +; CHECK-NEXT: ccall memcpy +; CHECK-NEXT: clc cra, 0(csp) # 16-byte Folded Reload +; CHECK-NEXT: cincoffset csp, csp, 16 +; CHECK-NEXT: cret +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 %a_i8, i8 addrspace(200)* align 8 %b_i8, i64 16, i1 false) + ret void +} + +define void @memmove_no_attr(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +; CHECK-LABEL: memmove_no_attr: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: cincoffset csp, csp, -16 +; CHECK-NEXT: csc cra, 0(csp) # 16-byte Folded Spill +; CHECK-NEXT: addi a2, zero, 16 +; CHECK-NEXT: ccall memmove +; CHECK-NEXT: clc cra, 0(csp) # 16-byte Folded Reload +; CHECK-NEXT: cincoffset csp, csp, 16 +; CHECK-NEXT: cret +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 8 %a_i8, i8 addrspace(200)* align 8 %b_i8, i64 16, i1 false) + ret void +} + +; We have to emit a call if the intrinsic has must_preserve_cheri_tags: +define void @memcpy_must_preserve(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +; CHECK-LABEL: memcpy_must_preserve: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: cincoffset csp, csp, -16 +; CHECK-NEXT: csc cra, 0(csp) # 16-byte Folded Spill +; CHECK-NEXT: addi a2, zero, 16 +; CHECK-NEXT: ccall memcpy +; CHECK-NEXT: clc cra, 0(csp) # 16-byte Folded Reload +; CHECK-NEXT: cincoffset csp, csp, 16 +; CHECK-NEXT: cret +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 %a_i8, i8 addrspace(200)* align 8 %b_i8, i64 16, i1 false) must_preserve_cheri_tags + ret void +} + +define void @memmove_must_preserve(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +; CHECK-LABEL: memmove_must_preserve: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: cincoffset csp, csp, -16 +; CHECK-NEXT: csc cra, 0(csp) # 16-byte Folded Spill +; CHECK-NEXT: addi a2, zero, 16 +; CHECK-NEXT: ccall memmove +; CHECK-NEXT: clc cra, 0(csp) # 16-byte Folded Reload +; CHECK-NEXT: cincoffset csp, csp, 16 +; CHECK-NEXT: cret +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 8 %a_i8, i8 addrspace(200)* align 8 %b_i8, i64 16, i1 false) must_preserve_cheri_tags + ret void +} + +; We should be able to inline the call memcpy/memmove if the intrinsic has no_preserve_cheri_tags: +; TODO: we should be able to elide this memcpy call +define void @memcpy_no_preserve(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +; CHECK-LABEL: memcpy_no_preserve: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: cincoffset csp, csp, -16 +; CHECK-NEXT: csc cra, 0(csp) +; CHECK-NEXT: .LBB4_1: # %entry +; CHECK-NEXT: # Label of block must be emitted +; CHECK-NEXT: auipcc ca3, %captab_pcrel_hi(memcpy) +; CHECK-NEXT: clc ca3, %pcrel_lo(.LBB4_1)(ca3) +; CHECK-NEXT: addi a2, zero, 16 +; CHECK-NEXT: cjalr ca3 +; CHECK-NEXT: clc cra, 0(csp) +; CHECK-NEXT: cincoffset csp, csp, 16 +; CHECK-NEXT: cret +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 %a_i8, i8 addrspace(200)* align 8 %b_i8, i64 16, i1 false) no_preserve_cheri_tags + ret void +} + +define void @memmove_no_preserve(%struct.pair addrspace(200)* %a, %struct.pair addrspace(200)* %b) addrspace(200) nounwind { +; CHECK-LABEL: memmove_no_preserve: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: cincoffset csp, csp, -16 +; CHECK-NEXT: csc cra, 0(csp) +; CHECK-NEXT: .LBB5_1: # %entry +; CHECK-NEXT: # Label of block must be emitted +; CHECK-NEXT: auipcc ca3, %captab_pcrel_hi(memmove) +; CHECK-NEXT: clc ca3, %pcrel_lo(.LBB5_1)(ca3) +; CHECK-NEXT: addi a2, zero, 16 +; CHECK-NEXT: cjalr ca3 +; CHECK-NEXT: clc cra, 0(csp) +; CHECK-NEXT: cincoffset csp, csp, 16 +; CHECK-NEXT: cret +entry: + %a_i8 = bitcast %struct.pair addrspace(200)* %a to i8 addrspace(200)* + %b_i8 = bitcast %struct.pair addrspace(200)* %b to i8 addrspace(200)* + call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 8 %a_i8, i8 addrspace(200)* align 8 %b_i8, i64 16, i1 false) no_preserve_cheri_tags + ret void +}