From d474283726c86b57d69ce0adc499e9234ee74722 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 | 10 +++- 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, 75 insertions(+), 3 deletions(-) create mode 100644 test/llvm-intrinsics/usub.with.overflow.ll diff --git a/lib/SPIRV/SPIRVReader.cpp b/lib/SPIRV/SPIRVReader.cpp index 8c1f418fd6..3a05846c5b 100644 --- a/lib/SPIRV/SPIRVReader.cpp +++ b/lib/SPIRV/SPIRVReader.cpp @@ -2340,13 +2340,21 @@ Value *SPIRVToLLVM::transValueWithoutDecoration(SPIRVValue *BV, Function *F, return mapValue(BV, transRelational(static_cast(BV), BB)); case OpIAddCarry: { - IRBuilder Builder(BB); + IRBuilder<> Builder(BB); auto *BC = static_cast(BV); return mapValue(BV, Builder.CreateBinaryIntrinsic( Intrinsic::uadd_with_overflow, 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 5fef4b5949..4df81d846b 100644 --- a/lib/SPIRV/SPIRVWriter.cpp +++ b/lib/SPIRV/SPIRVWriter.cpp @@ -2538,6 +2538,7 @@ bool LLVMToSPIRVBase::isKnownIntrinsic(Intrinsic::ID Id) { case Intrinsic::trap: case Intrinsic::arithmetic_fence: case Intrinsic::uadd_with_overflow: + case Intrinsic::usub_with_overflow: return true; default: // Unknown intrinsics' declarations should always be translated @@ -2955,6 +2956,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 b5bd87b460..e7398c7a7c 100644 --- a/lib/SPIRV/libSPIRV/SPIRVEntry.h +++ b/lib/SPIRV/libSPIRV/SPIRVEntry.h @@ -1005,7 +1005,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 1ea7c5af1f..0fa6327fa3 100644 --- a/lib/SPIRV/libSPIRV/SPIRVInstruction.h +++ b/lib/SPIRV/libSPIRV/SPIRVInstruction.h @@ -663,6 +663,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 35e48044b8..288a2c0fde 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)