Bring the wrapper-function call and return prototypes into line with the
convention adopted for orc_rt_Session_callController: the session handle
comes first, then the payload buffer, then the completion callback
immediately followed by its threaded token, with the token last.
orc_rt_WrapperFunctionReturn: (S, CallId, ResultBytes)
-> (S, ResultBytes, CallId)
orc_rt_WrapperFunction: (S, CallId, Return, ArgBytes)
-> (S, ArgBytes, Return, CallId)
The C++ adapters that mirror these signatures are updated to match --
WrapperFunction::handle, SPSWrapperFunction::handle, the
ORC_RT_SPS_WRAPPER macro, and StructuredYield -- along with the Session
dispatch path and the affected unit tests. No functional change beyond
argument order.
Commit 01683dbe7e generalized the Session's dispatch mechanism to
hand each runner an opaque Task (move_only_function<void()>), and
simplified QueueingRunner accordingly, but failed to update
ThreadPoolRunner.
Update ThreadPoolRunner to conform to Session's dispatch requirements
and update its unit test to dispatch plain tasks rather than
wrapper-function calls.
Replace the `Session::HandlerTag = void*` type alias with
orc_rt_ControllerHandlerTag. The intent is both to make this type
accessible to the C API, and to rename it to clarify its role as a tag
for handlers in the controller that are callable by the executor.
A controller call's on-complete handler may run managed code, so it must
be dispatched by the Session under a ManagedCodeTaskGroup token, not
invoked directly by the ControllerAccess (which previously held it as a
plain callable and called it itself -- untokened and undispatched).
Wrap it in an opaque ControllerAccess::OnControllerCallReturn that
implementations can hold but not call. They complete it via exactly one
of three Session-provided paths:
- handleControllerCallResult -- the controller returned a result;
dispatches the handler under a fresh token. Asserts the managed-code
group is still open (i.e. before notifyDisconnected).
- failPendingControllerCall -- an already-enqueued call is failed
because the connection dropped before a result arrived (the disconnect
drain); dispatches the handler under a token with a disconnect error.
- failControllerCallInline -- a call made from within callController
while already disconnecting, which can never be enqueued; runs the
handler inline with a disconnect error, where the caller (and its token)
is still on the stack.
Together these guarantee a controller call's handler always runs exactly
once, with a result or a disconnect error, and never runs managed code
without a token. Only the success path takes a result value; the two
failure paths produce the canonical disconnect error internally, so it
can't be gotten wrong. InProcessControllerAccess and the Session unit
tests are updated.
Rename Session::CallViaSession to Session::ControllerCaller, and test
utility CallViaMockControllerAccess to CallFromController.
The old names described who was routing the calls, but this is implicit
at the call-site anyway (CallViaSession was a method on Session, and
CallViaMockControllerAccess took a MockControllerAccess argument). The
new names reflect the call destination, which is the thing clients care
about.
d199b284c7 established that ManagedCodeTaskGroup tokens must bracket a
single span of execution on a stack, not a chain of asynchronous
operations (see orc-rt/docs/Design.md's "Managed code execution and
shutdown" section). Session::handleWrapperCall and sendWrapperResult
predated that policy and didn't conform to it: the token was acquired in
handleWrapperCall but only released in sendWrapperResult, holding it for
the whole call/response chain. If Fn deferred sending its result
asynchronously, Session shutdown would incorrectly wait on that deferred
completion instead of proceeding as soon as Fn's stack unwound.
Acquire the token as a TaskGroup::Token scoped to the dispatched call,
and let it release when Fn returns rather than when its result is sent.
RunWrapperCall dispatched wrapper-function calls, but the Session also
needs to dispatch continuations for results returned by the controller.
Replace it with a generalized Dispatch mechanism (DispatchFn) that is
handed an opaque Task for each unit of work, covering both.
QueueingRunner is correspondingly simplified: it no longer knows
anything about wrapper functions and just enqueues the Task it is given.
Tests and the noDispatch helper are updated to the new interface.
callManagedCodeSync is renamed to callManagedCode, and
callManagedCodeAsync is removed.
callManagedCodeAsync held a ManagedCodeTaskGroup token from the initial
call until the async continuation ran -- across the gap in which the
call is suspended and no managed code is executing. A token is meant to
guard managed code that is executing on a stack, so holding one across
that gap guards nothing while blocking shutdown.
callManagedCode holds a token only for the synchronous call to the
wrapped function, including anything the function runs inline on the
same thread before returning. An async function is called the same way,
with its continuation passed as an ordinary argument. Work deferred past
the function's return -- stashed, or handed to another thread -- runs on
a stack the token no longer guards; whoever later runs it is responsible
for ensuring a token covers it, acquiring one and aborting if the
acquire is denied.
See the new "Managed code execution and shutdown" section in
docs/Design.md for the full model (what a token guards, nested entries,
context-free acquire, and the abortability obligation).
Session::managedCodeTaskGroup() returned the raw
std::shared_ptr<TaskGroup> for the managed-code group, exposing the
group's full interface -- including close() and addOnComplete() -- to
callers, even though those operations are reserved for the Session.
Add TaskGroup::TokenSource, a strong handle whose sole role is to serve
as an argument to the TaskGroup::Token constructor. Session now exposes
managedCodeTokenSource() in its place, so clients can acquire tokens (to
keep managed code alive across shutdown) without gaining the ability to
close the group or register completion callbacks.
Adds orc_rt::asCCallback<&Class::method>, which produces a C-ABI
function pointer that forwards to the given member function. The first
parameter of the generated trampoline is an opaque context pointer (
void * for non-const methods, const void * for const methods) that is
cast back to the class type and used as the receiver:
struct Counter {
void inc(int N) { Count += N; }
int Count = 0;
};
Counter C;
void (*CB)(void *, int) = asCCallback<&Counter::inc>;
CB(asCCallbackContext<&Counter::inc>(C), 2); // C.Count == 2
asCCallbackContext produces the matching context pointer via
static_cast, so any base-class offset is applied and const-ness is
preserved; pairing it with asCCallback by the same method keeps things
correct under inheritance and const-qualification. (A raw cast of &C
would give the trampoline the wrong `this` when method's class is a base
at a non-zero offset.)
Specializations cover the const x noexcept combinations. The trampoline
is always noexcept -- a C caller cannot unwind a C++ exception, so an
escaping one terminates -- and return/argument types are restricted to
C-compatible (non-reference, trivially copyable) types via
static_assert.
Rename Session::OnCallHandlerCompleteFn to
Session::OnControllerCallReturnFn and add a comment to clarify its
purpose: This callback is used to handle retured values from calls to
the controller.
orc_rt_CreateWrapperFunctionBufferFromRange(const char *, size_t) left
the constructed buffer's data member uninitialized when the size
argument was zero. This could result in the returned buffer having a
non-null data field and a zero size field, which is a (malformed)
out-of-band error value, not an empty buffer.
Update orc_rt_CreateWrapperFunctionBufferFromRange to zero-initialize
the data field so that the size == 0 case yields a correctly formed
empty buffer.
noDispatch guards Sessions that must never dispatch a wrapper call, but
it did so with assert(false), which compiles out under NDEBUG.
Replace the assert with ADD_FAILURE(), which records a failure in every
build mode, and then complete the call via its Return continuation with
an out-of-band error. Completing the call means a caller awaiting the
result unblocks and fails too, rather than hanging, even when the
dispatch arrives on a non-test thread where ADD_FAILURE() alone may not
be observed.
GoogleTest recommends putting death tests in a suite whose name ends in
"DeathTest": it runs those suites before all others, which avoids the
fork-after-threads hazard that can make death tests flaky.
Move the nine EXPECT_DEATH tests in ErrorTest.cpp from the ErrorTest
suite to a new ErrorDeathTest suite. Only the suite name changes; the
test bodies are untouched.
ORC_RT_BUILTIN_UNREACHABLE is an optimizer hint that a location is
unreachable, wrapping __builtin_unreachable / __assume(false). Executing
it is undefined behavior.
ORC_RT_UNREACHABLE(MSG) marks a point the program must never reach. It
aborts with MSG in +Asserts builds; otherwise it lowers to
ORC_RT_BUILTIN_UNREACHABLE.
Adds a CompilerTest unit test covering both the returning-function
compile path and, in +Asserts builds only, the abort behavior via a
death test.
ORC_RT_LOG requires its format to be a string literal, so the specifier
for a runtime C string cannot be written at the call site by hand -- and
it differs by backend: os_log redacts a plain "%s" argument to <private>
unless it carries a "%{public}s" annotation, which libc's printf does
not understand.
Add ORC_RT_LOG_PUB_S, which expands to "%{public}s" on the os_log
backend and plain "%s" everywhere else. Call sites compose it into the
format by literal adjacency, so the format stays a single literal on
every backend:
ORC_RT_LOG(Info, General, "resolved " ORC_RT_LOG_PUB_S, SymbolName);
LoggingTest covers the compile/format-check path (including os_log
builds); the orc-rt-log-check delivery test confirms os_log publishes
the string rather than redacting it to <private>.
The orc_rt_log_Category_ControllerAccess category should be used to log
messages from ControllerAccess implementations. E.g.
ORC_RT_LOG(Error, ControllerAccess, "connect() failed with <error>");
Hoist runFinalizeActions and runDeallocActions into AllocAction.h and
turn them into function templates with a ReportErrorFn argument that
enables error reporting. This is used to report errors from dealloc
actions.
Update SimpleNativeMemoryMap to direct such errors to the Session via
ReportErrorsViaSession.
Add unit tests covering the new error-reporting paths.
ReportErrorsViaSession is a function object constructed from a Session&
that forwards Errors to that Session's reportError method. It will be
used in upcoming patches to simplify some error reporting plumbing.
The ORC Generic Runtime Environment (OGRE) will serve as our canonical
"blank executor" program: A program that simply listens for an ORC
controller connection, then runs whatever program is provided to it by
the controller.
os_log needs the format string as a literal (it builds format metadata
at the call site), while the none and printf backends accept a runtime
const char*. That mismatch let a non-literal format compile on
none/printf and fail only on an os_log build. Enforce the literal
requirement uniformly in the ORC_RT_LOG macro via an unevaluated
sizeof("" ...) check, so it is caught at the call site on every backend.
Implement the os_log backend, selected with ORC_RT_LOG_BACKEND=os_log.
With this backend selected, the ORC_RT_LOG macros expand at the call
site to os_log_with_type. Each ORC runtime log level maps to an
os_log_type_t (error -> ERROR, warning -> DEFAULT, info -> INFO, debug
-> DEBUG). The compile-time ORC_RT_LOG_LEVEL floor still applies, but
runtime filtering is left to the system (e.g. `log config`), so the
printf backend's ORC_RT_LOG and ORC_RT_LOG_OUTPUT have no effect.
Records go to the "org.llvm.orc-rt" subsystem with the category as the
os_log category. (Per-category handles are created on first use and
cached).
Add regression tests under test/regression/logging/os_log:
no-printf-output.test checks that the backend produces no console
output, and an opt-in delivery test (llvm-lit --param
run-os-log-tests=1) scrapes `log show` to confirm records reach the
expected subsystem and category.
…ackend
orc_rt::secureGetenv is like getenv, but returns null when the process
is running with elevated privileges (e.g. a set-user-ID or set-group-ID
program), so that a variable in an attacker-controlled environment
cannot influence a privileged host. On libcs where privilege can't be
checked (anything other than glibc or the BSDs/macOS), it fails secure
and returns null, so those environment variables are ignored until a
branch for the platform is added.
Switch the printf logging backend to read ORC_RT_LOG and
ORC_RT_LOG_OUTPUT through secureGetenv, so a redirected log file or
level can't be forced via the environment in a privileged process.
Note that running the ORC runtime in a process with elevated privileges
is extremely risky (since the JIT'd code will typically inherit these
elevated privileges), and expected to be rare. Exercise extreme caution
with such setups.
Implement the printf backend, selected at configure-time with
ORC_RT_LOG_BACKEND=printf. Messages are written to stderr, or to the
file named by the ORC_RT_LOG_OUTPUT environment variable.
The ORC_RT_LOG environment variable sets the runtime level floor (error,
warning, info, debug, or off) -- only messages at or above it are shown.
When unset it defaults to warning, so warnings and errors are shown and
info and debug are opt-in. The runtime floor cannot go below the
compile-time ORC_RT_LOG_LEVEL floor (the lowest level built in).
Also add regression tests, driven by a new orc-rt-log-check tool that
reports the compiled-in backend and levels (which lit exposes as
features) and emits a message at each level.
orc_rt_log_Level_getName is used primarily in text prefixes (e.g. the
upcoming printf backend's "[orc-rt:General:LEVEL]"), where uppercase is
the intended rendering. Storing the names as uppercase lets the backend
use them directly without case conversion. orc_rt_log_Level_parse
performs a case-insensitive parse, so ORC_RT_LOG=info and similar still
work.
[orc-rt] Hold log level names as uppercase.
orc_rt_log_Level_getName is expected to be primarily used in
text-prefixes (e.g. in the upcoming printf backend), where it should be
printed as uppercase. Storing as uppercase in the first place will save
us a toupper conversion on each log call.
Rename ORC_RT_LOG_LEVEL_NONE to ORC_RT_LOG_LEVEL_OFF. The old name
collided with the "none" backend (a different mechanism) and could be
misread as "no filtering" rather than "no output"; "off" avoids both
issues.
Add orc_rt_log_Category_getName, orc_rt_log_Level_getName, and
orc_rt_log_Level_parse. These are independent of the logging backend and
will be used by the printf backend and by upcoming test tools.
orc_rt_ is the top-level C namespace and orc_rt_log_ a nested namespace
for logging. Following the C API convention, types, values, and
variables are PascalCase and functions are camelCase (as in LLVM).
Adds orc_rt::CommandLineParser, a small header-only option parser in
orc-rt-utils for use by orc-rt tools that need argument handling.
Features:
- Long (--name, --name=value) and short (-n, -nvalue) options.
- Short-flag clustering (e.g. -vp9999) and "--" to terminate option
parsing, with remaining tokens collected as positionals.
- Typed values, with parse failures returning an orc_rt::Error.
- printHelp() with aligned, optionally short-name-aware output.
Values are bound to caller-owned storage supplied at registration time and
reset to their defaults on each parse().
Also adds CommandLineTest.cpp to the CoreTests unittest and fixes the
indentation of the docs subdirectory guard in orc-rt/CMakeLists.txt.
Move unit tests from orc-rt/unittests into orc-rt/test/unit, completing
the consolidation of the runtime's tests under orc-rt/test (alongside
test/regression and test/tools).
Set up the infrastructure for regression tests that build and run a
helper tool, in preparation for testing the logging backends.
- Add test/tools/ for test-support binaries, with a first tool,
orc-rt-smoke-check, wired into ORC_RT_TEST_DEPS and onto lit's PATH.
- Add test/regression/smoke-check.test, which runs the tool and matches
its output with FileCheck, exercising the tool-build and lit plumbing
end to end.
Move the regression lit config and the existing tests into
test/regression/, and point the check-orc-rt suite there.
This is a first step towards consolidating tests and test infrastructure
under orc-rt/test. Upcoming commits will add a test tools directory, and
move the existing unit tests (under orc-rt/unittests) into
orc-rt/test/unit.
Adds ORC_RT_LOG(Level, Category, Fmt, ...), a compile-time-configurable
logging facility for the ORC runtime, along with the default "none"
backend.
The backend is chosen at build time via the ORC_RT_LOG_BACKEND CMake
option (none, printf, os_log), and the lowest level to be compiled in
via ORC_RT_LOG_LEVEL.
The "none" backend implemented here compiles every log site out to
nothing (but still type-checks the format string and arguments in an
unevaluated context so disabled sites cannot bit-rot). Levels are Error,
Warning, Info, and Debug; categories are a typo-safe enum.
The printf and os_log backends are stubbed with #error and will follow.
Add a macro to annotate a function that takes a printf-style format
string, so that supporting compilers can check the format string against
the function's arguments at compile time.
This will be used by the upcoming logging API to type-check log format
strings, including at call sites that are compiled out.