Skip to content

Conversation

@petrhosek
Copy link
Member

This change expands the stdio support on baremetal to support opaque FILE*. This builds on top of the existing baremetal embedding API; we treat the standard FILE* streams as pointers that point to the cookie symbols which are a part of the embedding API. This also allows users to define their own FILE* streams, but we don't (yet) support the API that return FILE* such as fopen or fopencookie.

This change expands the stdio support on baremetal to support opaque
FILE*. This builds on top of the existing baremetal embedding API; we
treat the standard FILE* streams as pointers that point to the cookie
symbols which are a part of the embedding API. This also allows users
to define their own FILE* streams, but we don't (yet) support the API
that return FILE* such as fopen or fopencookie.
@llvmbot
Copy link
Member

llvmbot commented Nov 20, 2025

@llvm/pr-subscribers-libc

Author: Petr Hosek (petrhosek)

Changes

This change expands the stdio support on baremetal to support opaque FILE*. This builds on top of the existing baremetal embedding API; we treat the standard FILE* streams as pointers that point to the cookie symbols which are a part of the embedding API. This also allows users to define their own FILE* streams, but we don't (yet) support the API that return FILE* such as fopen or fopencookie.


Patch is 33.69 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/168931.diff

23 Files Affected:

  • (modified) clang/cmake/caches/Fuchsia-stage2.cmake (+4-2)
  • (modified) libc/config/baremetal/aarch64/entrypoints.txt (+8)
  • (modified) libc/config/baremetal/arm/entrypoints.txt (+8)
  • (modified) libc/config/baremetal/riscv/entrypoints.txt (+8)
  • (modified) libc/src/__support/OSUtil/baremetal/io.cpp (+4-38)
  • (modified) libc/src/__support/OSUtil/baremetal/io.h (+39)
  • (modified) libc/src/stdio/baremetal/CMakeLists.txt (+133-18)
  • (added) libc/src/stdio/baremetal/feof.cpp (+24)
  • (added) libc/src/stdio/baremetal/ferror.cpp (+23)
  • (added) libc/src/stdio/baremetal/fgetc.cpp ()
  • (added) libc/src/stdio/baremetal/fgets.cpp ()
  • (added) libc/src/stdio/baremetal/fprintf.cpp (+34)
  • (added) libc/src/stdio/baremetal/fputc.cpp (+22)
  • (added) libc/src/stdio/baremetal/fputc_internal.h (+29)
  • (added) libc/src/stdio/baremetal/fputs.cpp (+23)
  • (added) libc/src/stdio/baremetal/fputs_internal.h (+29)
  • (added) libc/src/stdio/baremetal/fwrite_internal.h (+27)
  • (modified) libc/src/stdio/baremetal/printf.cpp (+4-42)
  • (modified) libc/src/stdio/baremetal/putchar.cpp (+5-8)
  • (modified) libc/src/stdio/baremetal/puts.cpp (+3-9)
  • (added) libc/src/stdio/baremetal/vfprintf.cpp (+30)
  • (added) libc/src/stdio/baremetal/vfprintf_internal.h (+69)
  • (modified) libc/src/stdio/baremetal/vprintf.cpp (+4-43)
