blob: b5d80c86f0595d45fa119299c287e20c5b7832f0 [file] [log] [blame]
// Copyright 2025 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// This code was largely generated by Gemini for use in this testsuite only.
#include <inttypes.h>
#include <stdarg.h> // For va_list
#include <string.h>
#include <unistd.h>
// The file descriptor for Standard Output
#define STDOUT_FILENO 1
// Define a reasonable buffer size for the printf wrapper
#define PRINTF_BUFFER_SIZE 256
// --- Helper Functions ---
/**
* @brief Performs the low-level write syscall for the specified architecture.
* This replaces the standard write(2) function call.
*
* @param fd The file descriptor (e.g., STDOUT_FILENO).
* @param buf The buffer to write.
* @param count The number of bytes to write.
* @return The number of bytes written, or -1 on error.
*/
static ssize_t syscall_write(int fd, const void *buf, size_t count) {
ssize_t ret;
const long fd_long = (long)fd;
const long count_long = (long)count;
const long buf_long = (long)buf;
// Check for x86-64 (AMD64)
#if defined(__x86_64__)
__asm__ __volatile__(
"movq $1, %%rax\n" // Syscall number 1 (write) -> rax
"movq %1, %%rdi\n" // fd -> rdi (Arg 1)
"movq %2, %%rsi\n" // buf -> rsi (Arg 2)
"movq %3, %%rdx\n" // count -> rdx (Arg 3)
"syscall\n" // Execute syscall
"movq %%rax, %0\n" // result from rax -> ret
: "=r"(ret) // Output: ret
: "r"(fd_long), // Input 1: fd
"r"(buf_long), // Input 2: buf
"r"(count_long) // Input 3: count
: "%rax", "%rdi", "%rsi", "%rdx", "memory" // Clobbered registers
);
// Check for ARM64 (AArch64)
#elif defined(__aarch64__)
__asm__ __volatile__(
"mov x8, #64\n" // Syscall number 64 (write) -> x8 (Syscall register)
"mov x0, %1\n" // fd -> x0 (Arg 1)
"mov x1, %2\n" // buf -> x1 (Arg 2)
"mov x2, %3\n" // count -> x2 (Arg 3)
"svc #0\n" // Execute syscall
"mov %0, x0\n" // result from x0 -> ret
: "=r"(ret)
: "r"(fd_long), "r"(buf_long), "r"(count_long)
: "x0", "x1", "x2", "x8", "memory");
// Check for 32-bit ARM (e.g., armv7)
#elif defined(__arm__)
// Syscall number 4 (write)
__asm__ __volatile__(
"mov r7, #4\n" // Syscall number 4 (write) -> r7 (Syscall register)
"mov r0, %1\n" // fd -> r0 (Arg 1)
"mov r1, %2\n" // buf -> r1 (Arg 2)
"mov r2, %3\n" // count -> r2 (Arg 3)
"svc #0\n" // Execute syscall
"mov %0, r0\n" // result from r0 -> ret
: "=r"(ret)
: "r"(fd_long), "r"(buf_long), "r"(count_long)
: "r0", "r1", "r2", "r7", "memory");
// Check for RISC-V 64-bit (RV64)
#elif defined(__riscv) && (__riscv_xlen == 64)
// Syscall number 64 (write)
__asm__ __volatile__(
"li a7, 64\n" // Syscall number 64 (write) -> a7 (Syscall register)
"mv a0, %1\n" // fd -> a0 (Arg 1)
"mv a1, %2\n" // buf -> a1 (Arg 2)
"mv a2, %3\n" // count -> a2 (Arg 3)
"ecall\n" // Execute syscall
"mv %0, a0\n" // result from a0 -> ret
: "=r"(ret)
: "r"(fd_long), "r"(buf_long), "r"(count_long)
: "a0", "a1", "a2", "a7", "memory");
#else
ret = -1;
#endif
return ret;
}
/**
* @brief Manual implementation of string length calculation.
* @param s The null-terminated string.
* @return The length of the string (excluding null terminator).
*/
extern "C" size_t strlen(const char *__s) {
size_t len = 0;
while (*__s++) {
len++;
}
return len;
}
/**
* @brief Models the low-level writing of data to a memory buffer.
*
* This function serves the role of the low-level I/O operation (like write())
* but targets the memory buffer defined by the vsnprintf signature.
*
* @param dest The destination memory buffer (str).
* @param max_size The maximum size of the destination buffer (including null terminator).
* @param current_pos A pointer to track the total required length (required size).
* @param data The source data to copy.
* @param len The length of the source data.
* @return The number of characters successfully written to 'dest' (clipped by max_size).
*/
static size_t _write_to_buffer(char *dest, size_t max_size, size_t *current_pos, const char *data,
size_t len) {
size_t written_to_dest = 0;
size_t start_pos = *current_pos;
size_t i = 0;
// Advance the internal counter by the full length of data (tracks the required size)
*current_pos += len;
// Only write to 'dest' if we haven't hit max_size yet
while (i < len) {
if (dest && max_size > 0 && start_pos + i < max_size) {
// Write to the actual destination buffer
dest[start_pos + i] = data[i];
written_to_dest++;
}
i++;
}
return written_to_dest;
}
/**
* @brief Manual integer to string conversion (base 10 only).
*
* @param value The integer value to convert.
* @param buffer The temporary buffer to hold the resulting ASCII string.
* @param radix The base (must be 10 here).
* @param is_unsigned Flag if the value should be treated as unsigned.
* @return The length of the resulting string (excluding null terminator).
*/
static size_t _itoa_manual(long long value, char *buffer, int radix, int is_unsigned) {
if (radix != 10)
return 0;
const char digits[] = "0123456789";
char temp[32]; // Temporary buffer for reverse string
int i = 0;
int sign = 0;
// --- Handle Conversion ---
if (!is_unsigned && value < 0) {
sign = 1;
value = -value;
}
// Handle 0 case
if (value == 0 && !is_unsigned) {
temp[i++] = '0';
} else if (is_unsigned) {
unsigned long long un = (unsigned long long)value;
if (un == 0) {
temp[i++] = '0';
} else {
while (un != 0) {
temp[i++] = digits[un % 10];
un /= 10;
}
}
} else {
// Signed positive case
long long n = value;
while (n != 0) {
temp[i++] = digits[n % 10];
n /= 10;
}
}
// Add sign
if (sign) {
temp[i++] = '-';
}
// Reverse the string and copy to the target buffer
size_t len = i;
for (size_t j = 0; j < len; ++j) {
buffer[j] = temp[len - 1 - j];
}
return len;
}
// --- Main vsnprintf Implementation ---
/**
* @brief Custom vsnprintf implementation using low-level memory operations.
*
* Supports basic %d/%i (signed int), %u (unsigned int), %s (string), and %c (char).
*
* @param str The destination buffer. Can be NULL if size is 0.
* @param size The maximum number of bytes to be written to str (including the null terminator).
* @param format The format string.
* @param ap The va_list of arguments.
* @return The number of characters that would have been written if size was unlimited
* (excluding the null terminator).
*/
int vsnprintf_custom(char *str, size_t size, const char *format, va_list ap) {
size_t total_written_required = 0;
const char *p = format;
// Initialize the buffer with null termination
if (size > 0 && str) {
str[0] = '\0';
}
// Iterate through the format string
while (*p != '\0') {
if (*p != '%') {
_write_to_buffer(str, size, &total_written_required, p, 1);
p++;
continue;
}
p++; // Skip '%'
// Handle '%%' escape
if (*p == '%') {
_write_to_buffer(str, size, &total_written_required, p, 1);
p++;
continue;
}
char temp_buffer[64]; // Buffer for formatted numbers
size_t len = 0;
// Copy va_list to safely handle required promotions for printing
va_list current_ap;
va_copy(current_ap, ap);
switch (*p) {
case 'd':
case 'i': {
// int is promoted to long long in va_arg for safe handling
long long val = va_arg(ap, int);
len = _itoa_manual(val, temp_buffer, 10, 0);
_write_to_buffer(str, size, &total_written_required, temp_buffer, len);
break;
}
case 'u': {
// unsigned int is promoted to unsigned long long in va_arg
unsigned long long val = va_arg(ap, unsigned int);
len = _itoa_manual(val, temp_buffer, 10, 1);
_write_to_buffer(str, size, &total_written_required, temp_buffer, len);
break;
}
case 's': {
const char *s = va_arg(ap, const char *);
if (s == NULL)
s = "(null)";
len = strlen(s);
_write_to_buffer(str, size, &total_written_required, s, len);
break;
}
case 'c': {
// char is promoted to int in va_arg
char c = (char)va_arg(ap, int);
_write_to_buffer(str, size, &total_written_required, &c, 1);
break;
}
default:
// If unknown specifier, print the '%' and the char (e.g., "%x" prints "%x")
_write_to_buffer(str, size, &total_written_required, p - 1, 2);
break;
}
va_end(current_ap);
p++; // Move past the format specifier
}
// Null-terminate the string, ensuring we don't write past the buffer size
if (size > 0 && str) {
// The actual index to place the null terminator
size_t null_idx = (total_written_required < size) ? total_written_required : (size - 1);
str[null_idx] = '\0';
}
// Return the total length that *would* have been written (excluding null terminator)
return (int)total_written_required;
}
// Simple convenience wrapper, akin to the standard snprintf
int snprintf_manual(char *str, size_t size, const char *format, ...) {
va_list ap;
va_start(ap, format);
int result = vsnprintf_custom(str, size, format, ap);
va_end(ap);
return result;
}
/**
* @brief Custom printf wrapper using vsnprintf_custom for formatting and the
* write() system call for output (STDOUT_FILENO).
*
* @param format The format string.
* @param ... The variable arguments.
* @return The number of characters written to the file descriptor, or -1 on error.
*/
extern "C" __attribute__((visibility("default"))) int printf(const char *__restrict __format, ...) {
char buffer[PRINTF_BUFFER_SIZE];
va_list ap;
va_start(ap, __format);
// 1. Format the string into the buffer using the custom low-level vsnprintf
int required_len = vsnprintf_custom(buffer, PRINTF_BUFFER_SIZE, __format, ap);
va_end(ap);
// Determine the actual length to write. It is the minimum of the required length
// and the buffer's capacity minus the null terminator.
size_t actual_write_len = required_len;
if (actual_write_len >= PRINTF_BUFFER_SIZE) {
actual_write_len = PRINTF_BUFFER_SIZE - 1; // write up to the null terminator
}
// Check if there's anything to write
if (actual_write_len == 0) {
return 0;
}
// 2. Use the low-level write() system call to output the content
ssize_t written = syscall_write(STDOUT_FILENO, buffer, actual_write_len);
// Return the number of bytes written, or -1 if the write system call failed.
if (written < 0) {
return -1;
}
// Note: Standard printf returns the number of characters printed, which is written here.
return (int)written;
}