/* * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ #pragma once #include "glean/rts/binary.h" #include "glean/rts/id.h" #include "glean/rts/set.h" namespace facebook { namespace glean { namespace rts { /// Type of functions which can be called from bytecode using SysFun = void (*)( void* context, uint64_t* frame, const uint64_t* regs, uint64_t nregs); /// A typed bytecode register which can be read from and written to template struct Reg { uint64_t* ptr; explicit Reg(uint64_t* ptr = nullptr) : ptr(ptr) {} T get() const { T x; fromWord(x, *ptr); return x; } T operator*() const { return get(); } void set(T x) { *ptr = toWord(x); } // This is ugly but not having an operator for this is uglier. void operator<<(T x) { set(x); } private: // Marshalling values from bytecode registers to C++ values and back // Everything that is implicitly convertible to and from uint64_t template static std::enable_if_t, void> fromWord( U& x, uint64_t w) { x = w; } template static std::enable_if_t, uint64_t> toWord( U x) { return x; } // Id and Pid template static void fromWord(WordId& x, uint64_t w) { x = WordId::fromWord(w); } template static uint64_t toWord(WordId x) { return x.toWord(); } // data pointers static void fromWord(const unsigned char*& x, uint64_t w) { x = reinterpret_cast(w); } static uint64_t toWord(const unsigned char* x) { return reinterpret_cast(x); } // Outputs static void fromWord(binary::Output*& x, uint64_t w) { x = reinterpret_cast(w); } static void fromWord(const binary::Output*& x, uint64_t w) { x = reinterpret_cast(w); } static uint64_t toWord(binary::Output* x) { return reinterpret_cast(x); } static uint64_t toWord(const binary::Output* x) { return reinterpret_cast(x); } // SysFun static void fromWord(SysFun& x, uint64_t w) { x = reinterpret_cast(w); } static uint64_t toWord(SysFun x) { return reinterpret_cast(x); } // Sets static void fromWord(BytestringSet*& x, uint64_t w) { x = reinterpret_cast(w); } static void fromWord(const BytestringSet*& x, uint64_t w) { x = reinterpret_cast(w); } }; template inline std::ostream& operator<<(std::ostream& os, const Reg& reg) { return (os << reg.get()); } /// Arguments to syscalls. /// /// The primary template handles inputs which are passed by value template struct SysArg { using type = T; static T arg(uint64_t* ptr) { return Reg(ptr).get(); } }; // Outputs, i.e., registers which can be written to template struct SysArg> { using type = Reg; static Reg arg(uint64_t* ptr) { return Reg(ptr); } }; /// Typed wrapper over a function which can be invoked from bytecode template struct SysCall { static constexpr size_t arity = sizeof...(Args); }; /// Utility class for constructing 'SysCall's from an object and a method. template struct SysHandlerMethod { using syscall_t = std::conditional_t< std::is_void_v, SysCall, SysCall>>; static void call(void* context, uint64_t* frame, const uint64_t* regs, uint64_t nregs) { assert(nregs == syscall_t::arity); // Here, we invoke `f` with the marshalled arguments and if it returns a // result, marshal it back. These are the steps. // // * Build an `std::tuple` of all arguments to `F`, i.e., a pointer to the // context and all the marshalled args. We do via a tuple mostly because // we need to know the index for each arg and this seems easiest. // // * Use 'std::apply' to apply `F` to the arg tuple. // // * If `F` isn't void, stored its result in the result register. // Brace initialisation guarantees that individual components are // evaluated from left to right (as opposed to function arguments) // - meaning that the multiple increments of `regs` are well defined. std::tuple args{ *static_cast*>(context), SysArg::arg(&frame[*regs++])...}; if constexpr (std::is_void_v) { std::apply(F, args); } else { Reg(&frame[*regs]).set(std::apply(F, args)); } } }; template struct SysHandler; template < typename C, typename D, typename R, typename... Args, R (D::*F)(Args...)> struct SysHandler : SysHandlerMethod {}; template < typename C, typename D, typename R, typename... Args, R (D::*F)(Args...) const> struct SysHandler : SysHandlerMethod {}; /// A collection of system call handlers template class SysCalls { public: SysCalls(Context&& context, const uint64_t* handlers) : context(std::forward(context)), handlers(handlers) {} void* contextptr() const { return const_cast(static_cast(&context)); } const uint64_t* handlers_begin() const { return handlers; } const uint64_t* handlers_end() const { return handlers + sizeof...(Handlers); } private: Context context; const uint64_t* handlers; }; /// Construct a 'SysCall' from an object and a method. The object can be passed /// either by value/move reference (in which case it is captured in the /// 'SysCall' object) or by reference (in which case it must be alive when the /// 'SysCall' is invoked). template SysCalls::syscall_t...> syscalls( Context&& context) { // Note that this is static so we don't reconstruct it on each call. static const uint64_t handlers[sizeof...(Calls)] = { reinterpret_cast(&SysHandler::call)...}; return SysCalls::syscall_t...>( std::forward(context), handlers); } /// Construct a 'SysCall' from a functional object. The object can be passed /// either by value/move reference (in which case it is captured in the /// 'SysCall' object) or by reference (in which case it must be alive when the /// 'SysCall' is invoked). template auto syscall(Context&& context) { return syscalls<&std::remove_reference_t::operator()>( std::forward(context)); } } // namespace rts } // namespace glean } // namespace facebook