Skip to content

Conversation

@saturn691
Copy link
Contributor

@saturn691 saturn691 commented Jun 17, 2025

These functions are required for some downstream testing, specifically writing to stdout/stderr. This does not introduce any new support for writing to a stream other than stdout/stderr, and will cause undefined behaviour. Part of #146099.

  • Also groups the *_write_hook into a new header file which can be extended to easily support other [v][f]printf functions
  • Minor corrections to ensure consistency in the title of the source files

Also groups the *_write_hook into a new header file which can be
extended to easily support other [v][f]printf functions
@llvmbot llvmbot added the libc label Jun 17, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 17, 2025

@llvm/pr-subscribers-libc

Author: William Huynh (saturn691)

Changes

Also groups the *_write_hook into a new header file which can be extended to easily support other [v][f]printf functions


Full diff: https://github.com/llvm/llvm-project/pull/144567.diff

11 Files Affected:

  • (modified) libc/config/baremetal/aarch64/entrypoints.txt (+3)
  • (modified) libc/config/baremetal/arm/entrypoints.txt (+3)
  • (modified) libc/config/baremetal/riscv/entrypoints.txt (+3)
  • (modified) libc/src/stdio/baremetal/CMakeLists.txt (+49)
  • (added) libc/src/stdio/baremetal/fprintf.cpp (+46)
  • (added) libc/src/stdio/baremetal/fputc.cpp (+25)
  • (modified) libc/src/stdio/baremetal/printf.cpp (+2-12)
  • (added) libc/src/stdio/baremetal/putc.cpp (+25)
  • (modified) libc/src/stdio/baremetal/putchar.cpp (+2-1)
  • (modified) libc/src/stdio/baremetal/vprintf.cpp (+2-9)
  • (added) libc/src/stdio/baremetal/write_utils.h (+48)
