#![doc = include_str!("../docs/en/src/README.md")]
#![no_std]
#![allow(clippy::needless_doctest_main)]
mod arch;
pub mod code_manipulate;
mod os;
use code_manipulate::CodeManipulator;
#[derive(Debug)]
struct JumpEntry {
code: usize,
target: usize,
key: usize,
}
impl JumpEntry {
#[cfg(not(all(target_os = "windows", target_arch = "x86_64")))]
fn make_relative_address_absolute(&mut self) {
self.code = ((&raw const self.code) as usize).wrapping_add(self.code);
self.target = ((&raw const self.target) as usize).wrapping_add(self.target);
self.key = ((&raw const self.key) as usize).wrapping_add(self.key);
}
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
fn make_relative_address_absolute(&mut self) {
let code = (self.code as i32) as i64 as usize;
self.code = ((&raw const self.code) as usize).wrapping_add(code);
let target = (self.target as i32) as i64 as usize;
self.target = ((&raw const self.target) as usize).wrapping_add(target);
let key = (self.key as i32) as i64 as usize;
self.key = ((&raw const self.key) as usize).wrapping_add(key);
}
fn code_addr(&self) -> usize {
self.code
}
fn target_addr(&self) -> usize {
self.target
}
fn key_addr(&self) -> usize {
self.key & !1usize
}
fn likely_branch_is_true(&self) -> bool {
(self.key & 1usize) != 0
}
fn key_mut<M: CodeManipulator, const S: bool>(&self) -> &'static mut GenericStaticKey<M, S> {
unsafe { &mut *(self.key_addr() as *mut GenericStaticKey<M, S>) }
}
fn is_dummy(&self) -> bool {
self.code == 0
}
#[allow(unused)]
const fn dummy() -> Self {
Self {
code: 0,
target: 0,
key: 0,
}
}
}
pub struct GenericStaticKey<M: CodeManipulator, const S: bool> {
enabled: core::sync::atomic::AtomicBool,
entries: usize,
phantom: core::marker::PhantomData<M>,
}
pub type StaticKey<const S: bool> = GenericStaticKey<crate::os::ArchCodeManipulator, S>;
pub type StaticTrueKey = StaticKey<true>;
pub type StaticFalseKey = StaticKey<false>;
pub type RawStaticTrueKey<M> = GenericStaticKey<M, true>;
pub type RawStaticFalseKey<M> = GenericStaticKey<M, false>;
static DUMMY_STATIC_KEY: GenericStaticKey<code_manipulate::DummyCodeManipulator, false> =
GenericStaticKey::new(false);
impl<M: CodeManipulator, const S: bool> GenericStaticKey<M, S> {
#[inline(always)]
pub const fn initial_enabled(&self) -> bool {
S
}
const fn new(enabled: bool) -> Self {
Self {
enabled: core::sync::atomic::AtomicBool::new(enabled),
entries: 0,
phantom: core::marker::PhantomData,
}
}
fn entries(&self) -> *const JumpEntry {
self.entries as *const _
}
pub unsafe fn enable(&self) {
unsafe { static_key_update(self, true) }
}
pub unsafe fn disable(&self) {
unsafe { static_key_update(self, false) }
}
pub fn is_enabled(&self) -> bool {
self.enabled.load(core::sync::atomic::Ordering::Relaxed)
}
}
pub fn jump_entries_count() -> usize {
let jump_entry_start_addr = &raw mut os::JUMP_ENTRY_START;
let jump_entry_stop_addr = &raw mut os::JUMP_ENTRY_STOP;
unsafe { jump_entry_stop_addr.offset_from(jump_entry_start_addr) as usize }
}
static GLOBAL_INIT_STATE: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
const UNINITIALIZED: usize = 0;
const INITIALIZING: usize = 1;
const INITIALIZED: usize = 2;
pub fn global_init() {
if static_branch_unlikely!(DUMMY_STATIC_KEY) {
return;
}
match GLOBAL_INIT_STATE.compare_exchange(
UNINITIALIZED,
INITIALIZING,
core::sync::atomic::Ordering::Acquire,
core::sync::atomic::Ordering::Relaxed,
) {
Ok(UNINITIALIZED) => {
global_init_inner();
GLOBAL_INIT_STATE.store(INITIALIZED, core::sync::atomic::Ordering::Release);
}
Err(INITIALIZING) => {
while GLOBAL_INIT_STATE.load(core::sync::atomic::Ordering::Relaxed) == INITIALIZING {
core::hint::spin_loop();
}
}
_ => {
}
}
}
fn global_init_inner() {
let jump_entry_start_addr = &raw mut os::JUMP_ENTRY_START;
let jump_entry_stop_addr = &raw mut os::JUMP_ENTRY_STOP;
let jump_entry_len =
unsafe { jump_entry_stop_addr.offset_from(jump_entry_start_addr) as usize };
let jump_entries =
unsafe { core::slice::from_raw_parts_mut(jump_entry_start_addr, jump_entry_len) };
for jump_entry in jump_entries.iter_mut() {
if jump_entry.is_dummy() {
continue;
}
jump_entry.make_relative_address_absolute();
}
jump_entries.sort_unstable_by_key(|jump_entry| (jump_entry.key_addr(), jump_entry.code_addr()));
let mut last_key_addr = 0;
for jump_entry in jump_entries {
if jump_entry.is_dummy() {
continue;
}
let key_addr = jump_entry.key_addr();
if key_addr == last_key_addr {
continue;
}
let entries_start_addr = jump_entry as *mut _ as usize;
let key = jump_entry.key_mut::<code_manipulate::DummyCodeManipulator, true>();
key.entries = entries_start_addr;
last_key_addr = key_addr;
}
}
pub const fn new_static_false_key() -> StaticFalseKey {
StaticFalseKey::new(false)
}
pub const fn new_static_true_key() -> StaticTrueKey {
StaticTrueKey::new(true)
}
pub const fn new_static_false_key_generic<M: CodeManipulator>() -> RawStaticFalseKey<M> {
RawStaticFalseKey::<M>::new(false)
}
pub const fn new_static_true_key_generic<M: CodeManipulator>() -> RawStaticTrueKey<M> {
RawStaticTrueKey::<M>::new(true)
}
#[macro_export]
macro_rules! define_static_key_false {
($key: ident) => {
#[used]
static $key: $crate::StaticFalseKey = $crate::new_static_false_key();
};
}
#[macro_export]
macro_rules! define_static_key_true {
($key: ident) => {
#[used]
static $key: $crate::StaticTrueKey = $crate::new_static_true_key();
};
}
#[macro_export]
macro_rules! define_static_key_false_generic {
($key: ident, $manipulator: ty) => {
#[used]
static $key: $crate::RawStaticFalseKey<$manipulator> =
$crate::new_static_false_key_generic::<$manipulator>();
};
}
#[macro_export]
macro_rules! define_static_key_true_generic {
($key: ident, $manipulator: ty) => {
#[used]
static $key: $crate::RawStaticTrueKey<$manipulator> =
$crate::new_static_true_key_generic::<$manipulator>();
};
}
unsafe fn static_key_update<M: CodeManipulator, const S: bool>(
key: &GenericStaticKey<M, S>,
enabled: bool,
) {
if key.enabled.load(core::sync::atomic::Ordering::Relaxed) == enabled {
return;
}
key.enabled
.store(enabled, core::sync::atomic::Ordering::Relaxed);
let jump_entry_stop_addr = &raw const os::JUMP_ENTRY_STOP;
let mut jump_entry_addr = key.entries();
if jump_entry_addr.is_null() {
return;
}
loop {
if jump_entry_addr >= jump_entry_stop_addr {
break;
}
let jump_entry = unsafe { &*jump_entry_addr };
if key as *const _ as usize != jump_entry.key_addr() {
break;
}
unsafe {
jump_entry_update::<M>(jump_entry, enabled);
jump_entry_addr = jump_entry_addr.add(1);
}
}
}
#[derive(Debug)]
enum JumpLabelType {
Nop = 0,
Jmp = 1,
}
unsafe fn jump_entry_update<M: CodeManipulator>(jump_entry: &JumpEntry, enabled: bool) {
let jump_label_type = if enabled ^ jump_entry.likely_branch_is_true() {
JumpLabelType::Jmp
} else {
JumpLabelType::Nop
};
let code_bytes = arch::arch_jump_entry_instruction(jump_label_type, jump_entry);
unsafe {
M::write_code(jump_entry.code_addr() as *mut _, &code_bytes);
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! static_key_init_jmp_with_given_branch_likely {
($key:path, $branch:expr) => {'my_label: {
#[cfg(not(all(target_os = "windows", any(target_arch = "x86", target_arch = "x86_64"))))]
::core::arch::asm!(
$crate::arch_static_key_init_jmp_asm_template!(),
label {
break 'my_label !$branch;
},
sym $key,
const $branch as usize,
);
#[cfg(all(target_os = "windows", any(target_arch = "x86", target_arch = "x86_64")))]
::core::arch::asm!(
$crate::arch_static_key_init_jmp_asm_template!(),
label {
break 'my_label !$branch;
},
sym $key,
const $branch as usize,
options(att_syntax),
);
break 'my_label $branch;
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! static_key_init_nop_with_given_branch_likely {
($key:path, $branch:expr) => {'my_label: {
#[cfg(not(all(target_os = "windows", any(target_arch = "x86", target_arch = "x86_64"))))]
::core::arch::asm!(
$crate::arch_static_key_init_nop_asm_template!(),
label {
break 'my_label !$branch;
},
sym $key,
const $branch as usize,
);
#[cfg(all(target_os = "windows", any(target_arch = "x86", target_arch = "x86_64")))]
::core::arch::asm!(
$crate::arch_static_key_init_nop_asm_template!(),
label {
break 'my_label !$branch;
},
sym $key,
const $branch as usize,
options(att_syntax),
);
break 'my_label $branch;
}};
}
#[macro_export]
macro_rules! static_branch_unlikely {
($key:path) => {{
unsafe {
if $key.initial_enabled() {
$crate::static_key_init_jmp_with_given_branch_likely! { $key, false }
} else {
$crate::static_key_init_nop_with_given_branch_likely! { $key, false }
}
}
}};
}
#[macro_export]
macro_rules! static_branch_likely {
($key:path) => {{
unsafe {
if $key.initial_enabled() {
$crate::static_key_init_nop_with_given_branch_likely! { $key, true }
} else {
$crate::static_key_init_jmp_with_given_branch_likely! { $key, true }
}
}
}};
}