[orc-rt] Add os_log logging backend (Darwin only) (#209081)

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.
This commit is contained in:
Lang Hames 2026-07-13 15:08:09 +10:00 committed by GitHub
parent d8df9b5e92
commit b1c56fb53a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 194 additions and 2 deletions

View file

@ -27,7 +27,10 @@
|* "none" backend emits no code (but still type-checks every call site). The *|
|* "printf" backend writes each message to stderr, or to the file named by *|
|* the ORC_RT_LOG_OUTPUT environment variable; the ORC_RT_LOG variable sets a *|
|* runtime level threshold (error, warning, info, debug, or off). *|
|* runtime level threshold (error, warning, info, debug, or off). The *|
|* "os_log" backend (Apple) routes records to the unified logging system, *|
|* where filtering is configured out of band (e.g. `log config`), so *|
|* ORC_RT_LOG and ORC_RT_LOG_OUTPUT have no effect. *|
|* *|
|* IMPORTANT: log arguments should be free of observable side effects. *|
|* Whether they are evaluated is backend- and level-dependent, so they must *|
@ -41,6 +44,10 @@
#include "orc-rt-c/Compiler.h"
#include "orc-rt-c/config.h"
#if ORC_RT_LOG_BACKEND == ORC_RT_LOG_BACKEND_OS_LOG
#include <os/log.h>
#endif
ORC_RT_C_EXTERN_C_BEGIN
/**
@ -175,7 +182,84 @@ void orc_rt_log_printf(orc_rt_log_Level Level, orc_rt_log_Category Category,
#endif
#elif ORC_RT_LOG_BACKEND == ORC_RT_LOG_BACKEND_OS_LOG
#error "The os_log logging backend is not yet implemented."
/*
* Per-category os_log_t cache, homed in Logging_oslog.cpp. Slots start null and
* are filled lazily (and read) atomically by orc_rt_log_osLogHandle. An
* implementation detail of that accessor; do not use directly.
*/
extern os_log_t orc_rt_log_OSLogHandles[orc_rt_log_Category_Count];
/*
* Cold path for orc_rt_log_osLogHandle: creates the handle for Category (in the
* "org.llvm.orc-rt" subsystem), caches it, and returns it. Defined in
* Logging_oslog.cpp.
*/
os_log_t
orc_rt_log_osLogHandleSlow(orc_rt_log_Category Category) ORC_RT_C_NOTHROW;
/*
* Returns the os_log_t handle for the given category, creating it on first use.
* Inlined, with a per-category cache, so os_log_with_type's callsite enabled-
* check stays cheap; the warm path is just an atomic load. Returns
* OS_LOG_DEFAULT for an out-of-range category. Not called directly: use
* ORC_RT_LOG. (__atomic_* builtins are used rather than <atomic>/<stdatomic.h>
* so this compiles the same in C and C++; the os_log backend is Clang-only.)
*/
static inline os_log_t orc_rt_log_osLogHandle(orc_rt_log_Category Category) {
if (Category < 0 || Category >= orc_rt_log_Category_Count)
return OS_LOG_DEFAULT;
os_log_t Handle =
__atomic_load_n(&orc_rt_log_OSLogHandles[Category], __ATOMIC_ACQUIRE);
if (!Handle)
Handle = orc_rt_log_osLogHandleSlow(Category);
return Handle;
}
/*
* Each level maps to an os_log_type_t and expands, at the call site, directly
* to os_log_with_type -- os_log needs the format string as a literal and the
* type as a compile-time constant, so it cannot be routed through a runtime
* function. Runtime filtering is left to the system (e.g. `log config`);
* ORC_RT_LOG has no effect here. A level below the ORC_RT_LOG_LEVEL floor still
* compiles out to ORC_RT_LOG_DISABLED.
*/
#if ORC_RT_LOG_LEVEL_ERROR >= ORC_RT_LOG_LEVEL
#define ORC_RT_LOG_Error(Category, ...) \
os_log_with_type(orc_rt_log_osLogHandle(Category), OS_LOG_TYPE_ERROR, \
__VA_ARGS__)
#else
#define ORC_RT_LOG_Error(Category, ...) \
ORC_RT_LOG_DISABLED(Category, __VA_ARGS__)
#endif
#if ORC_RT_LOG_LEVEL_WARNING >= ORC_RT_LOG_LEVEL
#define ORC_RT_LOG_Warning(Category, ...) \
os_log_with_type(orc_rt_log_osLogHandle(Category), OS_LOG_TYPE_DEFAULT, \
__VA_ARGS__)
#else
#define ORC_RT_LOG_Warning(Category, ...) \
ORC_RT_LOG_DISABLED(Category, __VA_ARGS__)
#endif
#if ORC_RT_LOG_LEVEL_INFO >= ORC_RT_LOG_LEVEL
#define ORC_RT_LOG_Info(Category, ...) \
os_log_with_type(orc_rt_log_osLogHandle(Category), OS_LOG_TYPE_INFO, \
__VA_ARGS__)
#else
#define ORC_RT_LOG_Info(Category, ...) \
ORC_RT_LOG_DISABLED(Category, __VA_ARGS__)
#endif
#if ORC_RT_LOG_LEVEL_DEBUG >= ORC_RT_LOG_LEVEL
#define ORC_RT_LOG_Debug(Category, ...) \
os_log_with_type(orc_rt_log_osLogHandle(Category), OS_LOG_TYPE_DEBUG, \
__VA_ARGS__)
#else
#define ORC_RT_LOG_Debug(Category, ...) \
ORC_RT_LOG_DISABLED(Category, __VA_ARGS__)
#endif
#else
#error "Unknown ORC_RT_LOG_BACKEND."
#endif

View file

@ -26,6 +26,8 @@ set(files
# is header-only and the os_log backend is not yet implemented.
if(ORC_RT_LOG_BACKEND STREQUAL "printf")
list(APPEND files Logging_printf.cpp)
elseif(ORC_RT_LOG_BACKEND STREQUAL "os_log")
list(APPEND files Logging_oslog.cpp)
endif()
add_library(orc-rt-executor STATIC ${files})

View file

@ -0,0 +1,45 @@
//===- Logging_oslog.cpp - os_log logging backend ------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Runtime support for the os_log logging backend declared in
// orc-rt-c/Logging.h.
//
// The log records themselves are emitted at the call site by os_log_with_type
// (see the ORC_RT_LOG_* macros). The only runtime support needed here is a
// per-category os_log_t handle, which orc_rt_log_osLogHandle reads inline; this
// file homes the cache and provides the cold path that fills it.
//
//===----------------------------------------------------------------------===//
#include "orc-rt-c/Logging.h"
#include <os/log.h>
// Cache for the inline accessor (declared in Logging.h). Zero-initialized, so
// no static constructor runs; slots are filled lazily by the cold path below.
os_log_t orc_rt_log_OSLogHandles[orc_rt_log_Category_Count];
extern "C" os_log_t
orc_rt_log_osLogHandleSlow(orc_rt_log_Category Category) noexcept {
// Category is already range-checked by the inline caller.
//
// ORC rt logs to the "org.llvm.orc-rt" subsystem.
//
// Combined with the per-category name it lets records be filtered in Console
// or `log` (e.g. `log stream --predicate 'subsystem == "org.llvm.orc-rt"'`).
//
// Note: os_log_create deduplicates by (subsystem, category), so if two
// threads race here they get the same handle back; storing either one is
// correct and no lock is needed. The store publishes it for future warm-path
// loads.
os_log_t Handle =
os_log_create("org.llvm.orc-rt", orc_rt_log_Category_getName(Category));
__atomic_store_n(&orc_rt_log_OSLogHandles[Category], Handle,
__ATOMIC_RELEASE);
return Handle;
}

View file

@ -1,6 +1,7 @@
# -*- Python -*-
import os
import random
import lit.formats
import lit.util
@ -62,6 +63,25 @@ def add_logging_features():
add_logging_features()
# The os_log delivery tests scrape the unified log (via `log show`), which is
# slow and timing-sensitive, so they are opt-in: pass --param run-os-log-tests=1
# to enable them. They also need the `log` tool. Warn if the tests were
# requested but `log` is unavailable, so the request doesn't silently no-op.
if lit_config.params.get("run-os-log-tests"):
if lit.util.which("log"):
config.available_features.add("os-log-show-tests")
# A per-invocation id (stable across ALLOW_RETRIES) that the delivery
# test emits and matches, so it can't match a stale record from an
# earlier run.
config.substitutions.append(
("%{orc-rt-log-uid}", str(random.randint(1, 2**31 - 1)))
)
else:
lit_config.warning(
"run-os-log-tests was requested, but the 'log' tool was not found; "
"the os_log delivery tests will be skipped"
)
# Give logging tests a deterministic baseline: clear any logging environment
# inherited from the developer's shell. Tests opt in with `env ORC_RT_LOG=...`.
for var in ("ORC_RT_LOG", "ORC_RT_LOG_OUTPUT"):

View file

@ -0,0 +1,17 @@
# Verify that an emitted record reaches the unified log with the expected
# subsystem and category. Opt-in (llvm-lit --param run-os-log-tests=1).
#
# A unique per-run id makes the match specific to this run's record, not a
# stale one from an earlier run. It is passed both to the tool (which emits it)
# and to FileCheck via -Duid (lit substitutes %{...} on RUN lines, but not in
# the CHECK text, so the value reaches the pattern as [[uid]]). The record is
# emitted at ERROR level, which os_log persists, so `log show` sees it;
#
# REQUIRES: os-log-show-tests
# RUN: orc-rt-log-check --uid %{orc-rt-log-uid}
# RUN: log show --predicate \
# RUN: 'subsystem == "org.llvm.orc-rt" && category == "General"' \
# RUN: --style compact --last 15s \
# RUN: | FileCheck -Duid=%{orc-rt-log-uid} %s
# CHECK: delivery marker uid=[[uid]]

View file

@ -0,0 +1,5 @@
# These tests exercise the os_log logging backend, which is selected at build
# time. Skip the whole directory unless that backend is compiled in.
if "orc-rt-log-backend-os_log" not in config.available_features:
config.unsupported = True

View file

@ -0,0 +1,6 @@
# Verify that the os_log backend does not generate stderr/stdout messages.
#
# REQUIRES: orc-rt-log-level-error
# RUN: orc-rt-log-check 2>&1 | FileCheck %s --allow-empty
# CHECK-NOT: [orc-rt:General:

View file

@ -65,12 +65,17 @@ int main(int argc, char *argv[]) {
bool PrintBackend = false;
bool PrintEnabledLevels = false;
bool PrintHelp = false;
int UID = -1;
{
orc_rt::CommandLineParser P;
P.addFlag("print-backend", "Print log backend", false, PrintBackend)
.addFlag("print-enabled-levels", "Print enabled log levels", false,
PrintEnabledLevels)
.addValue("uid",
"Emit one marker record carrying this id (for os_log "
"delivery tests)",
-1, UID)
.addFlag("help", "Print help", false, PrintHelp);
if (auto Err = P.parse(argc, argv)) {
@ -99,6 +104,14 @@ int main(int argc, char *argv[]) {
if (PrintBackend || PrintEnabledLevels)
return 0;
if (UID != -1) {
// Emit one record whose payload carries the caller-supplied id, so a
// delivery test can match exactly its own record and not a stale one. The
// id is an integer (a %d scalar), which os_log shows unredacted.
ORC_RT_LOG(Error, General, "delivery marker uid=%d", UID);
return 0;
}
emitAllLevels();
return 0;