use skippy_ffi::{
FEATURE_DEVICE_EVENTS, FEATURE_DIAGNOSTIC_EVENTS, FEATURE_KV_EVENTS, FEATURE_LAYA_DECISIONS,
FEATURE_MODEL_LOAD_EVENTS_V2, FEATURE_NON_CHAT_WORKLOADS, FEATURE_RUNTIME_EVENT_REPORTER,
FEATURE_RUNTIME_EVENTS, FEATURE_UNLOAD_EVENTS,
};
use crate::logging::write_native_log_note;
use crate::runtime_events::abi_features_bitmask;
const MAX_KNOWN_FEATURE_BIT: u32 = 40;
struct FamilySpec {
bit: u64,
name: &'static str,
required_symbols: &'static [&'static [u8]],
}
const FAMILIES: &[FamilySpec] = &[
FamilySpec {
bit: FEATURE_RUNTIME_EVENTS,
name: "runtime_events",
required_symbols: &[
b"skippy_model_open_with_events\0",
b"skippy_model_open_from_parts_with_events\0",
],
},
FamilySpec {
bit: FEATURE_RUNTIME_EVENT_REPORTER,
name: "runtime_event_reporter",
required_symbols: &[
b"skippy_set_runtime_event_reporter\0",
b"skippy_clear_runtime_event_reporter\0",
],
},
FamilySpec {
bit: FEATURE_MODEL_LOAD_EVENTS_V2,
name: "model_load_events_v2",
required_symbols: &[b"skippy_emit_model_load_event_v2\0"],
},
FamilySpec {
bit: FEATURE_KV_EVENTS,
name: "kv_events",
required_symbols: &[b"skippy_emit_kv_event\0"],
},
FamilySpec {
bit: FEATURE_DEVICE_EVENTS,
name: "device_events",
required_symbols: &[b"skippy_emit_device_event\0"],
},
FamilySpec {
bit: FEATURE_DIAGNOSTIC_EVENTS,
name: "diagnostic_events",
required_symbols: &[b"skippy_emit_diagnostic_event\0"],
},
FamilySpec {
bit: FEATURE_UNLOAD_EVENTS,
name: "unload_events",
required_symbols: &[b"skippy_emit_unload_event\0"],
},
FamilySpec {
bit: FEATURE_NON_CHAT_WORKLOADS,
name: "non_chat_workloads",
required_symbols: &[
b"skippy_model_workload_info_v1\0",
b"skippy_session_embed\0",
b"skippy_session_rerank\0",
b"skippy_session_encode_prompt\0",
],
},
FamilySpec {
bit: FEATURE_LAYA_DECISIONS,
name: "laya_decisions",
required_symbols: &[
b"skippy_laya_model_open\0",
b"skippy_laya_model_free\0",
b"skippy_laya_model_info_v1\0",
b"skippy_laya_model_memory_v1\0",
b"skippy_laya_tokenize\0",
b"skippy_laya_read\0",
],
},
];
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CapabilityReport {
pub confirmed: u64,
pub health_messages: Vec<String>,
}
impl CapabilityReport {
pub fn family_confirmed(&self, bit: u64) -> bool {
self.confirmed & bit != 0
}
}
pub(crate) fn build_report(
features: u64,
symbol_exists: impl Fn(&[u8]) -> bool,
) -> CapabilityReport {
let mut confirmed = 0u64;
let mut health_messages = Vec::new();
let reserved_mask = !((1u64 << (MAX_KNOWN_FEATURE_BIT + 1)) - 1);
let reserved_bits = features & reserved_mask;
if reserved_bits != 0 {
health_messages.push(format!(
"skippy capability probe: reserved/unknown feature bits set: {reserved_bits:#x}"
));
}
for family in FAMILIES {
if features & family.bit == 0 {
continue;
}
let all_present = family
.required_symbols
.iter()
.all(|symbol| symbol_exists(symbol));
if all_present {
confirmed |= family.bit;
} else {
health_messages.push(format!(
"skippy capability probe: family '{}' advertised feature bit {:#x} but a required symbol is missing; disabling this family only",
family.name, family.bit
));
}
}
CapabilityReport {
confirmed,
health_messages,
}
}
pub(crate) fn symbol_available(name: &[u8]) -> bool {
#[cfg(feature = "dynamic-native-runtime")]
{
skippy_ffi::symbol_present(name)
}
#[cfg(not(feature = "dynamic-native-runtime"))]
{
#[cfg(unix)]
{
let symbol = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr().cast()) };
!symbol.is_null()
}
#[cfg(not(unix))]
{
let _ = name;
false
}
}
}
pub fn probe_capabilities() -> CapabilityReport {
if !skippy_ffi::native_runtime_loaded() {
return CapabilityReport::default();
}
let Some(features) = abi_features_bitmask() else {
return CapabilityReport::default();
};
let report = build_report(features, symbol_available);
for message in &report.health_messages {
write_native_log_note(message);
}
report
}
#[cfg(test)]
mod tests {
use super::*;
const WORKLOAD_SYMBOLS: &[&[u8]] = &[
b"skippy_model_workload_info_v1\0",
b"skippy_session_embed\0",
b"skippy_session_rerank\0",
b"skippy_session_encode_prompt\0",
];
#[test]
fn complete_workload_family_checks_all_exports_without_reserved_warning() {
let checked = std::cell::RefCell::new(Vec::new());
let report = build_report(FEATURE_NON_CHAT_WORKLOADS, |symbol| {
checked.borrow_mut().push(symbol.to_vec());
WORKLOAD_SYMBOLS.contains(&symbol)
});
assert_eq!(report.confirmed, FEATURE_NON_CHAT_WORKLOADS);
assert!(report.health_messages.is_empty());
assert_eq!(*checked.borrow(), WORKLOAD_SYMBOLS);
}
#[test]
fn each_missing_workload_export_disables_only_the_workload_family() {
for missing in WORKLOAD_SYMBOLS {
let report = build_report(FEATURE_NON_CHAT_WORKLOADS | FEATURE_KV_EVENTS, |symbol| {
symbol != *missing
});
assert!(!report.family_confirmed(FEATURE_NON_CHAT_WORKLOADS));
assert!(report.family_confirmed(FEATURE_KV_EVENTS));
assert_eq!(report.health_messages.len(), 1);
assert!(report.health_messages[0].contains("non_chat_workloads"));
assert!(!report.health_messages[0].contains("reserved"));
}
}
#[test]
fn unadvertised_workload_family_does_not_resolve_its_exports() {
let report = build_report(FEATURE_KV_EVENTS, |symbol| {
assert!(!WORKLOAD_SYMBOLS.contains(&symbol));
true
});
assert_eq!(report.confirmed, FEATURE_KV_EVENTS);
assert!(report.health_messages.is_empty());
}
#[test]
fn the_bit_after_the_highest_known_bit_remains_reserved() {
let report = build_report(
FEATURE_NON_CHAT_WORKLOADS | (1 << (MAX_KNOWN_FEATURE_BIT + 1)),
|_| true,
);
assert!(report.family_confirmed(FEATURE_NON_CHAT_WORKLOADS));
assert_eq!(report.health_messages.len(), 1);
assert!(report.health_messages[0].contains("reserved"));
}
#[test]
fn named_non_family_bits_are_not_reported_as_reserved() {
let report = build_report(
FEATURE_KV_EVENTS
| skippy_ffi::FEATURE_SYSTEM_ONE
| skippy_ffi::FEATURE_CACHEGEN_KV_PAGE,
|_| true,
);
assert_eq!(report.confirmed, FEATURE_KV_EVENTS);
assert!(report.health_messages.is_empty());
}
#[test]
fn max_known_feature_bit_is_the_highest_bit_skippy_ffi_defines() {
let abi = std::fs::read_to_string(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../skippy-ffi/src/abi.rs"
))
.expect("skippy-ffi's abi.rs is in the workspace");
let highest = abi
.lines()
.map(str::trim)
.filter(|line| line.starts_with("pub const FEATURE_"))
.map(|line| {
line.split_once(": u64 = ")
.and_then(|(_, initializer)| initializer.strip_suffix(';'))
.and_then(|initializer| initializer.strip_prefix("1 << "))
.and_then(|shift| shift.parse::<u32>().ok())
.unwrap_or_else(|| {
panic!(
"cannot read a feature bit from `{line}`: keep FEATURE_* \
constants as `u64 = 1 << N;` or teach this guard the new form"
)
})
})
.max()
.expect("skippy-ffi defines feature bits");
assert_eq!(
highest, MAX_KNOWN_FEATURE_BIT,
"a new FEATURE_* bit in skippy-ffi must raise MAX_KNOWN_FEATURE_BIT, \
or every runtime that sets it logs a reserved-bit warning"
);
}
#[test]
fn missing_family_falls_back_without_a_health_message() {
let report = build_report(0, |_| false);
assert_eq!(report.confirmed, 0);
assert!(report.health_messages.is_empty());
}
#[test]
fn complete_family_is_confirmed_without_a_health_message() {
let report = build_report(FEATURE_KV_EVENTS, |_| true);
assert!(report.family_confirmed(FEATURE_KV_EVENTS));
assert!(report.health_messages.is_empty());
}
#[test]
fn incomplete_symbols_disable_only_that_family() {
let report = build_report(FEATURE_KV_EVENTS | FEATURE_DEVICE_EVENTS, |symbol| {
symbol != b"skippy_emit_kv_event\0"
});
assert!(!report.family_confirmed(FEATURE_KV_EVENTS));
assert!(report.family_confirmed(FEATURE_DEVICE_EVENTS));
assert_eq!(report.health_messages.len(), 1);
assert!(report.health_messages[0].contains("kv_events"));
}
#[test]
fn independent_families_probe_independently() {
let report = build_report(
FEATURE_RUNTIME_EVENTS | FEATURE_RUNTIME_EVENT_REPORTER | FEATURE_UNLOAD_EVENTS,
|symbol| symbol != b"skippy_set_runtime_event_reporter\0",
);
assert!(report.family_confirmed(FEATURE_RUNTIME_EVENTS));
assert!(!report.family_confirmed(FEATURE_RUNTIME_EVENT_REPORTER));
assert!(report.family_confirmed(FEATURE_UNLOAD_EVENTS));
assert_eq!(report.health_messages.len(), 1);
}
#[test]
fn malformed_reserved_bits_emit_one_message_without_disabling_a_family() {
let reserved_bit = 1u64 << (MAX_KNOWN_FEATURE_BIT + 1);
let report = build_report(FEATURE_KV_EVENTS | reserved_bit, |_| true);
assert!(report.family_confirmed(FEATURE_KV_EVENTS));
assert_eq!(report.health_messages.len(), 1);
assert!(report.health_messages[0].contains("reserved"));
}
#[test]
fn larger_append_only_features_value_does_not_confuse_known_bits() {
let future_bits = 0b111u64 << 60;
let report = build_report(FEATURE_DIAGNOSTIC_EVENTS | future_bits, |_| true);
assert!(report.family_confirmed(FEATURE_DIAGNOSTIC_EVENTS));
assert_eq!(report.health_messages.len(), 1);
}
}