From 3c5e00e323c1b83a82a3128e390644f4dfde84b0 Mon Sep 17 00:00:00 2001 From: Kalman Szenes Date: Sat, 15 Aug 2026 21:33:42 -0400 Subject: [PATCH 1/3] fix: add automatic checking to `runtime_cal` --- doc/releases/changelog-dev.md | 3 + .../Transport/Transforms/TransportToLLVM.cpp | 78 +++++++---- .../Transport/ConvertTransportToLLVM.mlir | 15 +++ runtime/include/TransportCAPI.h | 7 + runtime/lib/transport/TransportCAPI.cpp | 23 +++- runtime/lib/transport/TransportWrappers.cpp | 123 ++++++++++++------ runtime/tests/Test_Transport.cpp | 22 ++++ 7 files changed, 199 insertions(+), 72 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 21dcfae48f..195426e6ff 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -27,6 +27,9 @@

Improvements 🛠

+* Add checking of the pointer and status code from `runtime_call`. + [(#XXXX)](https://github.com/PennyLaneAI/catalyst/pull/XXXX) + * a PennyLane `Backline` is serialized to the `catalyst.backline` module attribute and compiled through the transport passes. [(#3068)](https://github.com/PennyLaneAI/catalyst/pull/3068) diff --git a/mlir/lib/Transport/Transforms/TransportToLLVM.cpp b/mlir/lib/Transport/Transforms/TransportToLLVM.cpp index 679cc9773f..410d98c539 100644 --- a/mlir/lib/Transport/Transforms/TransportToLLVM.cpp +++ b/mlir/lib/Transport/Transforms/TransportToLLVM.cpp @@ -88,6 +88,25 @@ Value globalStr(ConversionPatternRewriter &rewriter, Location loc, ModuleOp mod, ArrayRef{0, 0}, LLVM::GEPNoWrapFlags::inbounds); } +void emitCheckedStatus(ConversionPatternRewriter &rewriter, Location loc, ModuleOp mod, + StringRef name, ArrayRef paramTys, ValueRange args, StringRef what) { + auto *ctx = rewriter.getContext(); + Value rc = emitCall(rewriter, loc, mod, name, paramTys, i32Ty(ctx), args); + Value msg = globalStr(rewriter, loc, mod, "transport_check_", what); + emitCall(rewriter, loc, mod, "__catalyst__transport__check", {i32Ty(ctx), ptrTy(ctx)}, Type(), + {rc, msg}); +} + +Value emitCheckedSession(ConversionPatternRewriter &rewriter, Location loc, ModuleOp mod, + StringRef name, ArrayRef paramTys, ValueRange args, StringRef what) { + auto *ctx = rewriter.getContext(); + Value s = emitCall(rewriter, loc, mod, name, paramTys, ptrTy(ctx), args); + Value msg = globalStr(rewriter, loc, mod, "transport_session_", what); + emitCall(rewriter, loc, mod, "__catalyst__transport__check_session", {ptrTy(ctx), ptrTy(ctx)}, + Type(), {s, msg}); + return s; +} + Value constInt(ConversionPatternRewriter &rewriter, Location loc, Type ty, int64_t v) { return LLVM::ConstantOp::create(rewriter, loc, ty, rewriter.getIntegerAttr(ty, v)); } @@ -121,9 +140,9 @@ struct CreateLowering : public OpConversionPattern { Value key = globalStr(rewriter, op.getLoc(), mod, "transport_key_", op.getKey()); Value role = constInt(rewriter, op.getLoc(), i32Ty(ctx), static_cast(sessTy.getRole())); - Value s = emitCall(rewriter, op.getLoc(), mod, "__catalyst__transport__create", - {ptrTy(ctx), ptrTy(ctx), i32Ty(ctx), ptrTy(ctx)}, ptrTy(ctx), - {lib, cfg, role, key}); + Value s = emitCheckedSession(rewriter, op.getLoc(), mod, "__catalyst__transport__create", + {ptrTy(ctx), ptrTy(ctx), i32Ty(ctx), ptrTy(ctx)}, + {lib, cfg, role, key}, "create"); rewriter.replaceOp(op, s); return success(); } @@ -150,9 +169,9 @@ template struct ConnectLoweringBase : public OpConver {adaptor.getSession(), peer, port}); rewriter.replaceOp(op, r); } else { - emitCall(rewriter, op.getLoc(), mod, "__catalyst__transport__connect", - {ptrTy(ctx), ptrTy(ctx), IntegerType::get(ctx, 16)}, i32Ty(ctx), - {adaptor.getSession(), peer, port}); + emitCheckedStatus(rewriter, op.getLoc(), mod, "__catalyst__transport__connect", + {ptrTy(ctx), ptrTy(ctx), IntegerType::get(ctx, 16)}, + {adaptor.getSession(), peer, port}, "connect"); rewriter.eraseOp(op); } return success(); @@ -174,8 +193,8 @@ struct ExchangeKeysLoweringBase : public OpConversionPattern { {ptrTy(ctx)}, i64Ty(ctx), {adaptor.getSession()}); rewriter.replaceOp(op, r); } else { - emitCall(rewriter, op.getLoc(), mod, "__catalyst__transport__exchange_keys", - {ptrTy(ctx)}, i32Ty(ctx), {adaptor.getSession()}); + emitCheckedStatus(rewriter, op.getLoc(), mod, "__catalyst__transport__exchange_keys", + {ptrTy(ctx)}, {adaptor.getSession()}, "exchange_keys"); rewriter.eraseOp(op); } return success(); @@ -189,8 +208,8 @@ struct AwaitLowering : public OpConversionPattern { LogicalResult matchAndRewrite(AwaitOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { auto *ctx = op.getContext(); - emitCall(rewriter, op.getLoc(), moduleOf(op), "__catalyst__transport__await", {i64Ty(ctx)}, - i32Ty(ctx), {adaptor.getToken()}); + emitCheckedStatus(rewriter, op.getLoc(), moduleOf(op), "__catalyst__transport__await", + {i64Ty(ctx)}, {adaptor.getToken()}, "await"); rewriter.eraseOp(op); return success(); } @@ -203,8 +222,9 @@ struct EstablishChannelLowering : public OpConversionPattern auto *ctx = op.getContext(); Value transport = globalStr(rewriter, op.getLoc(), moduleOf(op), "transport_kind_", op.getTransport()); - emitCall(rewriter, op.getLoc(), moduleOf(op), "__catalyst__transport__establish_channel", - {ptrTy(ctx), ptrTy(ctx)}, i32Ty(ctx), {adaptor.getSession(), transport}); + emitCheckedStatus(rewriter, op.getLoc(), moduleOf(op), + "__catalyst__transport__establish_channel", {ptrTy(ctx), ptrTy(ctx)}, + {adaptor.getSession(), transport}, "establish_channel"); rewriter.eraseOp(op); return success(); } @@ -217,8 +237,9 @@ struct SetCoprocessorFnLowering : public OpConversionPattern auto *ctx = op.getContext(); ModuleOp mod = moduleOf(op); Value sym = globalStr(rewriter, op.getLoc(), mod, "transport_coproc_fn_", op.getSymbol()); - emitCall(rewriter, op.getLoc(), mod, "__catalyst__transport__set_coprocessor_fn", - {ptrTy(ctx), ptrTy(ctx)}, i32Ty(ctx), {adaptor.getSession(), sym}); + emitCheckedStatus(rewriter, op.getLoc(), mod, "__catalyst__transport__set_coprocessor_fn", + {ptrTy(ctx), ptrTy(ctx)}, {adaptor.getSession(), sym}, + "set_coprocessor_fn"); rewriter.eraseOp(op); return success(); } @@ -232,9 +253,10 @@ struct SetMessageSizesLowering : public OpConversionPattern { Value idx = constInt(rewriter, op.getLoc(), i32Ty(ctx), op.getWorkItemIdx()); Value inB = constInt(rewriter, op.getLoc(), i64Ty(ctx), op.getInBytes()); Value outB = constInt(rewriter, op.getLoc(), i64Ty(ctx), op.getOutBytes()); - emitCall(rewriter, op.getLoc(), moduleOf(op), "__catalyst__transport__set_message_sizes", - {ptrTy(ctx), i32Ty(ctx), i64Ty(ctx), i64Ty(ctx)}, i32Ty(ctx), - {adaptor.getSession(), idx, inB, outB}); + emitCheckedStatus(rewriter, op.getLoc(), moduleOf(op), + "__catalyst__transport__set_message_sizes", + {ptrTy(ctx), i32Ty(ctx), i64Ty(ctx), i64Ty(ctx)}, + {adaptor.getSession(), idx, inB, outB}, "set_message_sizes"); rewriter.eraseOp(op); return success(); } @@ -288,9 +310,10 @@ struct StagePayloadLowering : public OpConversionPattern { auto [srcPtr, bytes] = memrefPtrAndBytes(rewriter, op.getLoc(), adaptor.getPayload(), memTy); Value decoderId = constInt(rewriter, op.getLoc(), i32Ty(ctx), op.getDecoderId()); - emitCall(rewriter, op.getLoc(), moduleOf(op), "__catalyst__transport__stage_payload", - {ptrTy(ctx), ptrTy(ctx), i64Ty(ctx), i32Ty(ctx)}, i32Ty(ctx), - {adaptor.getSession(), srcPtr, bytes, decoderId}); + emitCheckedStatus(rewriter, op.getLoc(), moduleOf(op), + "__catalyst__transport__stage_payload", + {ptrTy(ctx), ptrTy(ctx), i64Ty(ctx), i32Ty(ctx)}, + {adaptor.getSession(), srcPtr, bytes, decoderId}, "stage_payload"); rewriter.eraseOp(op); return success(); } @@ -302,8 +325,8 @@ struct PostLowering : public OpConversionPattern { ConversionPatternRewriter &rewriter) const override { auto *ctx = op.getContext(); Value idx = constInt(rewriter, op.getLoc(), i32Ty(ctx), op.getWorkItemIdx()); - emitCall(rewriter, op.getLoc(), moduleOf(op), "__catalyst__transport__post", - {ptrTy(ctx), i32Ty(ctx)}, i32Ty(ctx), {adaptor.getSession(), idx}); + emitCheckedStatus(rewriter, op.getLoc(), moduleOf(op), "__catalyst__transport__post", + {ptrTy(ctx), i32Ty(ctx)}, {adaptor.getSession(), idx}, "post"); rewriter.eraseOp(op); return success(); } @@ -326,9 +349,9 @@ struct CollectLowering : public OpConversionPattern { return rewriter.notifyMatchFailure(op, "collect dest must have identity layout"); } auto [dstPtr, bytes] = memrefPtrAndBytes(rewriter, op.getLoc(), adaptor.getDest(), memTy); - emitCall(rewriter, op.getLoc(), mod, "__catalyst__transport__collect", - {ptrTy(ctx), ptrTy(ctx), i64Ty(ctx)}, i32Ty(ctx), - {adaptor.getSession(), dstPtr, bytes}); + emitCheckedStatus(rewriter, op.getLoc(), mod, "__catalyst__transport__collect", + {ptrTy(ctx), ptrTy(ctx), i64Ty(ctx)}, + {adaptor.getSession(), dstPtr, bytes}, "collect"); rewriter.eraseOp(op); return success(); } @@ -371,8 +394,9 @@ struct GetSessionLowering : public OpConversionPattern { Value role = constInt(rewriter, op.getLoc(), i32Ty(ctx), static_cast(sessTy.getRole())); Value key = globalStr(rewriter, op.getLoc(), mod, "transport_key_", op.getKey()); - Value s = emitCall(rewriter, op.getLoc(), mod, "__catalyst__transport__get_session", - {i32Ty(ctx), ptrTy(ctx)}, ptrTy(ctx), {role, key}); + Value s = + emitCheckedSession(rewriter, op.getLoc(), mod, "__catalyst__transport__get_session", + {i32Ty(ctx), ptrTy(ctx)}, {role, key}, "get_session"); rewriter.replaceOp(op, s); return success(); } diff --git a/mlir/test/Transport/ConvertTransportToLLVM.mlir b/mlir/test/Transport/ConvertTransportToLLVM.mlir index d07473aa06..e2369e4b3a 100644 --- a/mlir/test/Transport/ConvertTransportToLLVM.mlir +++ b/mlir/test/Transport/ConvertTransportToLLVM.mlir @@ -15,6 +15,8 @@ // RUN: quantum-opt %s --convert-transport-to-llvm --split-input-file | FileCheck %s // CHECK-DAG: llvm.func @__catalyst__transport__create(!llvm.ptr, !llvm.ptr, i32, !llvm.ptr) -> !llvm.ptr +// CHECK-DAG: llvm.func @__catalyst__transport__check_session(!llvm.ptr, !llvm.ptr) +// CHECK-DAG: llvm.func @__catalyst__transport__check(i32, !llvm.ptr) // CHECK-DAG: llvm.func @__catalyst__transport__connect(!llvm.ptr, !llvm.ptr, i16) -> i32 // CHECK-DAG: llvm.func @__catalyst__transport__exchange_keys(!llvm.ptr) -> i32 // CHECK-DAG: llvm.func @__catalyst__transport__establish_channel(!llvm.ptr, !llvm.ptr) -> i32 @@ -30,22 +32,30 @@ // CHECK-LABEL: func.func @controller func.func @controller(%syndrome: memref, %correction: memref) { // CHECK: %[[S:.*]] = llvm.call @__catalyst__transport__create({{.*}}) : (!llvm.ptr, !llvm.ptr, i32, !llvm.ptr) -> !llvm.ptr + // CHECK: llvm.call @__catalyst__transport__check_session(%[[S]] %s = transport.create {backend_lib = "libbackend.so", config = "cfg"} -> !transport.session // CHECK: llvm.call @__catalyst__transport__connect(%[[S]] + // CHECK: llvm.call @__catalyst__transport__check( transport.connect %s {peer = "127.0.0.1", oob_port = 18560 : ui16} : !transport.session // CHECK: llvm.call @__catalyst__transport__exchange_keys(%[[S]]) + // CHECK: llvm.call @__catalyst__transport__check( transport.exchange_keys %s : !transport.session // CHECK: llvm.call @__catalyst__transport__establish_channel(%[[S]] + // CHECK: llvm.call @__catalyst__transport__check( transport.establish_channel %s "rdma" : !transport.session // CHECK: llvm.call @__catalyst__transport__set_message_sizes(%[[S]] + // CHECK: llvm.call @__catalyst__transport__check( transport.set_message_sizes %s {in_bytes = 8 : i64, out_bytes = 8 : i64} : !transport.session // CHECK: llvm.call @__catalyst__transport__start(%[[S]]) transport.start %s : !transport.session // CHECK: llvm.call @__catalyst__transport__stage_payload(%[[S]] + // CHECK: llvm.call @__catalyst__transport__check( // CHECK: llvm.call @__catalyst__transport__post(%[[S]] + // CHECK: llvm.call @__catalyst__transport__check( transport.stage_payload %s, %syndrome : !transport.session, memref transport.post %s : !transport.session // CHECK: llvm.call @__catalyst__transport__collect(%[[S]] + // CHECK: llvm.call @__catalyst__transport__check( transport.collect %s, %correction : !transport.session, memref // CHECK: llvm.call @__catalyst__transport__stop(%[[S]]) transport.stop %s : !transport.session @@ -60,12 +70,15 @@ func.func @controller(%syndrome: memref, %correction: memref) { // CHECK-LABEL: func.func @coprocessor func.func @coprocessor() { // CHECK: %[[C:.*]] = llvm.call @__catalyst__transport__create({{.*}}) : (!llvm.ptr, !llvm.ptr, i32, !llvm.ptr) -> !llvm.ptr + // CHECK: llvm.call @__catalyst__transport__check_session(%[[C]] %c = transport.create {backend_lib = "libbackend.so", config = "cfg"} -> !transport.session // CHECK: llvm.call @__catalyst__transport__connect_async(%[[C]] %t = transport.connect_async %c {peer = "127.0.0.1", oob_port = 18560 : ui16} : !transport.session -> !transport.token // CHECK: llvm.call @__catalyst__transport__await + // CHECK: llvm.call @__catalyst__transport__check( transport.await %t : !transport.token // CHECK: llvm.call @__catalyst__transport__set_coprocessor_fn(%[[C]], %{{.*}}) : (!llvm.ptr, !llvm.ptr) -> i32 + // CHECK: llvm.call @__catalyst__transport__check( transport.set_coprocessor_fn %c {symbol = "foo"} : !transport.session // CHECK: llvm.call @__catalyst__transport__destroy(%[[C]]) transport.destroy %c : !transport.session @@ -80,11 +93,13 @@ func.func @coprocessor() { func.func @resolve(%syndrome: memref, %correction: memref) { // CHECK: %[[R:.*]] = llvm.mlir.constant(0 : i32) : i32 // CHECK: %[[S:.*]] = llvm.call @__catalyst__transport__get_session(%[[R]], {{.*}}) : (i32, !llvm.ptr) -> !llvm.ptr + // CHECK: llvm.call @__catalyst__transport__check_session(%[[S]] %s = transport.get_session {key = "cop0"} : !transport.session // CHECK: llvm.call @__catalyst__transport__post(%[[S]] transport.stage_payload %s, %syndrome : !transport.session, memref transport.post %s : !transport.session // CHECK: llvm.call @__catalyst__transport__collect(%[[S]] + // CHECK: llvm.call @__catalyst__transport__check( transport.collect %s, %correction : !transport.session, memref return } diff --git a/runtime/include/TransportCAPI.h b/runtime/include/TransportCAPI.h index d0b3b82fe6..57c1562cf7 100644 --- a/runtime/include/TransportCAPI.h +++ b/runtime/include/TransportCAPI.h @@ -46,6 +46,13 @@ enum { CATALYST_TRANSPORT_ROLE_COPROCESSOR = 1, }; +// Abort if `rc` is not CATALYST_TRANSPORT_OK. Compiled-program adapters call this so a failed +// round cannot be mistaken for success. The C functions themselves still return `rc`. +void __catalyst__transport__check(int rc, const char *what); + +// Abort if `s` is `nullptr`. Compiled-program adapters call this after create / get_session. +void __catalyst__transport__check_session(CatalystTransportSession *s, const char *what); + // Create a session from a named backend plugin `.so` (dlopen'd by the runtime). `role` selects // which factory symbol is looked up (controller vs coprocessor). `config` is the backend's string. // Returns NULL on failure. diff --git a/runtime/lib/transport/TransportCAPI.cpp b/runtime/lib/transport/TransportCAPI.cpp index 81b6b4d459..ce42a6e529 100644 --- a/runtime/lib/transport/TransportCAPI.cpp +++ b/runtime/lib/transport/TransportCAPI.cpp @@ -31,6 +31,7 @@ #include "ConfigParser.hpp" #include "DynamicLibraryLoader.hpp" +#include "Exception.hpp" #include "Transport.hpp" #include "TransportBackend.h" #include "WireProtocol.hpp" @@ -255,6 +256,24 @@ void *resolve_coprocessor_fn_symbol(CatalystTransportSession *s, const char *sym extern "C" { +void __catalyst__transport__check(int rc, const char *what) { + if (rc == ::CATALYST_TRANSPORT_OK) { + return; + } + const std::string msg = std::string{"[transport] "} + ((what != nullptr) ? what : "call") + + " failed (" + collect_error_name(rc) + ")"; + RT_FAIL(msg.c_str()); +} + +void __catalyst__transport__check_session(CatalystTransportSession *s, const char *what) { + if (s != nullptr) { + return; + } + const std::string msg = + std::string{"[transport] "} + ((what != nullptr) ? what : "session") + ": null session"; + RT_FAIL(msg.c_str()); +} + CatalystTransportSession *__catalyst__transport__create(const char *backend_lib, const char *config, std::int32_t role, const char *key) { try { @@ -457,8 +476,8 @@ int __catalyst__transport__collect(CatalystTransportSession *s, void *reply, return s->sess->collect(replies, replies_bytes, 1); }); if (rc != CATALYST_TRANSPORT_OK) { - // The generated code discards this return value, so a failed round is otherwise silent: - // `reply` keeps whatever it held, and the caller consumes that as a valid result. + // C callers see the return code. Compiled adapters call __catalyst__transport__check so a + // failed round cannot be consumed as a valid reply. Log here so the C path is not silent. std::cerr << "[transport] collect failed (rc=" << rc << ": " << collect_error_name(rc) << "); the reply buffer was not written\n"; } diff --git a/runtime/lib/transport/TransportWrappers.cpp b/runtime/lib/transport/TransportWrappers.cpp index 200733ed98..f06008b90e 100644 --- a/runtime/lib/transport/TransportWrappers.cpp +++ b/runtime/lib/transport/TransportWrappers.cpp @@ -177,6 +177,16 @@ constexpr std::size_t I32 = sizeof(std::int32_t); constexpr std::size_t I64 = sizeof(std::int64_t); constexpr std::size_t U64 = sizeof(std::uint64_t); +int checked_status(int rc, const char *what) { + __catalyst__transport__check(rc, what); + return rc; +} + +CatalystTransportSession *checked_session(CatalystTransportSession *s, const char *what) { + __catalyst__transport__check_session(s, what); + return s; +} + } // namespace extern "C" { @@ -194,7 +204,7 @@ CatalystWrapperResult __catalyst__transport__create__wrapper(const char *buf, st Out out(U64); if (in.ok() && out) { *out.slot() = reinterpret_cast( - __catalyst__transport__create(library, config, role, key)); + checked_session(__catalyst__transport__create(library, config, role, key), "create")); } return finish(in, out); } @@ -206,8 +216,8 @@ CatalystWrapperResult __catalyst__transport__get_session__wrapper(const char *bu const char *key = in.str(); Out out(U64); if (in.ok() && out) { - *out.slot() = - reinterpret_cast(__catalyst__transport__get_session(role, key)); + *out.slot() = reinterpret_cast( + checked_session(__catalyst__transport__get_session(role, key), "get_session")); } return finish(in, out); } @@ -223,7 +233,8 @@ CatalystWrapperResult __catalyst__transport__connect__wrapper(const char *buf, s auto port = in.get(); Out out(I32); if (in.ok() && out) { - *out.slot() = __catalyst__transport__connect(session, peer, port); + *out.slot() = + checked_status(__catalyst__transport__connect(session, peer, port), "connect"); } return finish(in, out); } @@ -257,7 +268,7 @@ CatalystWrapperResult __catalyst__transport__await__wrapper(const char *buf, std auto token = in.get(); Out out(I32); if (in.ok() && out) { - *out.slot() = __catalyst__transport__await(token); + *out.slot() = checked_status(__catalyst__transport__await(token), "await"); } return finish(in, out); } @@ -268,7 +279,8 @@ CatalystWrapperResult __catalyst__transport__exchange_keys__wrapper(const char * auto *session = in.session(); Out out(I32); if (in.ok() && out) { - *out.slot() = __catalyst__transport__exchange_keys(session); + *out.slot() = + checked_status(__catalyst__transport__exchange_keys(session), "exchange_keys"); } return finish(in, out); } @@ -284,7 +296,8 @@ CatalystWrapperResult __catalyst__transport__establish_channel__wrapper(const ch const char *transport = in.str(); Out out(I32); if (in.ok() && out) { - *out.slot() = __catalyst__transport__establish_channel(session, transport); + *out.slot() = checked_status( + __catalyst__transport__establish_channel(session, transport), "establish_channel"); } return finish(in, out); } @@ -296,7 +309,8 @@ CatalystWrapperResult __catalyst__transport__set_coprocessor_fn__wrapper(const c const char *symbol = in.str(); Out out(I32); if (in.ok() && out) { - *out.slot() = __catalyst__transport__set_coprocessor_fn(session, symbol); + *out.slot() = checked_status( + __catalyst__transport__set_coprocessor_fn(session, symbol), "set_coprocessor_fn"); } return finish(in, out); } @@ -310,8 +324,9 @@ CatalystWrapperResult __catalyst__transport__set_message_sizes__wrapper(const ch auto out_bytes = in.get(); Out out(I32); if (in.ok() && out) { - *out.slot() = - __catalyst__transport__set_message_sizes(session, work_item, in_bytes, out_bytes); + *out.slot() = checked_status( + __catalyst__transport__set_message_sizes(session, work_item, in_bytes, out_bytes), + "set_message_sizes"); } return finish(in, out); } @@ -340,8 +355,10 @@ CatalystWrapperResult __catalyst__transport__start_benchmark__wrapper(const char // reserve the samples buffer with the caller's capacity auto *samples = static_cast(out.reserve(samples_bytes)); auto *rounds = out.slot(); - *status = __catalyst__transport__start_benchmark(session, iters, decoder_id, flags, samples, - samples_bytes, rounds); + *status = checked_status( + __catalyst__transport__start_benchmark(session, iters, decoder_id, flags, samples, + samples_bytes, rounds), + "start_benchmark"); } return finish(in, out); } @@ -390,14 +407,18 @@ CatalystWrapperResult __catalyst__transport__destroy__wrapper(const char *buf, s void __catalyst__transport__create__call(void **args, void **results) { put( results, 0, - reinterpret_cast(__catalyst__transport__create( - str_arg(args, 0), str_arg(args, 1), arg(args, 2), str_arg(args, 3)))); + reinterpret_cast(checked_session( + __catalyst__transport__create(str_arg(args, 0), str_arg(args, 1), + arg(args, 2), str_arg(args, 3)), + "create"))); } void __catalyst__transport__get_session__call(void **args, void **results) { - put(results, 0, - reinterpret_cast(__catalyst__transport__get_session( - arg(args, 0), str_arg(args, 1)))); + put( + results, 0, + reinterpret_cast(checked_session( + __catalyst__transport__get_session(arg(args, 0), str_arg(args, 1)), + "get_session"))); } //===----------------------------------------------------------------------===// @@ -405,9 +426,11 @@ void __catalyst__transport__get_session__call(void **args, void **results) { //===----------------------------------------------------------------------===// void __catalyst__transport__connect__call(void **args, void **results) { - put(results, 0, - __catalyst__transport__connect(session_arg(args, 0), str_arg(args, 1), - arg(args, 2))); + put( + results, 0, + checked_status(__catalyst__transport__connect(session_arg(args, 0), str_arg(args, 1), + arg(args, 2)), + "connect")); } void __catalyst__transport__connect_async__call(void **args, void **results) { @@ -417,7 +440,9 @@ void __catalyst__transport__connect_async__call(void **args, void **results) { } void __catalyst__transport__exchange_keys__call(void **args, void **results) { - put(results, 0, __catalyst__transport__exchange_keys(session_arg(args, 0))); + put(results, 0, + checked_status(__catalyst__transport__exchange_keys(session_arg(args, 0)), + "exchange_keys")); } void __catalyst__transport__exchange_keys_async__call(void **args, void **results) { @@ -425,7 +450,9 @@ void __catalyst__transport__exchange_keys_async__call(void **args, void **result } void __catalyst__transport__await__call(void **args, void **results) { - put(results, 0, __catalyst__transport__await(arg(args, 0))); + put( + results, 0, + checked_status(__catalyst__transport__await(arg(args, 0)), "await")); } //===----------------------------------------------------------------------===// @@ -433,22 +460,25 @@ void __catalyst__transport__await__call(void **args, void **results) { //===----------------------------------------------------------------------===// void __catalyst__transport__establish_channel__call(void **args, void **results) { - put( - results, 0, - __catalyst__transport__establish_channel(session_arg(args, 0), str_arg(args, 1))); + put(results, 0, + checked_status(__catalyst__transport__establish_channel(session_arg(args, 0), + str_arg(args, 1)), + "establish_channel")); } void __catalyst__transport__set_coprocessor_fn__call(void **args, void **results) { - put( - results, 0, - __catalyst__transport__set_coprocessor_fn(session_arg(args, 0), str_arg(args, 1))); + put(results, 0, + checked_status(__catalyst__transport__set_coprocessor_fn(session_arg(args, 0), + str_arg(args, 1)), + "set_coprocessor_fn")); } void __catalyst__transport__set_message_sizes__call(void **args, void **results) { put(results, 0, - __catalyst__transport__set_message_sizes( - session_arg(args, 0), arg(args, 1), - arg(args, 2), arg(args, 3))); + checked_status(__catalyst__transport__set_message_sizes( + session_arg(args, 0), arg(args, 1), + arg(args, 2), arg(args, 3)), + "set_message_sizes")); } //===----------------------------------------------------------------------===// @@ -470,21 +500,26 @@ void __catalyst__transport__reply_slot__call(void **args, void **results) { // The source is a buf: its data pointer, with the byte count as its own argument. void __catalyst__transport__stage_payload__call(void **args, void **results) { put(results, 0, - __catalyst__transport__stage_payload(session_arg(args, 0), data_of(args, 1), - arg(args, 2), - arg(args, 3))); + checked_status(__catalyst__transport__stage_payload( + session_arg(args, 0), data_of(args, 1), + arg(args, 2), arg(args, 3)), + "stage_payload")); } void __catalyst__transport__post__call(void **args, void **results) { - put( - results, 0, __catalyst__transport__post(session_arg(args, 0), arg(args, 1))); + put(results, 0, + checked_status(__catalyst__transport__post(session_arg(args, 0), + arg(args, 1)), + "post")); } // The reply is an out buffer, so it comes from results[1] while its size is argument 1. void __catalyst__transport__collect__call(void **args, void **results) { - put(results, 0, - __catalyst__transport__collect(session_arg(args, 0), data_of(results, 1), - arg(args, 1))); + put( + results, 0, + checked_status(__catalyst__transport__collect(session_arg(args, 0), data_of(results, 1), + arg(args, 1)), + "collect")); } void __catalyst__transport__last_rtt_ns__call(void **args, void **results) { @@ -498,10 +533,12 @@ void __catalyst__transport__last_rtt_ns__call(void **args, void **results) { void __catalyst__transport__start_benchmark__call(void **args, void **results) { put( results, 0, - __catalyst__transport__start_benchmark( - session_arg(args, 0), arg(args, 1), arg(args, 2), - arg(args, 3), static_cast(data_of(results, 1)), - arg(args, 4), static_cast(data_of(results, 2)))); + checked_status( + __catalyst__transport__start_benchmark( + session_arg(args, 0), arg(args, 1), arg(args, 2), + arg(args, 3), static_cast(data_of(results, 1)), + arg(args, 4), static_cast(data_of(results, 2))), + "start_benchmark")); } //===----------------------------------------------------------------------===// diff --git a/runtime/tests/Test_Transport.cpp b/runtime/tests/Test_Transport.cpp index e0f80b4908..816cbebb1c 100644 --- a/runtime/tests/Test_Transport.cpp +++ b/runtime/tests/Test_Transport.cpp @@ -17,9 +17,12 @@ #include #include "catch2/catch_test_macros.hpp" +#include "catch2/matchers/catch_matchers_string.hpp" #include "TransportCAPI.h" +using Catch::Matchers::ContainsSubstring; + namespace { CatalystTransportSession *make(std::int32_t role, const char *key) { return __catalyst__transport__create(STUB_BACKEND_PATH, "cfg", role, key); @@ -36,6 +39,25 @@ CatalystTransportSession *make_memcpy_coprocessor(const char *key) { } } // namespace +TEST_CASE("check aborts on a non-OK status and a null session", "[transport]") { + __catalyst__transport__check(CATALYST_TRANSPORT_OK, "ok"); + auto *s = make(CATALYST_TRANSPORT_ROLE_CONTROLLER, "check_live"); + REQUIRE(s != nullptr); + __catalyst__transport__check_session(s, "ok"); + __catalyst__transport__destroy(s); + + REQUIRE_THROWS_WITH(__catalyst__transport__check(CATALYST_TRANSPORT_ERR, "stage_payload"), + ContainsSubstring("stage_payload failed (error)")); + REQUIRE_THROWS_WITH(__catalyst__transport__check(CATALYST_TRANSPORT_ERR_TIMEOUT, "collect"), + ContainsSubstring("collect failed (timeout)")); + REQUIRE_THROWS_WITH(__catalyst__transport__check(CATALYST_TRANSPORT_ERR_STUCK, "collect"), + ContainsSubstring("stuck")); + REQUIRE_THROWS_WITH(__catalyst__transport__check(CATALYST_TRANSPORT_ERR_MEMORY, "post"), + ContainsSubstring("memory")); + REQUIRE_THROWS_WITH(__catalyst__transport__check_session(nullptr, "get_session"), + ContainsSubstring("get_session: null session")); +} + TEST_CASE("create registers a session resolvable by (role, key)", "[transport]") { auto *s = make(CATALYST_TRANSPORT_ROLE_CONTROLLER, "reg_ctrl"); REQUIRE(s != nullptr); From 65a8b9cf3e6268f877ec64d9740aa88ef2b529ad Mon Sep 17 00:00:00 2001 From: Shuli Shu <08cnbj@gmail.com> Date: Tue, 25 Aug 2026 09:56:15 -0400 Subject: [PATCH 2/3] make format --- runtime/lib/transport/TransportWrappers.cpp | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/runtime/lib/transport/TransportWrappers.cpp b/runtime/lib/transport/TransportWrappers.cpp index f06008b90e..a423ba0ad5 100644 --- a/runtime/lib/transport/TransportWrappers.cpp +++ b/runtime/lib/transport/TransportWrappers.cpp @@ -355,10 +355,10 @@ CatalystWrapperResult __catalyst__transport__start_benchmark__wrapper(const char // reserve the samples buffer with the caller's capacity auto *samples = static_cast(out.reserve(samples_bytes)); auto *rounds = out.slot(); - *status = checked_status( - __catalyst__transport__start_benchmark(session, iters, decoder_id, flags, samples, - samples_bytes, rounds), - "start_benchmark"); + *status = + checked_status(__catalyst__transport__start_benchmark(session, iters, decoder_id, flags, + samples, samples_bytes, rounds), + "start_benchmark"); } return finish(in, out); } @@ -531,14 +531,14 @@ void __catalyst__transport__last_rtt_ns__call(void **args, void **results) { //===----------------------------------------------------------------------===// void __catalyst__transport__start_benchmark__call(void **args, void **results) { - put( - results, 0, - checked_status( - __catalyst__transport__start_benchmark( - session_arg(args, 0), arg(args, 1), arg(args, 2), - arg(args, 3), static_cast(data_of(results, 1)), - arg(args, 4), static_cast(data_of(results, 2))), - "start_benchmark")); + put(results, 0, + checked_status(__catalyst__transport__start_benchmark( + session_arg(args, 0), arg(args, 1), + arg(args, 2), arg(args, 3), + static_cast(data_of(results, 1)), + arg(args, 4), + static_cast(data_of(results, 2))), + "start_benchmark")); } //===----------------------------------------------------------------------===// From a478f640d895ca1f4319d835884a846d95857a5b Mon Sep 17 00:00:00 2001 From: Shuli Shu <08cnbj@gmail.com> Date: Tue, 25 Aug 2026 09:59:52 -0400 Subject: [PATCH 3/3] changelog: point runtime_call checking entry at PR #3150 --- doc/releases/changelog-dev.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 195426e6ff..e322344e92 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -28,7 +28,7 @@

Improvements 🛠

* Add checking of the pointer and status code from `runtime_call`. - [(#XXXX)](https://github.com/PennyLaneAI/catalyst/pull/XXXX) + [(#3150)](https://github.com/PennyLaneAI/catalyst/pull/3150) * a PennyLane `Backline` is serialized to the `catalyst.backline` module attribute and compiled through the transport passes.