diff --git a/libc/config/baremetal/aarch64/entrypoints.txt b/libc/config/baremetal/aarch64/entrypoints.txt
index a8e653fdd5159..08b143d045463 100644
--- a/libc/config/baremetal/aarch64/entrypoints.txt
+++ b/libc/config/baremetal/aarch64/entrypoints.txt
@@ -124,8 +124,11 @@ set(TARGET_LIBC_ENTRYPOINTS
 
     # stdio.h entrypoints
     libc.src.stdio.asprintf
+    libc.src.stdio.fprintf
+    libc.src.stdio.fputc
     libc.src.stdio.getchar
     libc.src.stdio.printf
+    libc.src.stdio.putc
     libc.src.stdio.putchar
     libc.src.stdio.puts
     libc.src.stdio.remove
diff --git a/libc/config/baremetal/arm/entrypoints.txt b/libc/config/baremetal/arm/entrypoints.txt
index acafef17fa5d1..276f00a04b16a 100644
--- a/libc/config/baremetal/arm/entrypoints.txt
+++ b/libc/config/baremetal/arm/entrypoints.txt
@@ -124,8 +124,11 @@ set(TARGET_LIBC_ENTRYPOINTS
 
     # stdio.h entrypoints
     libc.src.stdio.asprintf
+    libc.src.stdio.fprintf
+    libc.src.stdio.fputc
     libc.src.stdio.getchar
     libc.src.stdio.printf
+    libc.src.stdio.putc
     libc.src.stdio.putchar
     libc.src.stdio.puts
     libc.src.stdio.remove
diff --git a/libc/config/baremetal/riscv/entrypoints.txt b/libc/config/baremetal/riscv/entrypoints.txt
index 023826f12d723..dc173f69ce554 100644
--- a/libc/config/baremetal/riscv/entrypoints.txt
+++ b/libc/config/baremetal/riscv/entrypoints.txt
@@ -124,8 +124,11 @@ set(TARGET_LIBC_ENTRYPOINTS
 
     # stdio.h entrypoints
     libc.src.stdio.asprintf
+    libc.src.stdio.fprintf
+    libc.src.stdio.fputc
     libc.src.stdio.getchar
     libc.src.stdio.printf
+    libc.src.stdio.putc
     libc.src.stdio.putchar
     libc.src.stdio.puts
     libc.src.stdio.remove
diff --git a/libc/src/stdio/baremetal/CMakeLists.txt b/libc/src/stdio/baremetal/CMakeLists.txt
index e879230a9d02c..ba1b0a690844a 100644
--- a/libc/src/stdio/baremetal/CMakeLists.txt
+++ b/libc/src/stdio/baremetal/CMakeLists.txt
@@ -1,3 +1,27 @@
+add_entrypoint_object(
+  fprintf
+  SRCS
+    fprintf.cpp
+  HDRS
+    ../fprintf.h
+    write_utils.h
+  DEPENDS
+    libc.src.__support.OSUtil.osutil
+    libc.src.__support.CPP.string_view
+    libc.src.stdio.printf_core.printf_main
+)
+
+add_entrypoint_object(
+  fputc
+  SRCS
+    fputc.cpp
+  HDRS
+    ../fputc.h
+    write_utils.h
+  DEPENDS
+    libc.src.__support.CPP.string_view
+)
+
 add_entrypoint_object(
   getchar
   SRCS
@@ -20,12 +44,24 @@ add_entrypoint_object(
     libc.include.stdio
 )
 
+add_entrypoint_object(
+  putc
+  SRCS
+    putc.cpp
+  HDRS
+    ../putc.h
+    write_utils.h
+  DEPENDS
+    libc.src.__support.CPP.string_view
+)
+
 add_entrypoint_object(
   printf
   SRCS
     printf.cpp
   HDRS
     ../printf.h
+    write_utils.h
   DEPENDS
     libc.src.stdio.printf_core.printf_main
     libc.src.stdio.printf_core.writer
@@ -102,3 +138,16 @@ add_entrypoint_object(
     libc.src.__support.arg_list
     libc.src.__support.OSUtil.osutil
 )
+
+add_header_library(
+  baremetal_write_utils
+  HDRS
+    write_utils.h
+  DEPENDS
+    libc.hdr.types.FILE
+    libc.hdr.stdio_macros
+    libc.src.__support.OSUtil.osutil
+    libc.src.__support.CPP.string_view
+    .stdout
+    .stderr
+)
\ No newline at end of file
diff --git a/libc/src/stdio/baremetal/fprintf.cpp b/libc/src/stdio/baremetal/fprintf.cpp
new file mode 100644
index 0000000000000..530c811af5652
--- /dev/null
+++ b/libc/src/stdio/baremetal/fprintf.cpp
@@ -0,0 +1,46 @@
+//===-- 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/macros/config.h"
+#include "src/stdio/baremetal/write_utils.h"
+#include "src/stdio/printf_core/printf_main.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);
+  static constexpr size_t BUFF_SIZE = 1024;
+  char buffer[BUFF_SIZE];
+
+  printf_core::WriteBuffer<printf_core::WriteMode::FLUSH_TO_STREAM> wb(
+      buffer, BUFF_SIZE, get_write_hook(stream), nullptr);
+  printf_core::Writer<printf_core::WriteMode::FLUSH_TO_STREAM> writer(wb);
+
+  int retval = printf_core::printf_main(&writer, format, args);
+
+  int flushval = wb.overflow_write("");
+  if (flushval != printf_core::WRITE_OK)
+    retval = flushval;
+
+  return retval;
+}
+
+} // 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..e33bbc663b970
--- /dev/null
+++ b/libc/src/stdio/baremetal/fputc.cpp
@@ -0,0 +1,25 @@
+//===-- Baremetal Implementation of fputc ---------------------------------===//
+//
+// 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 "src/__support/CPP/string_view.h"
+#include "src/__support/macros/config.h"
+#include "src/stdio/baremetal/write_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, fputc, (int c, ::FILE *stream)) {
+  char uc = static_cast<char>(c);
+
+  write(stream, cpp::string_view(&uc, 1));
+
+  return 0;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/baremetal/printf.cpp b/libc/src/stdio/baremetal/printf.cpp
index 7253c6549a4e4..5a320cc232456 100644
--- a/libc/src/stdio/baremetal/printf.cpp
+++ b/libc/src/stdio/baremetal/printf.cpp
@@ -7,27 +7,17 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdio/printf.h"
-#include "src/__support/OSUtil/io.h"
+
 #include "src/__support/arg_list.h"
 #include "src/__support/macros/config.h"
-#include "src/stdio/printf_core/core_structs.h"
+#include "src/stdio/baremetal/write_utils.h"
 #include "src/stdio/printf_core/printf_main.h"
-#include "src/stdio/printf_core/writer.h"
 
 #include <stdarg.h>
 #include <stddef.h>
 
 namespace LIBC_NAMESPACE_DECL {
 
-namespace {
-
-LIBC_INLINE int stdout_write_hook(cpp::string_view new_str, void *) {
-  write_to_stdout(new_str);
-  return printf_core::WRITE_OK;
-}
-
-} // namespace
-
 LLVM_LIBC_FUNCTION(int, printf, (const char *__restrict format, ...)) {
   va_list vlist;
   va_start(vlist, format);
diff --git a/libc/src/stdio/baremetal/putc.cpp b/libc/src/stdio/baremetal/putc.cpp
new file mode 100644
index 0000000000000..8150ae21eda23
--- /dev/null
+++ b/libc/src/stdio/baremetal/putc.cpp
@@ -0,0 +1,25 @@
+//===-- Baremetal Implementation of putc ----------------------------------===//
+//
+// 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/putc.h"
+
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/macros/config.h"
+#include "src/stdio/baremetal/write_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, putc, (int c, ::FILE *stream)) {
+  char uc = static_cast<char>(c);
+
+  write(stream, cpp::string_view(&uc, 1));
+
+  return 0;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/baremetal/putchar.cpp b/libc/src/stdio/baremetal/putchar.cpp
index ac21e6e783b01..fcaae40d09397 100644
--- a/libc/src/stdio/baremetal/putchar.cpp
+++ b/libc/src/stdio/baremetal/putchar.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdio/putchar.h"
+
 #include "src/__support/CPP/string_view.h"
 #include "src/__support/OSUtil/io.h"
 #include "src/__support/macros/config.h"
@@ -14,7 +15,7 @@
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(int, putchar, (int c)) {
-  char uc = static_cast<char>(c);
+  char uc = static_cast<unsigned char>(c);
 
   write_to_stdout(cpp::string_view(&uc, 1));
 
diff --git a/libc/src/stdio/baremetal/vprintf.cpp b/libc/src/stdio/baremetal/vprintf.cpp
index ab02533f14911..38985a9b85209 100644
--- a/libc/src/stdio/baremetal/vprintf.cpp
+++ b/libc/src/stdio/baremetal/vprintf.cpp
@@ -7,9 +7,11 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdio/vprintf.h"
+
 #include "src/__support/OSUtil/io.h"
 #include "src/__support/arg_list.h"
 #include "src/__support/macros/config.h"
+#include "src/stdio/baremetal/write_utils.h"
 #include "src/stdio/printf_core/core_structs.h"
 #include "src/stdio/printf_core/printf_main.h"
 #include "src/stdio/printf_core/writer.h"
@@ -19,15 +21,6 @@
 
 namespace LIBC_NAMESPACE_DECL {
 
-namespace {
-
-LIBC_INLINE int stdout_write_hook(cpp::string_view new_str, void *) {
-  write_to_stdout(new_str);
-  return printf_core::WRITE_OK;
-}
-
-} // namespace
-
 LLVM_LIBC_FUNCTION(int, vprintf,
                    (const char *__restrict format, va_list vlist)) {
   internal::ArgList args(vlist); // This holder class allows for easier copying
diff --git a/libc/src/stdio/baremetal/write_utils.h b/libc/src/stdio/baremetal/write_utils.h
new file mode 100644
index 0000000000000..ce493189c472a
--- /dev/null
+++ b/libc/src/stdio/baremetal/write_utils.h
@@ -0,0 +1,48 @@
+//===-- Baremetal helper functions for writing to stdout/stderr -*- 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 "hdr/stdio_macros.h" // For stdout/err
+#include "hdr/types/FILE.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/OSUtil/io.h"
+#include "src/__support/macros/config.h"
+#include "src/stdio/printf_core/core_structs.h" // For printf_core::WRITE_OK
+
+#include <stddef.h>
+
+namespace LIBC_NAMESPACE_DECL {
+namespace {
+
+LIBC_INLINE int stdout_write_hook(cpp::string_view new_str, void *) {
+  write_to_stdout(new_str);
+  return printf_core::WRITE_OK;
+}
+
+LIBC_INLINE int stderr_write_hook(cpp::string_view new_str, void *) {
+  write_to_stderr(new_str);
+  return printf_core::WRITE_OK;
+}
+
+LIBC_INLINE void write(::FILE *f, cpp::string_view new_str) {
+  if (f == stdout) {
+    write_to_stdout(new_str);
+  } else {
+    write_to_stderr(new_str);
+  }
+}
+
+LIBC_INLINE decltype(&stdout_write_hook) get_write_hook(::FILE *f) {
+  if (f == stdout) {
+    return &stdout_write_hook;
+  }
+
+  return &stderr_write_hook;
+}
+
+} // namespace
+} // namespace LIBC_NAMESPACE_DECL

@petrhosek
Copy link
Member

What's the motivation for this change? Do you have a concrete use cases or are you just filling in blanks? To give you some context, we omitted the stdio.h functions that take FILE* in the baremetal configuration deliberately because that brings additional complexity and our position is that baremetal code should ideally only use printf and scanf (and putchar) which easily map onto serial I/O but the FILE* does not is going to be platform dependent (I'm ignoring semihosting which is a separate configuration). That position won't be practical or sustainable in the long run since there are baremetal project that use existing libraries which weren't design for baremetal use and we will need a way to support FILE* in baremetal configurations. We already discussed different approaches in the past Embedded Toolchains, LLVM libc and libc++ meetings but we haven't yet written down the outcome of those discussions and the actual design. That's something we need to do before going too far with the implementation because it will also have implications on LLVM libc users and for other libraries that sit on top like libc++.

Comment on lines 39 to 42
if (f == stdout)
return &stdout_write_hook;

return &stderr_write_hook;
Copy link
Member

Choose a reason for hiding this comment

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

The same here, if the stream is neither stdout nor stderr, we shouldn't just implicitly fallback to stderr, we should return an error.

Copy link
Contributor Author

@saturn691 saturn691 Jul 2, 2025

Choose a reason for hiding this comment

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

The fprintf function returns the number of characters transmitted, or a negative value if an output
or encoding error occurred or if the implementation does not support a specified width length
modifier.

Reference: C23, 7.23.6.1 The printf function, paragraph 15

I've chosen to return -1 in the fprintf function (which calls this function), but nullptr here.

)

add_header_library(
baremetal_write_utils
Copy link
Member

Choose a reason for hiding this comment

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

This is not being used anywhere (see the comments above).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

By using .baremetal_*, this should be resolved

@saturn691
Copy link
Contributor Author

Hi Petr, I'm going to take this conversation to Discourse. It would be great if we can some consensus/input before I continue working on this PR.

https://discourse.llvm.org/t/rfc-implementation-of-stdio-on-baremetal/86944

@saturn691
Copy link
Contributor Author

Hi guys, re-requesting review.

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 but Petr should probably approve since this is likely to affect his team's usage.

Copy link

@SanjitRaman SanjitRaman left a comment

Choose a reason for hiding this comment

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

lgtm

@saturn691
Copy link
Contributor Author

saturn691 commented Aug 4, 2025

I am considering closing this PR, please see the attached RFC for more details.

https://discourse.llvm.org/t/rfc-implementation-of-stdio-on-baremetal/86944/4

@saturn691 saturn691 closed this Sep 1, 2025
@saturn691 saturn691 deleted the add-more-stdio branch September 1, 2025 09:31
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.

5 participants