/* Copyright 2023 The OpenXLA Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ #ifndef XLA_FFI_API_API_H_ #define XLA_FFI_API_API_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // This is a header-only base C++ library that defines templates for decoding // XLA FFI call frames and invoking corresponding C++ functions. This must have // no dependencies outside of the C++ standard library. // // There are two extensions to this base library: // // (1) xla/ffi/api/ffi.h for defining "external" FFI handlers loaded from // dynamic libraries potentially built with different toolchains and/or // a different XLA commit. It is a header-only library without any // dependencies. // // (2) xla/ffi/ffi.h for defining "internal" FFI handlers that must be // statically linked into the binary and must be built from the same // commit using the same toolchain, as it provides access to XLA // implementation details (e.g. ServiceExecutableOptions) and C++ ABI // across different libraries is hard. // // Extensions define template specializations for argument-decoding hooks // defined in this file. #include "xla/ffi/api/c_api.h" #ifdef __has_builtin #define XLA_FFI_HAS_BUILTIN(x) __has_builtin(x) #else #define XLA_FFI_HAS_BUILTIN(x) 0 #endif #if __has_attribute(always_inline) #define XLA_FFI_ATTRIBUTE_ALWAYS_INLINE __attribute__((always_inline)) #elif defined(_MSC_VER) #define XLA_FFI_ATTRIBUTE_ALWAYS_INLINE __forceinline #else #define XLA_FFI_ATTRIBUTE_ALWAYS_INLINE #endif #if __has_attribute(noinline) #define XLA_FFI_ATTRIBUTE_NEVER_INLINE __attribute__((noinline)) #elif defined(_MSC_VER) #define XLA_FFI_ATTRIBUTE_NEVER_INLINE __declspec(noinline) #else #define XLA_FFI_ATTRIBUTE_NEVER_INLINE #endif #if XLA_FFI_HAS_BUILTIN(__builtin_expect) #define XLA_FFI_PREDICT_FALSE(x) (__builtin_expect(false || (x), false)) #define XLA_FFI_PREDICT_TRUE(x) (__builtin_expect(false || (x), true)) #else #define XLA_FFI_PREDICT_FALSE(x) (x) #define XLA_FFI_PREDICT_TRUE(x) (x) #endif //===----------------------------------------------------------------------===// // Builtin enum pretty printing //===----------------------------------------------------------------------===// inline std::ostream& operator<<(std::ostream& os, const XLA_FFI_DataType dtype) { switch (dtype) { case XLA_FFI_DataType_INVALID: return os << "INVALID"; case XLA_FFI_DataType_PRED: return os << "PRED"; case XLA_FFI_DataType_S1: return os << "S1"; case XLA_FFI_DataType_S2: return os << "S2"; case XLA_FFI_DataType_S4: return os << "S4"; case XLA_FFI_DataType_S8: return os << "S8"; case XLA_FFI_DataType_S16: return os << "S16"; case XLA_FFI_DataType_S32: return os << "S32"; case XLA_FFI_DataType_S64: return os << "S64"; case XLA_FFI_DataType_U1: return os << "U1"; case XLA_FFI_DataType_U2: return os << "U2"; case XLA_FFI_DataType_U4: return os << "U4"; case XLA_FFI_DataType_U8: return os << "U8"; case XLA_FFI_DataType_U16: return os << "U16"; case XLA_FFI_DataType_U32: return os << "U32"; case XLA_FFI_DataType_U64: return os << "U64"; case XLA_FFI_DataType_F16: return os << "F16"; case XLA_FFI_DataType_F32: return os << "F32"; case XLA_FFI_DataType_F64: return os << "F64"; case XLA_FFI_DataType_BF16: return os << "BF16"; case XLA_FFI_DataType_C64: return os << "C64"; case XLA_FFI_DataType_C128: return os << "C128"; case XLA_FFI_DataType_TOKEN: return os << "TOKEN"; case XLA_FFI_DataType_F4E2M1FN: return os << "F4E2M1FN"; case XLA_FFI_DataType_F8E5M2: return os << "F8E5M2"; case XLA_FFI_DataType_F8E3M4: return os << "F8E3M4"; case XLA_FFI_DataType_F8E4M3: return os << "F8E4M3"; case XLA_FFI_DataType_F8E4M3FN: return os << "F8E4M3FN"; case XLA_FFI_DataType_F8E4M3B11FNUZ: return os << "F8E4M3B11FNUZ"; case XLA_FFI_DataType_F8E5M2FNUZ: return os << "F8E5M2FNUZ"; case XLA_FFI_DataType_F8E4M3FNUZ: return os << "F8E4M3FNUZ"; case XLA_FFI_DataType_F8E8M0FNU: return os << "F8E8M0FNU"; } } inline std::ostream& operator<<(std::ostream& os, const XLA_FFI_AttrType type) { switch (type) { case XLA_FFI_AttrType_ARRAY: return os << "array"; case XLA_FFI_AttrType_DICTIONARY: return os << "dictionary"; case XLA_FFI_AttrType_SCALAR: return os << "scalar"; case XLA_FFI_AttrType_STRING: return os << "string"; } } inline std::ostream& operator<<(std::ostream& os, const XLA_FFI_ExecutionStage stage) { switch (stage) { case XLA_FFI_ExecutionStage_INSTANTIATE: return os << "instantiate"; case XLA_FFI_ExecutionStage_PREPARE: return os << "prepare"; case XLA_FFI_ExecutionStage_INITIALIZE: return os << "initialize"; case XLA_FFI_ExecutionStage_EXECUTE: return os << "execute"; } } namespace xla::ffi { enum class ExecutionStage : uint8_t { kInstantiate = XLA_FFI_ExecutionStage_INSTANTIATE, kPrepare = XLA_FFI_ExecutionStage_PREPARE, kInitialize = XLA_FFI_ExecutionStage_INITIALIZE, kExecute = XLA_FFI_ExecutionStage_EXECUTE, }; enum class Traits : uint32_t { kCmdBufferCompatible = XLA_FFI_HANDLER_TRAITS_COMMAND_BUFFER_COMPATIBLE, }; // Forward declare template defined below. template class Binding; // Forward declare template defined below. template class Handler; //===----------------------------------------------------------------------===// // XLA FFI virtual base for implementing FFI handlers //===----------------------------------------------------------------------===// class Ffi { public: // Creates an empty binding specification which allows to define FFI handler // signature separately from implementation and rely on compile time type // checking to verify that signature matches the provided implementation. template static Binding Bind(); // Creates an empty binding for the instantiate stage. static Binding BindInstantiate(); // Automatic FFI binding that does binding specification inference from the // `fn` type signature and binds `fn` to it. This enables a more concise FFI // handler registration with fully automatic type inference at the cost of // less readable error messages, template metaprogramming "magic" and a risk // to accidentally change handler type without noticing it. template static auto BindTo(Fn fn, std::initializer_list traits = {}); virtual ~Ffi() = default; virtual XLA_FFI_Error* Call(XLA_FFI_CallFrame* call_frame) const = 0; // Registers FFI handler bundle with an XLA runtime under the given name on a // given platform. static XLA_FFI_Error* RegisterStaticHandler( const XLA_FFI_Api* api, std::string_view name, std::string_view platform, XLA_FFI_Handler_Bundle bundle, XLA_FFI_Handler_Traits traits = 0); // Registers FFI execute handler with an XLA runtime under the given name on a // given platform. static XLA_FFI_Error* RegisterStaticHandler( const XLA_FFI_Api* api, std::string_view name, std::string_view platform, XLA_FFI_Handler* execute, XLA_FFI_Handler_Traits traits = 0) { return RegisterStaticHandler( api, name, platform, XLA_FFI_Handler_Bundle{nullptr, nullptr, nullptr, execute}, traits); } // Registers a custom type so that it can be used with State and UserData // arguments to external FFI handlers. The `name` argument must be a unique // identifier for the type, and duplicate registrations with the same name // are not allowed. When successful, a unique ID will be returned by updating // `type_id`. static XLA_FFI_Error* RegisterTypeId(const XLA_FFI_Api* api, std::string_view name, XLA_FFI_TypeId* type_id); protected: template static std::string StrCat(Args... args); static XLA_FFI_Error* Sucess(); static XLA_FFI_Error* MakeError(const XLA_FFI_Api* api, XLA_FFI_Error_Code errc, std::string message); static XLA_FFI_Error* InvalidArgument(const XLA_FFI_Api* api, std::string message); static XLA_FFI_Error* CheckStructSize(const XLA_FFI_Api* api, std::string_view struct_name, size_t expected, size_t actual); static XLA_FFI_Error* StructSizeIsGreaterOrEqual(const XLA_FFI_Api* api, std::string_view struct_name, size_t expected, size_t actual); }; inline XLA_FFI_Error* Ffi::RegisterStaticHandler( const XLA_FFI_Api* api, std::string_view name, std::string_view platform, XLA_FFI_Handler_Bundle bundle, XLA_FFI_Handler_Traits traits) { XLA_FFI_Handler_Register_Args args; args.struct_size = XLA_FFI_Handler_Register_Args_STRUCT_SIZE; args.extension_start = nullptr; args.name = XLA_FFI_ByteSpan{name.data(), name.size()}; args.platform = XLA_FFI_ByteSpan{platform.data(), platform.size()}; args.bundle = bundle; args.traits = traits; return api->XLA_FFI_Handler_Register(&args); } inline XLA_FFI_Error* Ffi::RegisterTypeId(const XLA_FFI_Api* api, std::string_view name, XLA_FFI_TypeId* type_id) { XLA_FFI_TypeId_Register_Args args; args.struct_size = XLA_FFI_TypeId_Register_Args_STRUCT_SIZE; args.extension_start = nullptr; args.name = XLA_FFI_ByteSpan{name.data(), name.size()}; args.type_id = type_id; return api->XLA_FFI_TypeId_Register(&args); } template std::string Ffi::StrCat(Args... args) { std::stringstream ss; (ss << ... << args); return ss.str(); } inline XLA_FFI_Error* Ffi::Sucess() { return nullptr; } inline XLA_FFI_Error* Ffi::MakeError(const XLA_FFI_Api* api, XLA_FFI_Error_Code errc, std::string message) { XLA_FFI_Error_Create_Args args; args.struct_size = XLA_FFI_Error_Create_Args_STRUCT_SIZE; args.extension_start = nullptr; args.errc = errc; args.message = message.c_str(); return api->XLA_FFI_Error_Create(&args); } inline XLA_FFI_Error* Ffi::InvalidArgument(const XLA_FFI_Api* api, std::string message) { return MakeError(api, XLA_FFI_Error_Code_INVALID_ARGUMENT, std::move(message)); } inline XLA_FFI_Error* Ffi::CheckStructSize(const XLA_FFI_Api* api, std::string_view struct_name, size_t expected, size_t actual) { if (expected != actual) { return InvalidArgument( api, StrCat("Unexpected ", struct_name, " size: expected ", expected, " got ", actual, ". Check installed software versions.")); } return nullptr; } inline XLA_FFI_Error* Ffi::StructSizeIsGreaterOrEqual( const XLA_FFI_Api* api, std::string_view struct_name, size_t expected, size_t actual) { if (actual < expected) { return InvalidArgument( api, StrCat("Unexpected ", struct_name, " size: expected at least ", expected, " got ", actual, ". Check installed software versions.")); } return nullptr; } //===----------------------------------------------------------------------===// // Type tags for distinguishing handler argument types //===----------------------------------------------------------------------===// // Forward declare. class Dictionary; namespace internal { // WARNING: A lot of template metaprogramming on top of C++ variadic templates // parameter packs. We need this to be able to pattern match FFI handler // signature at compile time. // A type tag for decoding optional argument. template struct OptionalArgTag {}; // A type tag to forward all remaining args as `RemainingArgs`. struct RemainingArgsTag {}; // A type tag to distinguish parameters tied to results in the `Binding` // variadic template. In XLA FFI we use destination passing style APIs and don't // return anything from the handler, but instead pass a destination where the // handler should write the result. template struct RetTag {}; // A type tag for decoding optional result. template struct OptionalRetTag {}; // A type tag to forward all remaining results as `RemainingRets`. struct RemainingRetsTag {}; // A type tag to distinguish parameters tied to the attributes in the // `Binding` variadic template. template struct AttrTag {}; // A type tag to forward all attributes as `Dictionary` (and optionally decode // it into a custom struct). template struct AttrsTag {}; // A type tag to distinguish parameter extracted from an execution context. template struct CtxTag {}; //----------------------------------------------------------------------------// // A template for counting tagged arguments in the Ts pack (i.e. attributes). //----------------------------------------------------------------------------// template