diff --git a/clang/cmake/caches/Fuchsia-stage2.cmake b/clang/cmake/caches/Fuchsia-stage2.cmake
index be3d0cfa2e657..1e7b3e1b71373 100644
--- a/clang/cmake/caches/Fuchsia-stage2.cmake
+++ b/clang/cmake/caches/Fuchsia-stage2.cmake
@@ -341,7 +341,7 @@ foreach(target armv6m-none-eabi;armv7m-none-eabi;armv7em-none-eabi;armv8m.main-n
   foreach(lang C;CXX;ASM)
     # TODO: The preprocessor defines workaround various issues in libc and libc++ integration.
     # These should be addressed and removed over time.
-    set(RUNTIMES_${target}_CMAKE_${lang}_local_flags "--target=${target} -Wno-atomic-alignment \"-Dvfprintf(stream, format, vlist)=vprintf(format, vlist)\" \"-Dfprintf(stream, format, ...)=printf(format)\" \"-Dfputs(string, stream)=puts(string)\" -D_LIBCPP_PRINT=1")
+    set(RUNTIMES_${target}_CMAKE_${lang}_local_flags "--target=${target} -Wno-atomic-alignment")
     if(NOT ${target} STREQUAL "aarch64-none-elf")
       set(RUNTIMES_${target}_CMAKE_${lang}_local_flags "${RUNTIMES_${target}_CMAKE_${lang}_local_flags} -mthumb")
     endif()
@@ -372,6 +372,7 @@ foreach(target armv6m-none-eabi;armv7m-none-eabi;armv7em-none-eabi;armv8m.main-n
   set(RUNTIMES_${target}_LIBCXX_ENABLE_RTTI OFF CACHE BOOL "")
   set(RUNTIMES_${target}_LIBCXX_ENABLE_THREADS OFF CACHE BOOL "")
   set(RUNTIMES_${target}_LIBCXX_ENABLE_MONOTONIC_CLOCK OFF CACHE BOOL "")
+  set(RUNTIMES_${target}_LIBCXX_HAS_TERMINAL_AVAILABLE OFF CACHE BOOL "")
   set(RUNTIMES_${target}_LIBCXX_USE_COMPILER_RT ON CACHE BOOL "")
   set(RUNTIMES_${target}_LLVM_INCLUDE_TESTS OFF CACHE BOOL "")
   set(RUNTIMES_${target}_LLVM_ENABLE_ASSERTIONS OFF CACHE BOOL "")
@@ -406,7 +407,7 @@ foreach(target riscv32-unknown-elf)
   foreach(lang C;CXX;ASM)
     # TODO: The preprocessor defines workaround various issues in libc and libc++ integration.
     # These should be addressed and removed over time.
-    set(RUNTIMES_${target}_CMAKE_${lang}_FLAGS "--target=${target} -march=rv32imafc -mabi=ilp32f -Wno-atomic-alignment \"-Dvfprintf(stream, format, vlist)=vprintf(format, vlist)\" \"-Dfprintf(stream, format, ...)=printf(format)\" \"-Dfputs(string, stream)=puts(string)\" -D_LIBCPP_PRINT=1" CACHE STRING "")
+    set(RUNTIMES_${target}_CMAKE_${lang}_FLAGS "--target=${target} -march=rv32imafc -mabi=ilp32f -Wno-atomic-alignment" CACHE STRING "")
   endforeach()
   foreach(type SHARED;MODULE;EXE)
     set(RUNTIMES_${target}_CMAKE_${type}_LINKER_FLAGS "-fuse-ld=lld" CACHE STRING "")
@@ -427,6 +428,7 @@ foreach(target riscv32-unknown-elf)
   set(RUNTIMES_${target}_LIBCXX_ENABLE_RTTI OFF CACHE BOOL "")
   set(RUNTIMES_${target}_LIBCXX_ENABLE_THREADS OFF CACHE BOOL "")
   set(RUNTIMES_${target}_LIBCXX_ENABLE_MONOTONIC_CLOCK OFF CACHE BOOL "")
+  set(RUNTIMES_${target}_LIBCXX_HAS_TERMINAL_AVAILABLE OFF CACHE BOOL "")
   set(RUNTIMES_${target}_LIBCXX_USE_COMPILER_RT ON CACHE BOOL "")
   set(RUNTIMES_${target}_LLVM_INCLUDE_TESTS OFF CACHE BOOL "")
   set(RUNTIMES_${target}_LLVM_ENABLE_ASSERTIONS OFF CACHE BOOL "")
diff --git a/libc/config/baremetal/aarch64/entrypoints.txt b/libc/config/baremetal/aarch64/entrypoints.txt
index c69ab3d0bb37c..4c6b6dfc4d669 100644
--- a/libc/config/baremetal/aarch64/entrypoints.txt
+++ b/libc/config/baremetal/aarch64/entrypoints.txt
@@ -124,8 +124,15 @@ set(TARGET_LIBC_ENTRYPOINTS
 
     # stdio.h entrypoints
     libc.src.stdio.asprintf
+    libc.src.stdio.feof
+    libc.src.stdio.ferror
+    libc.src.stdio.fprintf
+    libc.src.stdio.fputc
+    libc.src.stdio.fputs
+    libc.src.stdio.fwrite
     libc.src.stdio.getchar
     libc.src.stdio.printf
+    libc.src.stdio.putc
     libc.src.stdio.putchar
     libc.src.stdio.puts
     libc.src.stdio.remove
@@ -134,6 +141,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdio.sprintf
     libc.src.stdio.sscanf
     libc.src.stdio.vasprintf
+    libc.src.stdio.vfprintf
     libc.src.stdio.vprintf
     libc.src.stdio.vscanf
     libc.src.stdio.vsnprintf
diff --git a/libc/config/baremetal/arm/entrypoints.txt b/libc/config/baremetal/arm/entrypoints.txt
index c566f8ad08c8e..0847a0905a6f7 100644
--- a/libc/config/baremetal/arm/entrypoints.txt
+++ b/libc/config/baremetal/arm/entrypoints.txt
@@ -124,8 +124,15 @@ set(TARGET_LIBC_ENTRYPOINTS
 
     # stdio.h entrypoints
     libc.src.stdio.asprintf
+    libc.src.stdio.feof
+    libc.src.stdio.ferror
+    libc.src.stdio.fprintf
+    libc.src.stdio.fputc
+    libc.src.stdio.fputs
+    libc.src.stdio.fwrite
     libc.src.stdio.getchar
     libc.src.stdio.printf
+    libc.src.stdio.putc
     libc.src.stdio.putchar
     libc.src.stdio.puts
     libc.src.stdio.remove
@@ -134,6 +141,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdio.sprintf
     libc.src.stdio.sscanf
     libc.src.stdio.vasprintf
+    libc.src.stdio.vfprintf
     libc.src.stdio.vprintf
     libc.src.stdio.vscanf
     libc.src.stdio.vsnprintf
diff --git a/libc/config/baremetal/riscv/entrypoints.txt b/libc/config/baremetal/riscv/entrypoints.txt
index a6aef96e91698..9a5c73b81e87f 100644
--- a/libc/config/baremetal/riscv/entrypoints.txt
+++ b/libc/config/baremetal/riscv/entrypoints.txt
@@ -124,8 +124,15 @@ set(TARGET_LIBC_ENTRYPOINTS
 
     # stdio.h entrypoints
     libc.src.stdio.asprintf
+    libc.src.stdio.feof
+    libc.src.stdio.ferror
+    libc.src.stdio.fprintf
+    libc.src.stdio.fputc
+    libc.src.stdio.fputs
+    libc.src.stdio.fwrite
     libc.src.stdio.getchar
     libc.src.stdio.printf
+    libc.src.stdio.putc
     libc.src.stdio.putchar
     libc.src.stdio.puts
     libc.src.stdio.remove
@@ -134,6 +141,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdio.sprintf
     libc.src.stdio.sscanf
     libc.src.stdio.vasprintf
+    libc.src.stdio.vfprintf
     libc.src.stdio.vprintf
     libc.src.stdio.vscanf
     libc.src.stdio.vsnprintf
diff --git a/libc/src/__support/OSUtil/baremetal/io.cpp b/libc/src/__support/OSUtil/baremetal/io.cpp
index 2a9ef6bfa6579..2978d701017a5 100644
--- a/libc/src/__support/OSUtil/baremetal/io.cpp
+++ b/libc/src/__support/OSUtil/baremetal/io.cpp
@@ -8,49 +8,15 @@
 
 #include "io.h"
 
+#include "hdr/types/FILE.h"
 #include "src/__support/CPP/string_view.h"
 #include "src/__support/macros/config.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
-// These are intended to be provided by the vendor.
-//
-// The signature of these types and functions intentionally match `fopencookie`
-// which allows the following:
-//
-// ```
-// struct __llvm_libc_stdio_cookie { ... };
-// ...
-// struct __llvm_libc_stdio_cookie __llvm_libc_stdin_cookie;
-// cookie_io_functions_t stdin_func = { .read = __llvm_libc_stdio_read };
-// FILE *stdin = fopencookie(&__llvm_libc_stdin_cookie, "r", stdin_func);
-// ...
-// struct __llvm_libc_stdio_cookie __llvm_libc_stdout_cookie;
-// cookie_io_functions_t stdout_func = { .write = __llvm_libc_stdio_write };
-// FILE *stdout = fopencookie(&__llvm_libc_stdout_cookie, "w", stdout_func);
-// ...
-// struct __llvm_libc_stdio_cookie __llvm_libc_stderr_cookie;
-// cookie_io_functions_t stderr_func = { .write = __llvm_libc_stdio_write };
-// FILE *stderr = fopencookie(&__llvm_libc_stderr_cookie, "w", stderr_func);
-// ```
-//
-// At the same time, implementation of functions like `printf` and `scanf` can
-// use `__llvm_libc_stdio_read` and `__llvm_libc_stdio_write` directly to avoid
-// the extra indirection.
-//
-// All three symbols `__llvm_libc_stdin_cookie`, `__llvm_libc_stdout_cookie`,
-// and `__llvm_libc_stderr_cookie` must be provided, even if they don't point
-// at anything.
-
-struct __llvm_libc_stdio_cookie;
-
-extern "C" struct __llvm_libc_stdio_cookie __llvm_libc_stdin_cookie;
-extern "C" struct __llvm_libc_stdio_cookie __llvm_libc_stdout_cookie;
-extern "C" struct __llvm_libc_stdio_cookie __llvm_libc_stderr_cookie;
-
-extern "C" ssize_t __llvm_libc_stdio_read(void *cookie, char *buf, size_t size);
-extern "C" ssize_t __llvm_libc_stdio_write(void *cookie, const char *buf,
-                                           size_t size);
+extern "C" FILE *stdin = reinterpret_cast<FILE *>(&__llvm_libc_stdin_cookie);
+extern "C" FILE *stdout = reinterpret_cast<FILE *>(&__llvm_libc_stdout_cookie);
+extern "C" FILE *stderr = reinterpret_cast<FILE *>(&__llvm_libc_stderr_cookie);
 
 ssize_t read_from_stdin(char *buf, size_t size) {
   return __llvm_libc_stdio_read(static_cast<void *>(&__llvm_libc_stdin_cookie),
diff --git a/libc/src/__support/OSUtil/baremetal/io.h b/libc/src/__support/OSUtil/baremetal/io.h
index aed34ec7e62e3..2605fd45776b9 100644
--- a/libc/src/__support/OSUtil/baremetal/io.h
+++ b/libc/src/__support/OSUtil/baremetal/io.h
@@ -16,6 +16,45 @@
 
 namespace LIBC_NAMESPACE_DECL {
 
+// These are intended to be provided by the vendor.
+//
+// The signature of these types and functions intentionally match `fopencookie`
+// which allows the following:
+//
+// ```
+// struct __llvm_libc_stdio_cookie { ... };
+// ...
+// struct __llvm_libc_stdio_cookie __llvm_libc_stdin_cookie;
+// cookie_io_functions_t stdin_func = { .read = __llvm_libc_stdio_read };
+// FILE *stdin = fopencookie(&__llvm_libc_stdin_cookie, "r", stdin_func);
+// ...
+// struct __llvm_libc_stdio_cookie __llvm_libc_stdout_cookie;
+// cookie_io_functions_t stdout_func = { .write = __llvm_libc_stdio_write };
+// FILE *stdout = fopencookie(&__llvm_libc_stdout_cookie, "w", stdout_func);
+// ...
+// struct __llvm_libc_stdio_cookie __llvm_libc_stderr_cookie;
+// cookie_io_functions_t stderr_func = { .write = __llvm_libc_stdio_write };
+// FILE *stderr = fopencookie(&__llvm_libc_stderr_cookie, "w", stderr_func);
+// ```
+//
+// At the same time, implementation of functions like `printf` and `scanf` can
+// use `__llvm_libc_stdio_read` and `__llvm_libc_stdio_write` directly to avoid
+// the extra indirection.
+//
+// All three symbols `__llvm_libc_stdin_cookie`, `__llvm_libc_stdout_cookie`,
+// and `__llvm_libc_stderr_cookie` must be provided, even if they don't point
+// at anything.
+
+struct __llvm_libc_stdio_cookie;
+
+extern "C" struct __llvm_libc_stdio_cookie __llvm_libc_stdin_cookie;
+extern "C" struct __llvm_libc_stdio_cookie __llvm_libc_stdout_cookie;
+extern "C" struct __llvm_libc_stdio_cookie __llvm_libc_stderr_cookie;
+
+extern "C" ssize_t __llvm_libc_stdio_read(void *cookie, char *buf, size_t size);
+extern "C" ssize_t __llvm_libc_stdio_write(void *cookie, const char *buf,
+                                           size_t size);
+
 ssize_t read_from_stdin(char *buf, size_t size);
 void write_to_stderr(cpp::string_view msg);
 void write_to_stdout(cpp::string_view msg);
diff --git a/libc/src/stdio/baremetal/CMakeLists.txt b/libc/src/stdio/baremetal/CMakeLists.txt
index bfeff0e2b5880..e920b4a1a30f4 100644
--- a/libc/src/stdio/baremetal/CMakeLists.txt
+++ b/libc/src/stdio/baremetal/CMakeLists.txt
@@ -1,3 +1,111 @@
+add_header_library(
+  fputc_internal
+  HDRS
+    fputc_internal.h
+  DEPENDS
+    libc.hdr.types.FILE
+    libc.src.__support.CPP.string_view
+    libc.src.__support.OSUtil.osutil
+)
+
+add_header_library(
+  fputs_internal
+  HDRS
+    fputs_internal.h
+  DEPENDS
+    libc.hdr.types.FILE
+    libc.src.__support.CPP.string_view
+    libc.src.__support.OSUtil.osutil
+)
+
+add_header_library(
+  fwrite_internal
+  HDRS
+    fwrite_internal.h
+  DEPENDS
+    libc.hdr.types.FILE
+    libc.src.__support.OSUtil.osutil
+)
+
+add_header_library(
+  vfprintf_internal
+  HDRS
+    vfprintf_internal.h
+  DEPENDS
+    libc.hdr.types.FILE
+    libc.hdr.stdio_macros
+    libc.src.__support.arg_list
+    libc.src.__support.CPP.limits
+    libc.src.__support.CPP.string_view
+    libc.src.__support.libc_errno
+    libc.src.__support.OSUtil.osutil
+    libc.src.stdio.printf_core.printf_main
+    libc.src.stdio.printf_core.writer
+    libc.src.stdio.printf_core.error_mapper
+    libc.src.stdio.printf_core.core_structs
+)
+
+add_entrypoint_object(
+  feof
+  SRCS
+    feof.cpp
+  HDRS
+    ../feof.h
+  DEPENDS
+    libc.hdr.types.FILE
+)
+
+add_entrypoint_object(
+  ferror
+  SRCS
+    ferror.cpp
+  HDRS
+    ../ferror.h
+  DEPENDS
+    libc.hdr.types.FILE
+)
+
+add_entrypoint_object(
+  fprintf
+  SRCS
+    fprintf.cpp
+  HDRS
+    ../fprintf.h
+  DEPENDS
+    .vfprintf_internal
+    libc.src.__support.arg_list
+)
+
+add_entrypoint_object(
+  fputc
+  SRCS
+    fputc.cpp
+  HDRS
+    ../fputc.h
+  DEPENDS
+    .fputc_internal
+)
+
+add_entrypoint_object(
+  fputs
+  SRCS
+    fputs.cpp
+  HDRS
+    ../fputc.h
+  DEPENDS
+    .fputs_internal
+)
+
+add_entrypoint_object(
+  fwrite
+  SRCS
+    fwrite.cpp
+  HDRS
+    ../fwrite.h
+  DEPENDS
+    .fwrite_internal
+)
+
 add_entrypoint_object(
   getchar
   SRCS
@@ -27,14 +135,8 @@ add_entrypoint_object(
   HDRS
     ../printf.h
   DEPENDS
-    libc.src.stdio.printf_core.printf_main
-    libc.src.stdio.printf_core.writer
-    libc.src.stdio.printf_core.error_mapper
-    libc.src.stdio.printf_core.core_structs
+    .vfprintf_internal
     libc.src.__support.arg_list
-    libc.src.__support.OSUtil.osutil
-    libc.src.__support.libc_errno
-    libc.src.__support.CPP.limits
 )
 
 add_entrypoint_object(
@@ -44,8 +146,17 @@ add_entrypoint_object(
   HDRS
     ../putchar.h
   DEPENDS
-    libc.src.__support.OSUtil.osutil
-    libc.src.__support.CPP.string_view
+    .fputc_internal
+)
+
+add_entrypoint_object(
+  putc
+  SRCS
+    putc.cpp
+  HDRS
+    ../putc.h
+  DEPENDS
+    .fputc_internal
 )
 
 add_entrypoint_object(
@@ -55,8 +166,7 @@ add_entrypoint_object(
   HDRS
     ../puts.h
   DEPENDS
-    libc.src.__support.OSUtil.osutil
-    libc.src.__support.CPP.string_view
+    .fputs_internal
 )
 
 add_header_library(
@@ -82,6 +192,17 @@ add_entrypoint_object(
     libc.src.__support.OSUtil.osutil
 )
 
+add_entrypoint_object(
+  vfprintf
+  SRCS
+    vfprintf.cpp
+  HDRS
+    ../vfprintf.h
+  DEPENDS
+    .vfprintf_internal
+    libc.src.__support.arg_list
+)
+
 add_entrypoint_object(
   vprintf
   SRCS
@@ -89,14 +210,8 @@ add_entrypoint_object(
   HDRS
     ../vprintf.h
   DEPENDS
-    libc.src.stdio.printf_core.printf_main
-    libc.src.stdio.printf_core.writer
-    libc.src.stdio.printf_core.error_mapper
-    libc.src.stdio.printf_core.core_structs
+    .vfprintf_internal
     libc.src.__support.arg_list
-    libc.src.__support.OSUtil.osutil
-    libc.src.__support.libc_errno
-    libc.src.__support.CPP.limits
 )
 
 add_entrypoint_object(
diff --git a/libc/src/stdio/baremetal/feof.cpp b/libc/src/stdio/baremetal/feof.cpp
new file mode 100644
index 0000000000000..3c241edeb47f6
--- /dev/null
+++ b/libc/src/stdio/baremetal/feof.cpp
@@ -0,0 +1,24 @@
+//===-- Implementation of feof 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/feof.h"
+
+#include "hdr/types/FILE.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, feof, (::FILE * stream)) {
+  (void)stream;
+  // TODO: Shall we have an embeddeding API for feof?
+  return 0;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
+
diff --git a/libc/src/stdio/baremetal/ferror.cpp b/libc/src/stdio/baremetal/ferror.cpp
new file mode 100644
index 0000000000000..5af5a1efdcd9a
--- /dev/null
+++ b/libc/src/stdio/baremetal/ferror.cpp
@@ -0,0 +1,23 @@
+//===-- Implementation of ferror 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/ferror.h"
+
+#include "hdr/types/FILE.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, ferror, (::FILE * stream)) {
+  (void)stream;
+  // TODO: Shall we have an embeddeding API for ferror?
+  return 0;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/baremetal/fgetc.cpp b/libc/src/stdio/baremetal/fgetc.cpp
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/libc/src/stdio/baremetal/fgets.cpp b/libc/src/stdio/baremetal/fgets.cpp
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/libc/src/stdio/baremetal/fprintf.cpp b/libc/src/stdio/baremetal/fprintf.cpp
new file mode 100644
index 0000000000000..fc96c56ec77ed
--- /dev/null
+++ b/libc/src/stdio/baremetal/fprintf.cpp
@@ -0,0 +1,34 @@
+//===-- Implementation of fprintf 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/fprintf.h"
+
+#include "hdr/types/FILE.h"
+#include "src/__support/arg_list.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/stdio/baremetal/vfprintf_internal.h"
+
+#include <stdarg.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, fprintf,
+                   (::FILE *__restrict stream, const char *__restrict format,
+                    ...)) {
+  va_list vlist;
+  va_start(vlist, format);
+  internal::ArgList args(vlist); // This holder class allows for easier copying
+                                 // and pointer semantics, as well as handling
+                                 // destruction automatically.
+  va_end(vlist);
+
+  return vfprintf_internal(stream, format, args);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/baremetal/fputc.cpp b/libc/src/stdio/baremetal/fputc.cpp
new file mode 100644
index 0000000000000..1b4086466ee7b
--- /dev/null
+++ b/libc/src/stdio/baremetal/fputc.cpp
@@ -0,0 +1,22 @@
+//===-- Implementation of fputc 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/fputc.h"
+
+#include "hdr/types/FILE.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/stdio/baremetal/fputc_internal.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, fputc, (int c, ::FILE *stream)) {
+  return fputc_internal(c, stream);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/baremetal/fputc_internal.h b/libc/src/stdio/baremetal/fputc_internal.h
new file mode 100644
index 0000000000000..e578d6584bcfe
--- /dev/null
+++ b/libc/src/stdio/baremetal/fputc_internal.h
@@ -0,0 +1,29 @@
+//===-- Baremetal implementation header of fputc ----------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_STDIO_BAREMETAL_FPUTC_INTERNAL_H
+#define LLVM_LIBC_SRC_STDIO_BAREMETAL_FPUTC_INTERNAL_H
+
+#include "hdr/types/FILE.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/OSUtil/io.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LIBC_INLINE int fputc_internal(int c, ::FILE *stream) {
+  char ch = static_cast<char>(c);
+  cpp::string_view str_view(&ch, 1);
+  __llvm_libc_stdio_write(stream, str_view.data(), str_view.size());
+  return 0;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDIO_BAREMETAL_FPUTC_INTERNAL_H
diff --git a/libc/src/stdio/baremetal/fputs.cpp b/libc/src/stdio/baremetal/fputs.cpp
new file mode 100644
index 0000000000000..daed53182686e
--- /dev/null
+++ b/libc/src/stdio/baremetal/fputs.cpp
@@ -0,0 +1,23 @@
+//===-- Implementation of fputs for baremetal -------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for li...
[truncated]

@smithp35
Copy link
Collaborator

Thanks very much for posting. We've got a company holiday tomorrow, but we'll take a look next week.

@github-actions
Copy link

github-actions bot commented Nov 20, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@petrhosek
Copy link
Member Author

I'm thinking of combining the fputc_internal, fputs_internal and fwrite_internal into a single function/header (e.g. write_internal) to reduce the complexity. I'm planning on making the same changes to fget* and *scanf* family of functions in a follow up change.

# TODO: The preprocessor defines workaround various issues in libc and libc++ integration.
# These should be addressed and removed over time.
set(RUNTIMES_${target}_CMAKE_${lang}_local_flags "--target=${target} -Wno-atomic-alignment \"-Dvfprintf(stream, format, vlist)=vprintf(format, vlist)\" \"-Dfprintf(stream, format, ...)=printf(format)\" \"-Dfputs(string, stream)=puts(string)\" -D_LIBCPP_PRINT=1")
set(RUNTIMES_${target}_CMAKE_${lang}_local_flags "--target=${target} -Wno-atomic-alignment")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may also want to update libc/cmake/cache files introduced recently. e.g. https://github.com/llvm/llvm-project/blob/main/libc/cmake/caches/armv6m-none-eabi.cmake

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those flags are only needed for building for libc++ which we don't do in those cache files, so we can remove those flags separately from this PR (I forgot to catch that in the review).

@github-actions
Copy link

github-actions bot commented Nov 20, 2025

🐧 Linux x64 Test Results

  • 111350 tests passed
  • 4431 tests skipped

Copy link
Contributor

@michaelrj-google michaelrj-google left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, we'll probably need to update this when we do the FILE* redesign but we were going to need to do that anyways.

LIBC_INLINE int fputs_internal(const char *str, ::FILE *stream) {
cpp::string_view str_view(str);
__llvm_libc_stdio_write(stream, str_view.data(), str_view.size());
__llvm_libc_stdio_write(stream, "\n", 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fputs, unlike puts, does not automatically add a newline.


LIBC_INLINE int fputs_internal(const char *str, ::FILE *stream) {
cpp::string_view str_view(str);
__llvm_libc_stdio_write(stream, str_view.data(), str_view.size());
Copy link
Contributor

@mysterymath mysterymath Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions return ssize_t, and the fopencookie docs provide interpretations for the return codes that should really be handled as specified by the standard for the C functions. Do we have a TODO or issue for this? It seems like there should be something to track that we're currently ignoring part of the embedding API surface.


LLVM_LIBC_FUNCTION(int, ferror, (::FILE * stream)) {
(void)stream;
// TODO: Shall we have an embeddeding API for ferror?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is tricky; if one were to define that the cookie functions are capable of reporting error/EOF conditions, then really ferror and feof should work like one would logically expect. That would mandate some state tracking associated with the standard streams.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Difficult to answer the embedding API question without a bit more context. I guess this is aimed the middle-tier described in https://discourse.llvm.org/t/rfc-implementation-of-stdio-on-baremetal/86944

Presumably the state could be tracked internally with some kind of a FILE* -> State structure, or it could be stored in the FILE struct (if needed) and manipulated by an embedding API to set it.

The former would minimise the complexity of the embedding API, at the expense of some flexibility for those that don't need it. It maybe that the size that the extra state takes up is sufficiently small that it isn't worth the complexity.


LIBC_INLINE size_t fwrite_internal(const void *buffer, size_t size,
size_t nmemb, ::FILE *stream) {
__llvm_libc_stdio_write(stream, reinterpret_cast<const char *>(buffer),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a nit for baremetal, but we should probably check for overflow on the multiplication. Either as a TODO, or just in this change.

Copy link
Collaborator

@smithp35 smithp35 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC this is an interim change, not necessarily a step towards the 3 tier model described in https://discourse.llvm.org/t/rfc-implementation-of-stdio-on-baremetal/86944

In that assumed context, this looks good to me too. Looks like it will help with building libc++.


LLVM_LIBC_FUNCTION(int, ferror, (::FILE * stream)) {
(void)stream;
// TODO: Shall we have an embeddeding API for ferror?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Difficult to answer the embedding API question without a bit more context. I guess this is aimed the middle-tier described in https://discourse.llvm.org/t/rfc-implementation-of-stdio-on-baremetal/86944

Presumably the state could be tracked internally with some kind of a FILE* -> State structure, or it could be stored in the FILE struct (if needed) and manipulated by an embedding API to set it.

The former would minimise the complexity of the embedding API, at the expense of some flexibility for those that don't need it. It maybe that the size that the extra state takes up is sufficiently small that it isn't worth the complexity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants