use core::{
cmp::min, mem::MaybeUninit, ops::Deref, ptr::copy_nonoverlapping, slice::from_raw_parts,
};
#[cfg(any(target_os = "solana", target_arch = "bpf"))]
use solana_define_syscall::definitions::{sol_log_, sol_remaining_compute_units};
const TRUNCATED_SLICE: [u8; 3] = [b'.', b'.', b'.'];
const TRUNCATED: u8 = b'@';
const UNINIT_BYTE: MaybeUninit<u8> = MaybeUninit::uninit();
pub struct Logger<const BUFFER: usize> {
buffer: [MaybeUninit<u8>; BUFFER],
len: usize,
}
impl<const BUFFER: usize> Default for Logger<BUFFER> {
#[inline]
fn default() -> Self {
Self {
buffer: [UNINIT_BYTE; BUFFER],
len: 0,
}
}
}
impl<const BUFFER: usize> Deref for Logger<BUFFER> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
unsafe { from_raw_parts(self.buffer.as_ptr() as *const _, self.len) }
}
}
impl<const BUFFER: usize> Logger<BUFFER> {
#[inline(always)]
pub fn append<T: Log>(&mut self, value: T) -> &mut Self {
self.append_with_args(value, &[]);
self
}
#[inline]
pub fn append_with_args<T: Log>(&mut self, value: T, args: &[Argument]) -> &mut Self {
if self.is_full() {
if BUFFER > 0 {
unsafe {
let last = self.buffer.get_unchecked_mut(BUFFER - 1);
last.write(TRUNCATED);
}
}
} else {
self.len += value.write_with_args(&mut self.buffer[self.len..], args);
if self.len > BUFFER {
self.len = BUFFER;
unsafe {
let last = self.buffer.get_unchecked_mut(BUFFER - 1);
last.write(TRUNCATED);
}
}
}
self
}
#[inline(always)]
pub fn log(&self) {
log_message(self);
}
#[inline(always)]
pub fn clear(&mut self) {
self.len = 0;
}
#[inline(always)]
pub fn is_full(&self) -> bool {
self.len == BUFFER
}
#[inline(always)]
pub fn remaining(&self) -> usize {
BUFFER - self.len
}
}
#[inline(always)]
pub fn log_message(message: &[u8]) {
#[cfg(any(target_os = "solana", target_arch = "bpf"))]
unsafe {
sol_log_(message.as_ptr(), message.len() as u64);
}
#[cfg(all(not(any(target_os = "solana", target_arch = "bpf")), feature = "std"))]
{
let message = core::str::from_utf8(message).unwrap();
std::println!("{message}");
}
#[cfg(all(
not(any(target_os = "solana", target_arch = "bpf")),
not(feature = "std")
))]
core::hint::black_box(message);
}
#[inline(always)]
pub fn remaining_compute_units() -> u64 {
#[cfg(any(target_os = "solana", target_arch = "bpf"))]
unsafe {
sol_remaining_compute_units()
}
#[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
core::hint::black_box(0u64)
}
#[non_exhaustive]
pub enum Argument {
Precision(u8),
TruncateEnd(usize),
TruncateStart(usize),
}
pub unsafe trait Log {
#[inline(always)]
fn debug(&self, buffer: &mut [MaybeUninit<u8>]) -> usize {
self.debug_with_args(buffer, &[])
}
#[inline(always)]
fn debug_with_args(&self, buffer: &mut [MaybeUninit<u8>], args: &[Argument]) -> usize {
self.write_with_args(buffer, args)
}
#[inline(always)]
fn write(&self, buffer: &mut [MaybeUninit<u8>]) -> usize {
self.write_with_args(buffer, &[])
}
fn write_with_args(&self, buffer: &mut [MaybeUninit<u8>], parameters: &[Argument]) -> usize;
}
macro_rules! impl_log_for_unsigned_integer {
( $type:tt ) => {
unsafe impl Log for $type {
#[inline]
fn write_with_args(&self, buffer: &mut [MaybeUninit<u8>], args: &[Argument]) -> usize {
const MAX_DIGITS: usize = $type::MAX.ilog10() as usize + 1;
if buffer.is_empty() {
return 0;
}
match *self {
0 => {
unsafe {
buffer.get_unchecked_mut(0).write(b'0');
}
1
}
mut value => {
let mut digits = [UNINIT_BYTE; MAX_DIGITS];
let mut offset = MAX_DIGITS;
while value > 0 {
let remainder = value % 10;
value /= 10;
offset -= 1;
unsafe {
digits
.get_unchecked_mut(offset)
.write(b'0' + remainder as u8);
}
}
let precision = if let Some(Argument::Precision(p)) = args
.iter()
.find(|arg| matches!(arg, Argument::Precision(_)))
{
*p as usize
} else {
0
};
let written = MAX_DIGITS - offset;
let length = buffer.len();
let required = match precision {
0 => written,
_precision if precision < written => written + 1,
_ => precision + 2,
};
let is_truncated = required > length;
let digits_to_write = min(MAX_DIGITS - offset, length);
unsafe {
let source = digits.as_ptr().add(offset);
let ptr = buffer.as_mut_ptr();
if precision == 0 {
copy_nonoverlapping(source, ptr, digits_to_write);
}
else if precision >= digits_to_write {
(ptr as *mut u8).write(b'0');
if length > 2 {
(ptr.add(1) as *mut u8).write(b'.');
let padding = min(length - 2, precision - digits_to_write);
(ptr.add(2) as *mut u8).write_bytes(b'0', padding);
let current = 2 + padding;
if current < length {
let remaining = min(digits_to_write, length - current);
copy_nonoverlapping(source, ptr.add(current), remaining);
}
}
}
else {
let integer_part = digits_to_write - precision;
copy_nonoverlapping(source, ptr, integer_part);
(ptr.add(integer_part) as *mut u8).write(b'.');
let current = integer_part + 1;
if current < length {
let remaining = min(precision, length - current);
copy_nonoverlapping(
source.add(integer_part),
ptr.add(current),
remaining,
);
}
}
}
let written = min(required, length);
if is_truncated {
unsafe {
buffer.get_unchecked_mut(written - 1).write(TRUNCATED);
}
}
written
}
}
}
}
};
}
impl_log_for_unsigned_integer!(u8);
impl_log_for_unsigned_integer!(u16);
impl_log_for_unsigned_integer!(u32);
impl_log_for_unsigned_integer!(u64);
impl_log_for_unsigned_integer!(usize);
#[cfg(not(target_arch = "bpf"))]
impl_log_for_unsigned_integer!(u128);
macro_rules! impl_log_for_signed {
( $type:tt ) => {
unsafe impl Log for $type {
#[inline]
fn write_with_args(&self, buffer: &mut [MaybeUninit<u8>], args: &[Argument]) -> usize {
if buffer.is_empty() {
return 0;
}
match *self {
0 => {
unsafe {
buffer.get_unchecked_mut(0).write(b'0');
}
1
}
value => {
let mut prefix = 0;
if *self < 0 {
if buffer.len() == 1 {
unsafe {
buffer.get_unchecked_mut(0).write(TRUNCATED);
}
return 1;
}
unsafe {
buffer.get_unchecked_mut(0).write(b'-');
}
prefix += 1;
};
prefix
+ $type::unsigned_abs(value)
.write_with_args(&mut buffer[prefix..], args)
}
}
}
}
};
}
impl_log_for_signed!(i8);
impl_log_for_signed!(i16);
impl_log_for_signed!(i32);
impl_log_for_signed!(i64);
impl_log_for_signed!(isize);
#[cfg(not(target_arch = "bpf"))]
impl_log_for_signed!(i128);
unsafe impl Log for &str {
#[inline]
fn debug_with_args(&self, buffer: &mut [MaybeUninit<u8>], _args: &[Argument]) -> usize {
if buffer.is_empty() {
return 0;
}
unsafe {
buffer.get_unchecked_mut(0).write(b'"');
}
let mut offset = 1;
offset += self.write(&mut buffer[offset..]);
match buffer.len() - offset {
0 => {
unsafe {
buffer.get_unchecked_mut(offset - 1).write(TRUNCATED);
}
}
_ => {
unsafe {
buffer.get_unchecked_mut(offset).write(b'"');
}
offset += 1;
}
}
offset
}
#[inline]
fn write_with_args(&self, buffer: &mut [MaybeUninit<u8>], args: &[Argument]) -> usize {
let (size, truncate_end) = match args
.iter()
.find(|arg| matches!(arg, Argument::TruncateEnd(_) | Argument::TruncateStart(_)))
{
Some(Argument::TruncateEnd(size)) => (*size, Some(true)),
Some(Argument::TruncateStart(size)) => (*size, Some(false)),
_ => (buffer.len(), None),
};
let (destination, source, length_to_write, written_truncated_slice_length, truncated) =
if truncate_end.is_none() {
let length = min(size, self.len());
(
buffer.as_mut_ptr(),
self.as_ptr(),
length,
0,
length != self.len(),
)
} else {
let max_length = min(size, buffer.len());
let ptr = buffer.as_mut_ptr();
if max_length >= self.len() {
(ptr, self.as_ptr(), self.len(), 0, false)
}
else if max_length > TRUNCATED_SLICE.len() {
let length = max_length - TRUNCATED_SLICE.len();
unsafe {
let (offset, source, destination) = if truncate_end == Some(true) {
(length, self.as_ptr(), ptr)
} else {
(
0,
self.as_ptr().add(self.len() - length),
ptr.add(TRUNCATED_SLICE.len()),
)
};
copy_nonoverlapping(
TRUNCATED_SLICE.as_ptr(),
ptr.add(offset) as *mut _,
TRUNCATED_SLICE.len(),
);
(destination, source, length, TRUNCATED_SLICE.len(), false)
}
}
else {
(ptr, TRUNCATED_SLICE.as_ptr(), max_length, 0, true)
}
};
if length_to_write > 0 {
unsafe {
copy_nonoverlapping(source, destination as *mut _, length_to_write);
}
if truncated {
unsafe {
let last = buffer.get_unchecked_mut(length_to_write - 1);
last.write(TRUNCATED);
}
}
}
written_truncated_slice_length + length_to_write
}
}
macro_rules! impl_log_for_slice {
( [$type:ident] ) => {
unsafe impl<$type> Log for &[$type]
where
$type: Log
{
impl_log_for_slice!(@generate_write);
}
};
( [$type:ident; $size:ident] ) => {
unsafe impl<$type, const $size: usize> Log for &[$type; $size]
where
$type: Log
{
impl_log_for_slice!(@generate_write);
}
};
( @generate_write ) => {
#[inline]
fn write_with_args(&self, buffer: &mut [MaybeUninit<u8>], _args: &[Argument]) -> usize {
if buffer.is_empty() {
return 0;
}
let length = buffer.len();
unsafe {
buffer.get_unchecked_mut(0).write(b'[');
}
let mut offset = 1;
for value in self.iter() {
if offset >= length {
unsafe {
buffer.get_unchecked_mut(length - 1).write(TRUNCATED);
}
offset = length;
break;
}
if offset > 1 {
if offset + 2 >= length {
unsafe {
buffer.get_unchecked_mut(length - 1).write(TRUNCATED);
}
offset = length;
break;
} else {
unsafe {
buffer.get_unchecked_mut(offset).write(b',');
buffer.get_unchecked_mut(offset + 1).write(b' ');
}
offset += 2;
}
}
offset += value.debug(&mut buffer[offset..]);
}
if offset < length {
unsafe {
buffer.get_unchecked_mut(offset).write(b']');
}
offset += 1;
}
offset
}
};
}
impl_log_for_slice!([T]);
impl_log_for_slice!([T; N]);
unsafe impl Log for bool {
#[inline]
fn debug_with_args(&self, buffer: &mut [MaybeUninit<u8>], args: &[Argument]) -> usize {
let value = if *self { "true" } else { "false" };
value.debug_with_args(buffer, args)
}
#[inline]
fn write_with_args(&self, buffer: &mut [MaybeUninit<u8>], args: &[Argument]) -> usize {
let value = if *self { "true" } else { "false" };
value.write_with_args(buffer, args)
}
}