From d009cd45ad56b162796a7662cc2b5b570b21ab3d Mon Sep 17 00:00:00 2001 From: bwlodarcz Date: Fri, 13 Oct 2023 12:33:06 +0200 Subject: [PATCH] Add support for ISubBorrow SPIRV instruction (#2168) This commit implements bidirectional translation of the llvm.usub.with.overflow and the ISubBorrow intrinsic. Intrinsic llvm.usub.with.overflow returns struct which second element have a type of i1. The llvm type i1 is, in llvm-spirv, directly translated to BoolType. SPIRV specification requires that the composite which returns from ISubBorrow needs to have both elements of the same type. In result, current implementation is not compliant and should be considered temporary. --- lib/SPIRV/SPIRVReader.cpp | 8 +++ lib/SPIRV/SPIRVWriter.cpp | 6 +++ lib/SPIRV/libSPIRV/SPIRVEntry.h | 1 - lib/SPIRV/libSPIRV/SPIRVInstruction.h | 1 + lib/SPIRV/libSPIRV/SPIRVOpCode.h | 2 +- test/llvm-intrinsics/usub.with.overflow.ll | 58 ++++++++++++++++++++++ 6 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 test/llvm-intrinsics/usub.with.overflow.ll diff --git a/lib/SPIRV/SPIRVReader.cpp b/lib/SPIRV/SPIRVReader.cpp index 1ec40ed899..df6197658f 100644 --- a/lib/SPIRV/SPIRVReader.cpp +++ b/lib/SPIRV/SPIRVReader.cpp @@ -2513,6 +2513,14 @@ Value *SPIRVToLLVM::transValueWithoutDecoration(SPIRVValue *BV, Function *F, transValue(BC->getOperand(0), F, BB), transValue(BC->getOperand(1), F, BB))); } + case OpISubBorrow: { + IRBuilder Builder(BB); + auto *BC = static_cast(BV); + return mapValue(BV, Builder.CreateBinaryIntrinsic( + Intrinsic::usub_with_overflow, + transValue(BC->getOperand(0), F, BB), + transValue(BC->getOperand(1), F, BB))); + } case OpGetKernelWorkGroupSize: case OpGetKernelPreferredWorkGroupSizeMultiple: return mapValue( diff --git a/lib/SPIRV/SPIRVWriter.cpp b/lib/SPIRV/SPIRVWriter.cpp index 20fd2ccc81..fc3ee23fea 100644 --- a/lib/SPIRV/SPIRVWriter.cpp +++ b/lib/SPIRV/SPIRVWriter.cpp @@ -3485,6 +3485,7 @@ bool LLVMToSPIRVBase::isKnownIntrinsic(Intrinsic::ID Id) { case Intrinsic::trap: case Intrinsic::ubsantrap: case Intrinsic::uadd_with_overflow: + case Intrinsic::usub_with_overflow: case Intrinsic::arithmetic_fence: case Intrinsic::masked_gather: case Intrinsic::masked_scatter: @@ -3974,6 +3975,11 @@ SPIRVValue *LLVMToSPIRVBase::transIntrinsicInst(IntrinsicInst *II, transValue(II->getArgOperand(0), BB), transValue(II->getArgOperand(1), BB), BB); } + case Intrinsic::usub_with_overflow: { + return BM->addBinaryInst(OpISubBorrow, transType(II->getType()), + transValue(II->getArgOperand(0), BB), + transValue(II->getArgOperand(1), BB), BB); + } case Intrinsic::memset: { // Generally there is no direct mapping of memset to SPIR-V. But it turns // out that memset is emitted by Clang for initialization in default diff --git a/lib/SPIRV/libSPIRV/SPIRVEntry.h b/lib/SPIRV/libSPIRV/SPIRVEntry.h index c030346e95..e5475a2be9 100644 --- a/lib/SPIRV/libSPIRV/SPIRVEntry.h +++ b/lib/SPIRV/libSPIRV/SPIRVEntry.h @@ -1009,7 +1009,6 @@ _SPIRV_OP(ImageDrefGather) _SPIRV_OP(QuantizeToF16) _SPIRV_OP(ArrayLength) _SPIRV_OP(OuterProduct) -_SPIRV_OP(ISubBorrow) _SPIRV_OP(SMulExtended) _SPIRV_OP(UMulExtended) _SPIRV_OP(DPdx) diff --git a/lib/SPIRV/libSPIRV/SPIRVInstruction.h b/lib/SPIRV/libSPIRV/SPIRVInstruction.h index 17a753528c..f3d2232098 100644 --- a/lib/SPIRV/libSPIRV/SPIRVInstruction.h +++ b/lib/SPIRV/libSPIRV/SPIRVInstruction.h @@ -668,6 +668,7 @@ _SPIRV_OP(IAdd) _SPIRV_OP(IAddCarry) _SPIRV_OP(FAdd) _SPIRV_OP(ISub) +_SPIRV_OP(ISubBorrow) _SPIRV_OP(FSub) _SPIRV_OP(IMul) _SPIRV_OP(FMul) diff --git a/lib/SPIRV/libSPIRV/SPIRVOpCode.h b/lib/SPIRV/libSPIRV/SPIRVOpCode.h index 53c0f626d5..a4e0a7f50c 100644 --- a/lib/SPIRV/libSPIRV/SPIRVOpCode.h +++ b/lib/SPIRV/libSPIRV/SPIRVOpCode.h @@ -71,7 +71,7 @@ inline bool isAtomicOpCode(Op OpCode) { } inline bool isBinaryOpCode(Op OpCode) { return ((unsigned)OpCode >= OpIAdd && (unsigned)OpCode <= OpFMod) || - OpCode == OpDot || OpCode == OpIAddCarry; + OpCode == OpDot || OpCode == OpIAddCarry || OpCode == OpISubBorrow; } inline bool isShiftOpCode(Op OpCode) { diff --git a/test/llvm-intrinsics/usub.with.overflow.ll b/test/llvm-intrinsics/usub.with.overflow.ll new file mode 100644 index 0000000000..74588ee7e3 --- /dev/null +++ b/test/llvm-intrinsics/usub.with.overflow.ll @@ -0,0 +1,58 @@ +; RUN: llvm-as %s -o %t.bc +; RUN: llvm-spirv %t.bc -spirv-text -o - | FileCheck --check-prefix CHECK-SPIRV %s +; RUN: llvm-spirv %t.bc -o %t.spv +; Current implementation doesn't comply with specification and should be fixed in future. +; TODO: spirv-val %t.spv +; RUN: llvm-spirv -r %t.spv -o %t.rev.bc +; RUN: llvm-dis %t.rev.bc -o - | FileCheck --check-prefix CHECK-LLVM %s + +target triple = "spir64-unknown-unknown" + + +; CHECK-SPIRV: TypeInt [[#I16TYPE:]] 16 +; CHECK-SPIRV: TypeInt [[#I32TYPE:]] 32 +; CHECK-SPIRV: TypeInt [[#I64TYPE:]] 64 +; CHECK-SPIRV: TypeBool [[#BTYPE:]] +; CHECK-SPIRV: TypeStruct [[#S0TYPE:]] [[#I16TYPE]] [[#BTYPE]] +; CHECK-SPIRV: TypeStruct [[#S1TYPE:]] [[#I32TYPE]] [[#BTYPE]] +; CHECK-SPIRV: TypeStruct [[#S2TYPE:]] [[#I64TYPE]] [[#BTYPE]] +; CHECK-SPIRV: TypeVector [[#V4XI32TYPE:]] [[#I32TYPE]] 4 +; CHECK-SPIRV: TypeVector [[#V4XBTYPE:]] [[#BTYPE]] 4 +; CHECK-SPIRV: TypeStruct [[#S3TYPE:]] [[#V4XI32TYPE]] [[#V4XBTYPE]] +; CHECK-SPIRV: ISubBorrow [[#S0TYPE]] +; CHECK-SPIRV: ISubBorrow [[#S1TYPE]] +; CHECK-SPIRV: ISubBorrow [[#S2TYPE]] +; CHECK-SPIRV: ISubBorrow [[#S3TYPE]] +; CHECK-LLVM: call { i16, i1 } @llvm.usub.with.overflow.i16(i16 %a, i16 %b) +; CHECK-LLVM: call { i32, i1 } @llvm.usub.with.overflow.i32(i32 %a, i32 %b) +; CHECK-LLVM: call { i64, i1 } @llvm.usub.with.overflow.i64(i64 %a, i64 %b) +; CHECK-LLVM: call { <4 x i32>, <4 x i1> } @llvm.usub.with.overflow.v4i32(<4 x i32> %a, <4 x i32> %b) + +define spir_func void @test_usub_with_overflow_i16(i16 %a, i16 %b) { +entry: + %res = call {i16, i1} @llvm.usub.with.overflow.i16(i16 %a, i16 %b) + ret void +} + +define spir_func void @test_usub_with_overflow_i32(i32 %a, i32 %b) { +entry: + %res = call {i32, i1} @llvm.usub.with.overflow.i32(i32 %a, i32 %b) + ret void +} + +define spir_func void @test_usub_with_overflow_i64(i64 %a, i64 %b) { +entry: + %res = call {i64, i1} @llvm.usub.with.overflow.i64(i64 %a, i64 %b) + ret void +} + +define spir_func void @test_usub_with_overflow_v4i32(<4 x i32> %a, <4 x i32> %b) { +entry: + %res = call {<4 x i32>, <4 x i1>} @llvm.usub.with.overflow.v4i32(<4 x i32> %a, <4 x i32> %b) + ret void +} + +declare {i16, i1} @llvm.usub.with.overflow.i16(i16 %a, i16 %b) +declare {i32, i1} @llvm.usub.with.overflow.i32(i32 %a, i32 %b) +declare {i64, i1} @llvm.usub.with.overflow.i64(i64 %a, i64 %b) +declare {<4 x i32>, <4 x i1>} @llvm.usub.with.overflow.v4i32(<4 x i32> %a, <4 x i32> %b)