From e44176c8f0557f3dae9d012f8876a91f2f765f1b Mon Sep 17 00:00:00 2001 From: Volodymyr Turanskyy Date: Thu, 20 Aug 2026 11:32:24 +0100 Subject: [PATCH 1/2] [ATfE] Prototype: LLVM libc file IO support This is a prototype/RFC for adding file IO support to bare-metal LLVM libc. LLVM libc changes are intended to land in upstream LLVM - it is easier to show how the proposed interface is used with ATfE semihosting implementation though. LLVM libc changes: The embedding API libc/src/__support/OSUtil/baremetal/io.h is extended with these functions: - __llvm_libc_stdio_open - __llvm_libc_stdio_close - __llvm_libc_stdio_seek - __llvm_libc_stdio_set_buffer - __llvm_libc_stdio_flush which map naturally to, e.g. the API expected by picolibc. These are used to implement the following additional C library functions that are enough to implement file IO required by libc++ fstream: - fopen - fclose - fseek - fseeko - ftell - ftello - setbuf - setvbuf - fflush The embedding API uses opaque pointer as a cookie, the semihosting implementation then keeps a small struct with either just the semihosting handle for standard streams or also current position to support seeking for file streams. Semihosting implementation is unbuffered, however other implementations can provide it by default or through the buffering management functions (which are also required by C++ fstream). Other implementations can use the struct of function pointers pattern for the cookie to easily implement support for multiple types of file systems, e.g. UART, flash or FAT. The RFC is mainly about the embedding API and libc side implementation if it is reasonable for upstream LLVM libc. Note: There is a TODO comment whether we need embedding API for ungetc, what we can do is declare a weak hook: __llvm_libc_stdio_ungetc(void *cookie, unsigned char ch) and check in the current ungetc implementation if it is provided: use it if yes or keep current 1 char buffer otherwise. --- .../llvmlibc-support/semihost/semihost.cpp | 296 ++++++++++++++---- .../llvmlibc-support/semihost/semihost.h | 20 +- libc/config/baremetal/aarch64/entrypoints.txt | 8 + libc/config/baremetal/arm/entrypoints.txt | 8 + libc/src/__support/OSUtil/baremetal/io.h | 27 ++ libc/src/stdio/CMakeLists.txt | 28 +- libc/src/stdio/baremetal/CMakeLists.txt | 57 ++++ libc/src/stdio/baremetal/fclose.cpp | 12 + libc/src/stdio/baremetal/fflush.cpp | 6 +- libc/src/stdio/baremetal/fopen.cpp | 25 ++ libc/src/stdio/baremetal/fseek.cpp | 23 ++ libc/src/stdio/baremetal/fseeko.cpp | 31 ++ libc/src/stdio/baremetal/ftell.cpp | 24 ++ libc/src/stdio/baremetal/ftello.cpp | 32 ++ libc/src/stdio/baremetal/setbuf.cpp | 22 ++ libc/src/stdio/baremetal/setvbuf.cpp | 21 ++ libc/src/stdio/generic/CMakeLists.txt | 26 ++ libcxx/include/fstream | 8 +- libcxx/src/ios.instantiations.cpp | 2 +- 19 files changed, 586 insertions(+), 90 deletions(-) create mode 100644 libc/src/stdio/baremetal/fclose.cpp create mode 100644 libc/src/stdio/baremetal/fopen.cpp create mode 100644 libc/src/stdio/baremetal/fseek.cpp create mode 100644 libc/src/stdio/baremetal/fseeko.cpp create mode 100644 libc/src/stdio/baremetal/ftell.cpp create mode 100644 libc/src/stdio/baremetal/ftello.cpp create mode 100644 libc/src/stdio/baremetal/setbuf.cpp create mode 100644 libc/src/stdio/baremetal/setvbuf.cpp diff --git a/arm-software/embedded/llvmlibc-support/semihost/semihost.cpp b/arm-software/embedded/llvmlibc-support/semihost/semihost.cpp index db0781dfefa4..c699cf22173b 100644 --- a/arm-software/embedded/llvmlibc-support/semihost/semihost.cpp +++ b/arm-software/embedded/llvmlibc-support/semihost/semihost.cpp @@ -1,29 +1,168 @@ // -// Copyright (c) 2022-2025, Arm Limited and affiliates. +// Copyright (c) 2022-2026, Arm Limited and affiliates. // -// Part of the Arm Toolchain project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. +// Part of the Arm Toolchain 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 // #include "semihost.h" #include "platform.h" +#include #include +#include #include #include namespace { +// File cookies +inline constexpr size_t DEFAULT_FILE_COOKIE_COUNT = 4; +__llvm_libc_semihost_file_cookie + default_file_cookies[DEFAULT_FILE_COOKIE_COUNT]; +} // namespace -void stdio_open(struct __llvm_libc_stdio_cookie *cookie, size_t mode) { - const char std_stream_name[] = ":tt"; +extern "C" { +// Override this weak definition with a pool backed by an application-owned +// array to change the maximum number of simultaneously open files. +__attribute__((weak)) __llvm_libc_semihost_file_cookie_pool + __llvm_libc_semihost_file_cookie_storage = {default_file_cookies, + DEFAULT_FILE_COOKIE_COUNT}; +struct __llvm_libc_stdio_cookie __llvm_libc_stdin_cookie; +struct __llvm_libc_stdio_cookie __llvm_libc_stdout_cookie; +struct __llvm_libc_stdio_cookie __llvm_libc_stderr_cookie; +} + +namespace { +// File cookie helpers +bool is_std_stream_cookie(void *cookie) { + return cookie == &__llvm_libc_stdin_cookie || + cookie == &__llvm_libc_stdout_cookie || + cookie == &__llvm_libc_stderr_cookie; +} + +size_t handle_from_cookie(void *cookie) { + return static_cast<__llvm_libc_stdio_cookie *>(cookie)->handle; +} + +__llvm_libc_semihost_file_cookie *allocate_file_cookie() { + for (size_t i = 0; i < __llvm_libc_semihost_file_cookie_storage.size; ++i) { + auto *cookie = &__llvm_libc_semihost_file_cookie_storage.cookies[i]; + if (!cookie->in_use) { + cookie->in_use = true; + cookie->position = 0; + return cookie; + } + } + return nullptr; +} + +void release_file_cookie(void *cookie) { + static_cast<__llvm_libc_semihost_file_cookie *>(cookie)->in_use = false; +} + +off_t position_from_cookie(void *cookie) { + return static_cast<__llvm_libc_semihost_file_cookie *>(cookie)->position; +} + +void set_file_position(void *cookie, off_t position) { + static_cast<__llvm_libc_semihost_file_cookie *>(cookie)->position = position; +} + +void advance_file_position(void *cookie, ssize_t amount) { + if (amount > 0) + set_file_position(cookie, position_from_cookie(cookie) + + static_cast(amount)); +} +} // namespace + +namespace { +// Helper functions implemented here to avoid dependency on libc which is not +// available in LLVM libc hermetic testing. +static int _isspace(char ch) { + return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\v' || + ch == '\f'; +} + +__attribute__((no_builtin("strlen"))) size_t _strlen(const char *str) { + const char *end = str; + while (*end) + ++end; + return static_cast(end - str); +} + +// Semihosting helpers +int semihost_errno_negative() { + int error = semihosting_call(SYS_ERRNO, nullptr); + return error > 0 ? -error : -EIO; +} + +off_t semihost_file_length(size_t handle) { + size_t args[] = {handle}; + off_t length = semihosting_call(SYS_FLEN, args); + return length < 0 ? semihost_errno_negative() : length; +} + +ssize_t semihost_read(size_t handle, char *buf, size_t size) { + size_t args[] = { + handle, + reinterpret_cast(buf), + size, + }; + ssize_t not_read = semihosting_call(SYS_READ, args); + return not_read < 0 ? semihost_errno_negative() + : static_cast(size) - not_read; +} + +ssize_t semihost_read_console(char *buf, size_t size) { + for (size_t i = 0; i < size; ++i) { + long ch = semihosting_call(SYS_READC, nullptr); + buf[i] = static_cast(ch & 0xff); + if (buf[i] == '\r') + buf[i] = '\n'; + } + return static_cast(size); +} + +ssize_t semihost_write(size_t handle, const char *buf, size_t size) { + size_t args[] = { + handle, + reinterpret_cast(buf), + size, + }; + ssize_t not_written = semihosting_call(SYS_WRITE, args); + return not_written < 0 ? semihost_errno_negative() + : static_cast(size) - not_written; +} + +int semihost_seek(size_t handle, off_t position) { size_t args[] = { - reinterpret_cast(std_stream_name), + handle, + static_cast(position), + }; + return semihosting_call(SYS_SEEK, args) == 0 ? 0 : semihost_errno_negative(); +} + +long semihost_open(const char *path, size_t mode) { + size_t args[] = { + reinterpret_cast(path), mode, - sizeof(std_stream_name) - 1UL, + _strlen(path), }; - cookie->handle = semihosting_call(SYS_OPEN, args); + long handle = semihosting_call(SYS_OPEN, args); + return handle < 0 ? semihost_errno_negative() : handle; } + +int semihost_close(size_t handle) { + size_t args[] = {handle}; + return semihosting_call(SYS_CLOSE, args); +} + +void stdio_cookie_open(struct __llvm_libc_stdio_cookie *cookie, size_t mode) { + const char std_stream_name[] = ":tt"; + cookie->handle = static_cast(semihost_open(std_stream_name, mode)); +} + } // namespace extern "C" { @@ -55,41 +194,101 @@ void __llvm_libc_exit(int status) { semihosting_call_exit(status); } -struct __llvm_libc_stdio_cookie __llvm_libc_stdin_cookie; -struct __llvm_libc_stdio_cookie __llvm_libc_stdout_cookie; -struct __llvm_libc_stdio_cookie __llvm_libc_stderr_cookie; +ssize_t __llvm_libc_stdio_read(void *cookie, char *buf, size_t size) { + if (cookie == &__llvm_libc_stdin_cookie) + return semihost_read_console(buf, size); + if (is_std_stream_cookie(cookie)) + return -EBADF; -// Currently only supports reading from stdin. -// We use SYS_READC for reading from stdin as QEMUs SYS_READ does not block. -// For other files SYS_READ should be used as SYS_READC is intended for console -// input and may block indefinitely in QEMU. -// TODO: Extend to handle regular files when implemented in LLVM libc. + ssize_t result = semihost_read(handle_from_cookie(cookie), buf, size); + advance_file_position(cookie, result); + return result; +} -ssize_t __llvm_libc_stdio_read(struct __llvm_libc_stdio_cookie *cookie, - char *buf, size_t size) { - if (cookie != &__llvm_libc_stdin_cookie) - return -1; - - for (size_t i = 0; i < size; ++i) { - long ch = semihosting_call(SYS_READC, nullptr); - buf[i] = static_cast(ch & 0xff); - if (buf[i] == '\r') - buf[i] = '\n'; +ssize_t __llvm_libc_stdio_write(void *cookie, const char *buf, size_t size) { + ssize_t result = semihost_write(handle_from_cookie(cookie), buf, size); + if (!is_std_stream_cookie(cookie)) + advance_file_position(cookie, result); + return result; +} + +int __llvm_libc_stdio_open(const char *path, const char *mode, void **cookie) { + auto *file_cookie = allocate_file_cookie(); + if (!file_cookie) + return -EMFILE; + + size_t open_mode = OPENMODE_R; + if (mode[0] == 'w') + open_mode = OPENMODE_W; + else if (mode[0] == 'a') + open_mode = OPENMODE_A; + for (const char *option = mode + 1; *option != '\0'; ++option) { + if (*option == '+') + open_mode |= OPENMODE_PLUS; + else if (*option == 'b') + open_mode |= OPENMODE_B; + } + + long handle = semihost_open(path, open_mode); + if (handle < 0) { + release_file_cookie(file_cookie); + return static_cast(handle); } - return size; + file_cookie->stdio_cookie.handle = static_cast(handle); + if (mode[0] == 'a') { + off_t length = semihost_file_length(handle_from_cookie(file_cookie)); + if (length < 0) { + semihost_close(handle_from_cookie(file_cookie)); + release_file_cookie(file_cookie); + return static_cast(length); + } + set_file_position(file_cookie, length); + } + *cookie = file_cookie; + return 0; } -ssize_t __llvm_libc_stdio_write(struct __llvm_libc_stdio_cookie *cookie, - const char *buf, size_t size) { - size_t args[] = { - static_cast(cookie->handle), - reinterpret_cast(buf), - size, - }; - ssize_t retval = semihosting_call(SYS_WRITE, args); - if (retval >= 0) - retval = size - retval; - return retval; +off_t __llvm_libc_stdio_seek(void *cookie, off_t offset, int whence) { + if (is_std_stream_cookie(cookie)) + return -ESPIPE; + + off_t base; + if (whence == SEEK_SET) + base = 0; + else if (whence == SEEK_CUR) + base = position_from_cookie(cookie); + else if (whence == SEEK_END) { + base = semihost_file_length(handle_from_cookie(cookie)); + if (base < 0) + return base; + } else + return -EINVAL; + + off_t position = base + offset; + if (position < 0) + return -EINVAL; + int error = semihost_seek(handle_from_cookie(cookie), position); + if (error) + return error; + set_file_position(cookie, position); + return position; +} + +int __llvm_libc_stdio_set_buffer(void *, char *, size_t, int) { + // Semihost streams are unbuffered. + return 0; +} + +int __llvm_libc_stdio_flush(void *) { + // Semihost streams are unbuffered. + return 0; +} + +int __llvm_libc_stdio_close(void *cookie) { + int result = semihost_close(handle_from_cookie(cookie)); + if (!is_std_stream_cookie(cookie)) + release_file_cookie(cookie); + return result; } bool __llvm_libc_timespec_get_active(struct timespec *ts) { @@ -114,9 +313,9 @@ bool __llvm_libc_timespec_get_utc(struct timespec *ts) { // Entry point void _platform_init(void) { - stdio_open(&__llvm_libc_stdin_cookie, OPENMODE_R); - stdio_open(&__llvm_libc_stdout_cookie, OPENMODE_W); - stdio_open(&__llvm_libc_stderr_cookie, OPENMODE_W); + stdio_cookie_open(&__llvm_libc_stdin_cookie, OPENMODE_R); + stdio_cookie_open(&__llvm_libc_stdout_cookie, OPENMODE_W); + stdio_cookie_open(&__llvm_libc_stderr_cookie, OPENMODE_W); } // Debug output @@ -135,21 +334,6 @@ void _platform_debug_putc(int c) { // - Escape sequences: \ copies next char as-is unless inside ' quotes // or at the end of the string. -// Helper functions implemented here to avoid dependency on libc which is not -// available in LLVM libc hermetic testing. -static int _isspace(char ch) { - return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\v' || - ch == '\f'; -} - -__attribute__((no_builtin("strlen"))) static size_t _strlen(const char *str) { - const char *pend = str; - while (*pend) { - pend++; - } - return (size_t)(pend - str); -} - static inline void skip_spaces(const char *&p) { while (_isspace(*p)) ++p; diff --git a/arm-software/embedded/llvmlibc-support/semihost/semihost.h b/arm-software/embedded/llvmlibc-support/semihost/semihost.h index d21ee2ecdaa3..600b78fb0cca 100644 --- a/arm-software/embedded/llvmlibc-support/semihost/semihost.h +++ b/arm-software/embedded/llvmlibc-support/semihost/semihost.h @@ -13,6 +13,8 @@ #ifndef LLVMET_LLVMLIBC_SUPPORT_SEMIHOST_H #define LLVMET_LLVMLIBC_SUPPORT_SEMIHOST_H +#include +#include #include #include @@ -112,6 +114,22 @@ inline constexpr uint32_t OPENMODE_B = 1; inline constexpr uint32_t OPENMODE_PLUS = 2; } // namespace -struct __llvm_libc_stdio_cookie { int handle; }; +struct __llvm_libc_stdio_cookie { + size_t handle; +}; + +struct __llvm_libc_semihost_file_cookie { + __llvm_libc_stdio_cookie stdio_cookie; + bool in_use; + off_t position; +}; + +struct __llvm_libc_semihost_file_cookie_pool { + __llvm_libc_semihost_file_cookie *cookies; + size_t size; +}; + +extern "C" __llvm_libc_semihost_file_cookie_pool + __llvm_libc_semihost_file_cookie_storage; #endif // LLVMET_LLVMLIBC_SUPPORT_SEMIHOST_H diff --git a/libc/config/baremetal/aarch64/entrypoints.txt b/libc/config/baremetal/aarch64/entrypoints.txt index 6c9733799f01..255bbf577ffa 100644 --- a/libc/config/baremetal/aarch64/entrypoints.txt +++ b/libc/config/baremetal/aarch64/entrypoints.txt @@ -128,14 +128,22 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.stdio.feof libc.src.stdio.ferror libc.src.stdio.fflush + libc.src.stdio.fclose libc.src.stdio.fgetc libc.src.stdio.fgets libc.src.stdio.fprintf libc.src.stdio.fputc libc.src.stdio.fputs + libc.src.stdio.fopen libc.src.stdio.fread libc.src.stdio.fscanf libc.src.stdio.fwrite + libc.src.stdio.fseek + libc.src.stdio.fseeko + libc.src.stdio.ftell + libc.src.stdio.ftello + libc.src.stdio.setbuf + libc.src.stdio.setvbuf libc.src.stdio.getc libc.src.stdio.getchar libc.src.stdio.printf diff --git a/libc/config/baremetal/arm/entrypoints.txt b/libc/config/baremetal/arm/entrypoints.txt index a6b0dc50d030..81b1cb90687c 100644 --- a/libc/config/baremetal/arm/entrypoints.txt +++ b/libc/config/baremetal/arm/entrypoints.txt @@ -128,14 +128,22 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.stdio.feof libc.src.stdio.ferror libc.src.stdio.fflush + libc.src.stdio.fclose libc.src.stdio.fgetc libc.src.stdio.fgets libc.src.stdio.fprintf libc.src.stdio.fputc libc.src.stdio.fputs + libc.src.stdio.fopen libc.src.stdio.fread libc.src.stdio.fscanf libc.src.stdio.fwrite + libc.src.stdio.fseek + libc.src.stdio.fseeko + libc.src.stdio.ftell + libc.src.stdio.ftello + libc.src.stdio.setbuf + libc.src.stdio.setvbuf libc.src.stdio.getc libc.src.stdio.getchar libc.src.stdio.printf diff --git a/libc/src/__support/OSUtil/baremetal/io.h b/libc/src/__support/OSUtil/baremetal/io.h index 5e691f26060e..bb270b4b7b6e 100644 --- a/libc/src/__support/OSUtil/baremetal/io.h +++ b/libc/src/__support/OSUtil/baremetal/io.h @@ -9,6 +9,7 @@ #ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_BAREMETAL_IO_H #define LLVM_LIBC_SRC___SUPPORT_OSUTIL_BAREMETAL_IO_H +#include "include/llvm-libc-types/off_t.h" #include "include/llvm-libc-types/size_t.h" #include "include/llvm-libc-types/ssize_t.h" #include "src/__support/CPP/string_view.h" @@ -47,10 +48,36 @@ namespace LIBC_NAMESPACE_DECL { struct __llvm_libc_stdio_cookie; +// On success, store a non-null application-owned cookie in `cookie` and return +// 0. On failure, return a negative errno value. +extern "C" int __llvm_libc_stdio_open(const char *path, const char *mode, + void **cookie); + +// Return the number of bytes read, which can be less than `size` and is zero at +// end-of-file. On failure, return a negative errno value. extern "C" ssize_t __llvm_libc_stdio_read(void *cookie, char *buf, size_t size); + +// Return the number of bytes written, which can be less than `size`. On +// failure, return a negative errno value. extern "C" ssize_t __llvm_libc_stdio_write(void *cookie, const char *buf, size_t size); +// Return the resulting absolute file position on success. On failure, return a +// negative errno value. +extern "C" off_t __llvm_libc_stdio_seek(void *cookie, off_t offset, int whence); + +// Configure buffering for `cookie`. Return 0 on success or nonzero on failure, +// matching setvbuf. The application owns any supplied buffer. +extern "C" int __llvm_libc_stdio_set_buffer(void *cookie, char *buffer, + size_t size, int mode); + +// Flush buffered output for `cookie`, or all output streams if `cookie` is +// null. Return 0 on success or EOF on failure, matching fflush. +extern "C" int __llvm_libc_stdio_flush(void *cookie); + +// Return 0 on success or EOF on failure, matching fclose. +extern "C" int __llvm_libc_stdio_close(void *cookie); + void write_to_stderr(cpp::string_view msg); } // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt index 68ee57fae646..c4db9834e7ce 100644 --- a/libc/src/stdio/CMakeLists.txt +++ b/libc/src/stdio/CMakeLists.txt @@ -67,32 +67,6 @@ add_entrypoint_object( libc.src.__support.File.file ) -add_entrypoint_object( - setbuf - SRCS - setbuf.cpp - HDRS - setbuf.h - DEPENDS - libc.src.errno.errno - libc.hdr.types.off_t - libc.src.__support.File.file - libc.src.__support.File.platform_file -) - -add_entrypoint_object( - setvbuf - SRCS - setvbuf.cpp - HDRS - setvbuf.h - DEPENDS - libc.src.errno.errno - libc.hdr.types.FILE - libc.src.__support.File.file - libc.src.__support.File.platform_file -) - add_entrypoint_object( sscanf SRCS @@ -274,6 +248,8 @@ add_stdio_entrypoint_object(ftell) add_stdio_entrypoint_object(fseeko) add_stdio_entrypoint_object(fileno) add_stdio_entrypoint_object(ftello) +add_stdio_entrypoint_object(setbuf) +add_stdio_entrypoint_object(setvbuf) add_stdio_entrypoint_object(fflush) add_stdio_entrypoint_object(clearerr) add_stdio_entrypoint_object(clearerr_unlocked) diff --git a/libc/src/stdio/baremetal/CMakeLists.txt b/libc/src/stdio/baremetal/CMakeLists.txt index d26cdbb0f9d5..d0734d5531dd 100644 --- a/libc/src/stdio/baremetal/CMakeLists.txt +++ b/libc/src/stdio/baremetal/CMakeLists.txt @@ -10,6 +10,62 @@ add_object_library( libc.src.__support.OSUtil.osutil ) +add_entrypoint_object( + fopen + SRCS fopen.cpp + HDRS ../fopen.h + DEPENDS libc.hdr.errno_macros libc.src.__support.OSUtil.osutil libc.src.errno.errno +) + +add_entrypoint_object( + fclose + SRCS fclose.cpp + HDRS ../fclose.h + DEPENDS libc.src.__support.OSUtil.osutil +) + +add_entrypoint_object( + fseek + SRCS fseek.cpp + HDRS ../fseek.h + DEPENDS libc.hdr.errno_macros libc.src.__support.OSUtil.osutil libc.src.errno.errno +) + +add_entrypoint_object( + ftell + SRCS ftell.cpp + HDRS ../ftell.h + DEPENDS libc.hdr.errno_macros libc.hdr.stdio_macros libc.src.__support.OSUtil.osutil libc.src.errno.errno +) + +add_entrypoint_object( + fseeko + SRCS fseeko.cpp + HDRS ../fseeko.h + DEPENDS libc.hdr.errno_macros libc.src.__support.OSUtil.osutil libc.src.errno.errno +) + +add_entrypoint_object( + ftello + SRCS ftello.cpp + HDRS ../ftello.h + DEPENDS libc.hdr.errno_macros libc.hdr.stdio_macros libc.src.__support.OSUtil.osutil libc.src.errno.errno +) + +add_entrypoint_object( + setbuf + SRCS setbuf.cpp + HDRS ../setbuf.h + DEPENDS libc.hdr.stdio_macros libc.hdr.types.FILE libc.src.__support.OSUtil.osutil +) + +add_entrypoint_object( + setvbuf + SRCS setvbuf.cpp + HDRS ../setvbuf.h + DEPENDS libc.hdr.types.FILE libc.src.__support.OSUtil.osutil +) + add_header_library( vfprintf_internal HDRS @@ -98,6 +154,7 @@ add_entrypoint_object( ../fflush.h DEPENDS .file_internal + libc.src.__support.OSUtil.osutil ) add_entrypoint_object( diff --git a/libc/src/stdio/baremetal/fclose.cpp b/libc/src/stdio/baremetal/fclose.cpp new file mode 100644 index 000000000000..2714a11c681c --- /dev/null +++ b/libc/src/stdio/baremetal/fclose.cpp @@ -0,0 +1,12 @@ +#include "src/stdio/fclose.h" + +#include "src/__support/OSUtil/io.h" +#include "src/__support/common.h" + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(int, fclose, (::FILE * stream)) { + return __llvm_libc_stdio_close(stream); +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/stdio/baremetal/fflush.cpp b/libc/src/stdio/baremetal/fflush.cpp index 4a39c25ae11c..136e98cc7dae 100644 --- a/libc/src/stdio/baremetal/fflush.cpp +++ b/libc/src/stdio/baremetal/fflush.cpp @@ -8,15 +8,13 @@ #include "src/stdio/fflush.h" +#include "src/__support/OSUtil/io.h" #include "src/__support/common.h" namespace LIBC_NAMESPACE_DECL { -// Baremetal uses unbuffered I/O, so there is nothing to flush. LLVM_LIBC_FUNCTION(int, fflush, (::FILE * stream)) { - (void)stream; - // TODO: Shall we have an embedding API for fflush? - return 0; + return __llvm_libc_stdio_flush(stream); } } // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/stdio/baremetal/fopen.cpp b/libc/src/stdio/baremetal/fopen.cpp new file mode 100644 index 000000000000..1d7cfe98f2cb --- /dev/null +++ b/libc/src/stdio/baremetal/fopen.cpp @@ -0,0 +1,25 @@ +#include "src/stdio/fopen.h" + +#include "hdr/errno_macros.h" +#include "src/__support/OSUtil/io.h" +#include "src/__support/common.h" +#include "src/__support/libc_errno.h" + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(::FILE *, fopen, + (const char *__restrict path, const char *__restrict mode)) { + if (path == nullptr || mode == nullptr || mode[0] == '\0') { + libc_errno = EINVAL; + return nullptr; + } + void *cookie = nullptr; + int result = __llvm_libc_stdio_open(path, mode, &cookie); + if (result != 0 || cookie == nullptr) { + libc_errno = result != 0 ? -result : EINVAL; + return nullptr; + } + return reinterpret_cast<::FILE *>(cookie); +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/stdio/baremetal/fseek.cpp b/libc/src/stdio/baremetal/fseek.cpp new file mode 100644 index 000000000000..f7db74786fd3 --- /dev/null +++ b/libc/src/stdio/baremetal/fseek.cpp @@ -0,0 +1,23 @@ +#include "src/stdio/fseek.h" + +#include "hdr/errno_macros.h" +#include "src/__support/OSUtil/io.h" +#include "src/__support/common.h" +#include "src/__support/libc_errno.h" + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(int, fseek, (::FILE * stream, long offset, int whence)) { + if (stream == nullptr) { + libc_errno = EINVAL; + return -1; + } + off_t result = __llvm_libc_stdio_seek(stream, offset, whence); + if (result < 0) { + libc_errno = static_cast(-result); + return -1; + } + return 0; +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/stdio/baremetal/fseeko.cpp b/libc/src/stdio/baremetal/fseeko.cpp new file mode 100644 index 000000000000..c5b32b1158e1 --- /dev/null +++ b/libc/src/stdio/baremetal/fseeko.cpp @@ -0,0 +1,31 @@ +//===-- Implementation of fseeko for baremetal -----------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "src/stdio/fseeko.h" + +#include "hdr/errno_macros.h" +#include "src/__support/OSUtil/io.h" +#include "src/__support/common.h" +#include "src/__support/libc_errno.h" + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(int, fseeko, (::FILE * stream, off_t offset, int whence)) { + if (stream == nullptr) { + libc_errno = EINVAL; + return -1; + } + off_t result = __llvm_libc_stdio_seek(stream, offset, whence); + if (result < 0) { + libc_errno = static_cast(-result); + return -1; + } + return 0; +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/stdio/baremetal/ftell.cpp b/libc/src/stdio/baremetal/ftell.cpp new file mode 100644 index 000000000000..7ebb4d059622 --- /dev/null +++ b/libc/src/stdio/baremetal/ftell.cpp @@ -0,0 +1,24 @@ +#include "src/stdio/ftell.h" + +#include "hdr/errno_macros.h" +#include "hdr/stdio_macros.h" +#include "src/__support/OSUtil/io.h" +#include "src/__support/common.h" +#include "src/__support/libc_errno.h" + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(long, ftell, (::FILE * stream)) { + if (stream == nullptr) { + libc_errno = EINVAL; + return -1; + } + off_t result = __llvm_libc_stdio_seek(stream, 0, SEEK_CUR); + if (result < 0) { + libc_errno = static_cast(-result); + return -1; + } + return static_cast(result); +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/stdio/baremetal/ftello.cpp b/libc/src/stdio/baremetal/ftello.cpp new file mode 100644 index 000000000000..84ae74a72da7 --- /dev/null +++ b/libc/src/stdio/baremetal/ftello.cpp @@ -0,0 +1,32 @@ +//===-- Implementation of ftello for baremetal -----------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "src/stdio/ftello.h" + +#include "hdr/errno_macros.h" +#include "hdr/stdio_macros.h" +#include "src/__support/OSUtil/io.h" +#include "src/__support/common.h" +#include "src/__support/libc_errno.h" + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(off_t, ftello, (::FILE * stream)) { + if (stream == nullptr) { + libc_errno = EINVAL; + return static_cast(-1); + } + off_t result = __llvm_libc_stdio_seek(stream, 0, SEEK_CUR); + if (result < 0) { + libc_errno = static_cast(-result); + return static_cast(-1); + } + return result; +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/stdio/baremetal/setbuf.cpp b/libc/src/stdio/baremetal/setbuf.cpp new file mode 100644 index 000000000000..482f0ac43c75 --- /dev/null +++ b/libc/src/stdio/baremetal/setbuf.cpp @@ -0,0 +1,22 @@ +//===-- Bare-metal implementation of setbuf ------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "src/stdio/setbuf.h" +#include "hdr/stdio_macros.h" +#include "src/__support/OSUtil/io.h" +#include "src/__support/common.h" + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(void, setbuf, + (::FILE *__restrict stream, char *__restrict buffer)) { + __llvm_libc_stdio_set_buffer(stream, buffer, BUFSIZ, + buffer == nullptr ? _IONBF : _IOFBF); +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/stdio/baremetal/setvbuf.cpp b/libc/src/stdio/baremetal/setvbuf.cpp new file mode 100644 index 000000000000..4235230a10ef --- /dev/null +++ b/libc/src/stdio/baremetal/setvbuf.cpp @@ -0,0 +1,21 @@ +//===-- Bare-metal implementation of setvbuf -----------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "src/stdio/setvbuf.h" +#include "src/__support/OSUtil/io.h" +#include "src/__support/common.h" + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(int, setvbuf, + (::FILE *__restrict stream, char *__restrict buffer, + int mode, size_t size)) { + return __llvm_libc_stdio_set_buffer(stream, buffer, size, mode); +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/stdio/generic/CMakeLists.txt b/libc/src/stdio/generic/CMakeLists.txt index b6bb41b5053a..3df8ab501c4a 100644 --- a/libc/src/stdio/generic/CMakeLists.txt +++ b/libc/src/stdio/generic/CMakeLists.txt @@ -10,6 +10,32 @@ function(add_generic_entrypoint_object name) endif() endfunction() +add_generic_entrypoint_object( + setbuf + SRCS + ../setbuf.cpp + HDRS + ../setbuf.h + DEPENDS + libc.src.errno.errno + libc.hdr.types.off_t + libc.src.__support.File.file + libc.src.__support.File.platform_file +) + +add_generic_entrypoint_object( + setvbuf + SRCS + ../setvbuf.cpp + HDRS + ../setvbuf.h + DEPENDS + libc.src.errno.errno + libc.hdr.types.FILE + libc.src.__support.File.file + libc.src.__support.File.platform_file +) + add_generic_entrypoint_object( clearerr SRCS diff --git a/libcxx/include/fstream b/libcxx/include/fstream index c2296fe42729..fc06b2965b8c 100644 --- a/libcxx/include/fstream +++ b/libcxx/include/fstream @@ -192,7 +192,7 @@ typedef basic_fstream wfstream; # include <__config> // Downstream issue: #375 (Enable fstream independently of filesystem) -# if _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION || _LIBCPP_LIBC_NEWLIB +# if _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION || _LIBCPP_LIBC_NEWLIB || _LIBCPP_LIBC_LLVM_LIBC # include <__algorithm/max.h> # include <__assert> @@ -242,6 +242,8 @@ public: using native_handle_type = void*; // HANDLE # elif __has_include() using native_handle_type = int; // POSIX file descriptor +# elif _LIBCPP_LIBC_LLVM_LIBC + using native_handle_type = FILE*; # else # error "Provide a native file handle!" # endif @@ -278,6 +280,8 @@ public: return std::__filebuf_windows_native_handle(__file_); # elif __has_include() return fileno(__file_); +# elif _LIBCPP_LIBC_LLVM_LIBC + return __file_; # else # error "Provide a way to determine the file native handle!" # endif @@ -1640,7 +1644,7 @@ _LIBCPP_END_NAMESPACE_STD _LIBCPP_POP_MACROS -# endif // _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION +# endif // _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION || _LIBCPP_LIBC_NEWLIB || _LIBCPP_LIBC_LLVM_LIBC #endif // __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS) diff --git a/libcxx/src/ios.instantiations.cpp b/libcxx/src/ios.instantiations.cpp index a6e56395617a..98d6f53f7802 100644 --- a/libcxx/src/ios.instantiations.cpp +++ b/libcxx/src/ios.instantiations.cpp @@ -58,7 +58,7 @@ template class _LIBCPP_CLASS_TEMPLATE_INSTANTIATION_VIS basic_ostringstream; // Downstream issue: #375 (Enable fstream independently of filesystem) -#if _LIBCPP_HAS_FILESYSTEM || _LIBCPP_LIBC_NEWLIB +#if _LIBCPP_HAS_FILESYSTEM || _LIBCPP_LIBC_NEWLIB || _LIBCPP_LIBC_LLVM_LIBC template class _LIBCPP_CLASS_TEMPLATE_INSTANTIATION_VIS basic_ifstream; template class _LIBCPP_CLASS_TEMPLATE_INSTANTIATION_VIS basic_ofstream; template class _LIBCPP_CLASS_TEMPLATE_INSTANTIATION_VIS basic_filebuf; From ed1f705e5df22145a720eec0e6da2faee58b0c27 Mon Sep 17 00:00:00 2001 From: Volodymyr Turanskyy Date: Thu, 20 Aug 2026 16:15:21 +0100 Subject: [PATCH 2/2] Address review comments - Remove _cookie suffix from helper functions. - Remove unnecessary static declaration. - Move call to _strlen() out of semihost_open(). - Avoid negative offsets for advance_file_position(). --- .../llvmlibc-support/semihost/semihost.cpp | 68 ++++++++++--------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/arm-software/embedded/llvmlibc-support/semihost/semihost.cpp b/arm-software/embedded/llvmlibc-support/semihost/semihost.cpp index c699cf22173b..e51bfd08b7dc 100644 --- a/arm-software/embedded/llvmlibc-support/semihost/semihost.cpp +++ b/arm-software/embedded/llvmlibc-support/semihost/semihost.cpp @@ -35,17 +35,17 @@ struct __llvm_libc_stdio_cookie __llvm_libc_stderr_cookie; namespace { // File cookie helpers -bool is_std_stream_cookie(void *cookie) { +bool is_std_stream(void *cookie) { return cookie == &__llvm_libc_stdin_cookie || cookie == &__llvm_libc_stdout_cookie || cookie == &__llvm_libc_stderr_cookie; } -size_t handle_from_cookie(void *cookie) { +size_t get_handle(void *cookie) { return static_cast<__llvm_libc_stdio_cookie *>(cookie)->handle; } -__llvm_libc_semihost_file_cookie *allocate_file_cookie() { +__llvm_libc_semihost_file_cookie *allocate_file() { for (size_t i = 0; i < __llvm_libc_semihost_file_cookie_storage.size; ++i) { auto *cookie = &__llvm_libc_semihost_file_cookie_storage.cookies[i]; if (!cookie->in_use) { @@ -57,11 +57,11 @@ __llvm_libc_semihost_file_cookie *allocate_file_cookie() { return nullptr; } -void release_file_cookie(void *cookie) { +void release_file(void *cookie) { static_cast<__llvm_libc_semihost_file_cookie *>(cookie)->in_use = false; } -off_t position_from_cookie(void *cookie) { +off_t get_file_position(void *cookie) { return static_cast<__llvm_libc_semihost_file_cookie *>(cookie)->position; } @@ -69,17 +69,16 @@ void set_file_position(void *cookie, off_t position) { static_cast<__llvm_libc_semihost_file_cookie *>(cookie)->position = position; } -void advance_file_position(void *cookie, ssize_t amount) { - if (amount > 0) - set_file_position(cookie, position_from_cookie(cookie) + - static_cast(amount)); +void advance_file_position(void *cookie, size_t amount) { + set_file_position(cookie, + get_file_position(cookie) + static_cast(amount)); } } // namespace namespace { // Helper functions implemented here to avoid dependency on libc which is not // available in LLVM libc hermetic testing. -static int _isspace(char ch) { +int _isspace(char ch) { return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\v' || ch == '\f'; } @@ -143,11 +142,11 @@ int semihost_seek(size_t handle, off_t position) { return semihosting_call(SYS_SEEK, args) == 0 ? 0 : semihost_errno_negative(); } -long semihost_open(const char *path, size_t mode) { +long semihost_open(const char *path, size_t length, size_t mode) { size_t args[] = { reinterpret_cast(path), mode, - _strlen(path), + length, }; long handle = semihosting_call(SYS_OPEN, args); return handle < 0 ? semihost_errno_negative() : handle; @@ -160,7 +159,8 @@ int semihost_close(size_t handle) { void stdio_cookie_open(struct __llvm_libc_stdio_cookie *cookie, size_t mode) { const char std_stream_name[] = ":tt"; - cookie->handle = static_cast(semihost_open(std_stream_name, mode)); + cookie->handle = static_cast( + semihost_open(std_stream_name, sizeof(std_stream_name) - 1, mode)); } } // namespace @@ -197,23 +197,27 @@ void __llvm_libc_exit(int status) { ssize_t __llvm_libc_stdio_read(void *cookie, char *buf, size_t size) { if (cookie == &__llvm_libc_stdin_cookie) return semihost_read_console(buf, size); - if (is_std_stream_cookie(cookie)) + if (is_std_stream(cookie)) return -EBADF; - ssize_t result = semihost_read(handle_from_cookie(cookie), buf, size); - advance_file_position(cookie, result); + ssize_t result = semihost_read(get_handle(cookie), buf, size); + if (result < 0) + return result; + advance_file_position(cookie, static_cast(result)); return result; } ssize_t __llvm_libc_stdio_write(void *cookie, const char *buf, size_t size) { - ssize_t result = semihost_write(handle_from_cookie(cookie), buf, size); - if (!is_std_stream_cookie(cookie)) - advance_file_position(cookie, result); + ssize_t result = semihost_write(get_handle(cookie), buf, size); + if (result < 0) + return result; + if (!is_std_stream(cookie)) + advance_file_position(cookie, static_cast(result)); return result; } int __llvm_libc_stdio_open(const char *path, const char *mode, void **cookie) { - auto *file_cookie = allocate_file_cookie(); + auto *file_cookie = allocate_file(); if (!file_cookie) return -EMFILE; @@ -229,17 +233,17 @@ int __llvm_libc_stdio_open(const char *path, const char *mode, void **cookie) { open_mode |= OPENMODE_B; } - long handle = semihost_open(path, open_mode); + long handle = semihost_open(path, _strlen(path), open_mode); if (handle < 0) { - release_file_cookie(file_cookie); + release_file(file_cookie); return static_cast(handle); } file_cookie->stdio_cookie.handle = static_cast(handle); if (mode[0] == 'a') { - off_t length = semihost_file_length(handle_from_cookie(file_cookie)); + off_t length = semihost_file_length(get_handle(file_cookie)); if (length < 0) { - semihost_close(handle_from_cookie(file_cookie)); - release_file_cookie(file_cookie); + semihost_close(get_handle(file_cookie)); + release_file(file_cookie); return static_cast(length); } set_file_position(file_cookie, length); @@ -249,16 +253,16 @@ int __llvm_libc_stdio_open(const char *path, const char *mode, void **cookie) { } off_t __llvm_libc_stdio_seek(void *cookie, off_t offset, int whence) { - if (is_std_stream_cookie(cookie)) + if (is_std_stream(cookie)) return -ESPIPE; off_t base; if (whence == SEEK_SET) base = 0; else if (whence == SEEK_CUR) - base = position_from_cookie(cookie); + base = get_file_position(cookie); else if (whence == SEEK_END) { - base = semihost_file_length(handle_from_cookie(cookie)); + base = semihost_file_length(get_handle(cookie)); if (base < 0) return base; } else @@ -267,7 +271,7 @@ off_t __llvm_libc_stdio_seek(void *cookie, off_t offset, int whence) { off_t position = base + offset; if (position < 0) return -EINVAL; - int error = semihost_seek(handle_from_cookie(cookie), position); + int error = semihost_seek(get_handle(cookie), position); if (error) return error; set_file_position(cookie, position); @@ -285,9 +289,9 @@ int __llvm_libc_stdio_flush(void *) { } int __llvm_libc_stdio_close(void *cookie) { - int result = semihost_close(handle_from_cookie(cookie)); - if (!is_std_stream_cookie(cookie)) - release_file_cookie(cookie); + int result = semihost_close(get_handle(cookie)); + if (!is_std_stream(cookie)) + release_file(cookie); return result; }