Skip to content

Device compatibility for exception throwing code using assertions - #2038

Open
EkremA wants to merge 16 commits into
developfrom
exception_to_assert
Open

EkremA wants to merge 16 commits into
developfrom
exception_to_assert

Conversation

@EkremA

@EkremA EkremA commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Added compatibility for code possibly throwing exceptions on device. In LLVM C++ thrown exceptions are handled with the functions __cxa_throw and __cxa_allocate_exception in LLVM IR. However if we run such code on GPU devices, we may not have access to appropriate implementations of these exception functions.

This PR suggests a compilation pass, which parses device IR for calls and invokes to __cxa_throw and replacing them with an assertion __acpp_sscp_assert_fail. Then, the function __cxa_allocate_exception is removed altogether as any allocation of exception structures would no longer be required.

Additionally, the fix of a typo in one of the CI workflow files is suggested.

@illuhad illuhad left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've only read the beginning of the pass -- looks like this needs some general cleanups first :)
Let me know once it's ready to review.

*/
// SPDX-License-Identifier: BSD-2-Clause
#ifndef ACPP_REFLECTION_EXCEPTION_TO_ASSERTION_PASS_HPP
#define ACPP_REFLECTION_EXCEPTION_TO_ASSERTION_PASS_HPP

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it framed as part of reflection and in reflection subdirectory? I don't think it has anything to do with those. Probably it should go into compiler/sscp?


static const char* CXAThrow = "__cxa_throw";
static const char* ACPPSSCPAssertFail = "__acpp_sscp_assert_fail";
//static const char* GlibcxxAssertFailBuiltinName = "__acpp_sscp_glibcxx_assert_fail";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this -> remove

Comment thread src/compiler/AdaptiveCppLlvmPasses.cpp Outdated
#ifdef HIPSYCL_WITH_DEVICE_EXCEPTIONS
#include "hipSYCL/compiler/reflection/ExceptionToAssertionPass.hpp"
// #include "hipSYCL/compiler/reflection/FunctionNameExtractionPass.hpp"
#endif

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this include here? The pass is only used inside TargetSeparationPass, right?

Comment thread src/compiler/CMakeLists.txt Outdated
if(NOT DEFINED HIPSYCL_DEBUG_LEVEL)
message(FATAL_ERROR "HIPSYCL_DEBUG_LEVEL was not defined by the top-level CMakeLists.txt.")
if(NOT HIPSYCL_DEBUG_LEVEL)
set(HIPSYCL_DEBUG_LEVEL 2 CACHE INTEGER)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a change for debugging purposes - please remove from this PR!

Comment thread src/compiler/CMakeLists.txt Outdated

set(DEVICE_EXCEPTIONS
reflection/ExceptionToAssertionPass.cpp)
add_definitions(-DHIPSYCL_WITH_DEVICE_EXCEPTIONS)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just add it to the existing SSCP_COMPILER sources above. No need to create something new and even have a macro for it.

// detect if C++ throws occur in source code
llvm::Function* oldcxaThrow = nullptr;
for(auto& F : M)
if(F.getName().contains(CXAThrow)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need contains or can we just compare for equality?

oldcxaThrow = &F;
}
if (!oldcxaThrow) {
llvm::errs() << "Function " << CXAThrow << " not found. Assuming there is no use of exceptions in source code.\n";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error messages should be prefixed with the pass that produces them. E.g. llvm::errs() << "ExceptionToAssertionPas: ..."

Also, why would we even error in this case? Isn't it expected that in most cases there's no exception in device code?

for(auto& F : M)
if(F.getName().contains(CXAThrow)) {
oldcxaThrow = &F;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That whole segment I think can be replaced with M.getFunction(CXAThrow)

#if LLVM_VERSION_MAJOR >= 16
llvmCharType = llvm::PointerType::get(M.getContext(), 0);
#else
llvmCharType = llvm::PointerType::get(llvm::Type::getInt8Ty(M.getContext()), 0);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No indentation inside preprocessor if/else

llvmCharType = llvm::PointerType::get(llvm::Type::getInt8Ty(M.getContext()), 0);
#endif
if(auto* F = M.getFunction(ACPPSSCPAssertFail))
ACPPSSCPAssertFailDeclaration=F;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That if seems unnecessary.... why not llvm::Function* ACPPSSCPAssertFailDeclaration = M.getFunction(...) ?

@illuhad

illuhad commented Apr 10, 2026

Copy link
Copy Markdown
Collaborator

Also, please add some LIT test cases for this feature :-)

EkremA added 7 commits April 13, 2026 21:56
@EkremA

EkremA commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Code has been slightly restructured. getAssertFailBuiltin is a static member function of DeviceAssertPass now. Added LIT tests which check for absence of function symbol __cxa_throw using the utility acpp-hcf-tool, see hcfinspect.sh.

PR is ready for review.

}
}
}
if(auto *Call = llvm::dyn_cast<llvm::CallInst>(&Inst)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This indentation is misleading. It looks like the CallInst check is inside the InvokeInst check (which would not make any sense), but I think it's actually outside.

}
// replace all uses of invoke @__cxa_throw with unreachable instruction and erase the invoke instruction
for (int i=0; i<cxaThrowInvokes.size(); i++) {
UnreachableForCXAInvokes[i]->replaceAllUsesWith(cxaThrowInvokes[i]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this the wrong way around? This looks like you are replacing the unreachables with the cxa throw invokes.

Also, it feels a little overcomplicated to store the unreachables in an array. Couldn't you just:

auto *Unreachable = new llvm::UnreachableInst(M.getContext(), cxaThrowInvokes[i]); 
cxaThrowInvokes[i]->replaceAllUsesWith(Unreachable);

ACPPSSCPAssertFailArgs.push_back(builder.CreateGlobalStringPtr(functionName));

auto *Call = llvm::CallInst::Create(DeviceAssertPass::getAssertFailBuiltin(M), ACPPSSCPAssertFailArgs);
Call->insertBefore(cxaThrowCalls[i]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section is duplicated from the invoke case...can't you create a function with this code to share the code between both cases?





Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need all those empty lines? :D

cxaAllocExcArgs.push_back(*cxaAllocExcIt);
i++;
}
cxaAllocExcCallsArgs.push_back(cxaAllocExcArgs);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of copying out all arguments, can't you just directly access the first argument with callInst->getArgOperand(0)?

static_cast<int>(ConstInt->getZExtValue()));
}
}
auto* allocainstruction = new llvm::AllocaInst(cxaAllocExcArgs[0]->getType(), 0, allocSize, "exception_alloca", Call);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This identifier name does not look like LLVM coding style to me.

//remove all calls to __cxa_allocate_exception
for (auto *Call : cxaAllocExcCalls2) {
if(Call!=nullptr)
Call->eraseFromParent();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need a separate iteration to erase? Can't we combine that with the RAUW step?

Comment on lines +296 to +297
set(REFLECTION_BUILTINS "")
endif()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block seems to be copy-pasted from above....please remove!

${SSCP_COMPILER}
${STDPAR_COMPILER}
${REFLECTION_BUILTINS}
${DEVICE_EXCEPTIONS}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer needed, right?

});
q.wait();
std::cout << *data << std::endl;
//CHECK-NOT: CXATHROWHIT

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good if we could also check for ACPPASSERTFAILHIT here, which I think you're not actually using anywhere?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants