[libc-commits] [libc] 907eb11 - [libc] Add getc, ungetc, fflush to enable libc++ iostream on baremetal (#175530)

via libc-commits libc-commits at lists.llvm.org
Thu Feb 12 02:29:40 PST 2026


Author: Volodymyr Turanskyy
Date: 2026-02-12T10:29:34Z
New Revision: 907eb11cc128630c52cd2191b925873b6ee56d6e

URL: https://github.com/llvm/llvm-project/commit/907eb11cc128630c52cd2191b925873b6ee56d6e
DIFF: https://github.com/llvm/llvm-project/commit/907eb11cc128630c52cd2191b925873b6ee56d6e.diff

LOG: [libc] Add getc, ungetc, fflush to enable libc++ iostream on baremetal (#175530)

After https://github.com/llvm/llvm-project/pull/168931 landed getc,
ungetc and fflush are still missing at link time while trying to make
libc++ std::cout work with LLVM libc on baremetal.

ungetc implementation is very minimal only to cover the current standard
streams implementation from the patch above.

Added: 
    libc/src/stdio/baremetal/fflush.cpp
    libc/src/stdio/baremetal/file_internal.cpp
    libc/src/stdio/baremetal/ungetc.cpp

Modified: 
    libc/config/baremetal/aarch64/entrypoints.txt
    libc/config/baremetal/arm/entrypoints.txt
    libc/src/stdio/baremetal/CMakeLists.txt
    libc/src/stdio/baremetal/file_internal.h
    libc/src/stdio/baremetal/getc.cpp
    libc/src/stdio/baremetal/vfscanf_internal.h

Removed: 
    


################################################################################
diff  --git a/libc/config/baremetal/aarch64/entrypoints.txt b/libc/config/baremetal/aarch64/entrypoints.txt
index 742d96761c415..e0282bcb4fa23 100644
--- a/libc/config/baremetal/aarch64/entrypoints.txt
+++ b/libc/config/baremetal/aarch64/entrypoints.txt
@@ -126,6 +126,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdio.asprintf
     libc.src.stdio.feof
     libc.src.stdio.ferror
+    libc.src.stdio.fflush
     libc.src.stdio.fgetc
     libc.src.stdio.fgets
     libc.src.stdio.fprintf
@@ -134,6 +135,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdio.fread
     libc.src.stdio.fscanf
     libc.src.stdio.fwrite
+    libc.src.stdio.getc
     libc.src.stdio.getchar
     libc.src.stdio.printf
     libc.src.stdio.putc
@@ -152,6 +154,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdio.vsnprintf
     libc.src.stdio.vsprintf
     libc.src.stdio.vsscanf
+    libc.src.stdio.ungetc
 
     # stdbit.h entrypoints
     libc.src.stdbit.stdc_bit_ceil_uc

diff  --git a/libc/config/baremetal/arm/entrypoints.txt b/libc/config/baremetal/arm/entrypoints.txt
index 95cb0dea8e49e..f7ef4ea09f5b6 100644
--- a/libc/config/baremetal/arm/entrypoints.txt
+++ b/libc/config/baremetal/arm/entrypoints.txt
@@ -126,6 +126,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdio.asprintf
     libc.src.stdio.feof
     libc.src.stdio.ferror
+    libc.src.stdio.fflush
     libc.src.stdio.fgetc
     libc.src.stdio.fgets
     libc.src.stdio.fprintf
@@ -134,6 +135,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdio.fread
     libc.src.stdio.fscanf
     libc.src.stdio.fwrite
+    libc.src.stdio.getc
     libc.src.stdio.getchar
     libc.src.stdio.printf
     libc.src.stdio.putc
@@ -152,6 +154,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdio.vsnprintf
     libc.src.stdio.vsprintf
     libc.src.stdio.vsscanf
+    libc.src.stdio.ungetc
 
     # stdbit.h entrypoints
     libc.src.stdbit.stdc_bit_ceil_uc

diff  --git a/libc/src/stdio/baremetal/CMakeLists.txt b/libc/src/stdio/baremetal/CMakeLists.txt
index a706accecf152..abe134959e09b 100644
--- a/libc/src/stdio/baremetal/CMakeLists.txt
+++ b/libc/src/stdio/baremetal/CMakeLists.txt
@@ -1,5 +1,7 @@
-add_header_library(
+add_object_library(
   file_internal
+  SRCS
+    file_internal.cpp
   HDRS
     file_internal.h
   DEPENDS
@@ -31,6 +33,7 @@ add_header_library(
   HDRS
     vfscanf_internal.h
   DEPENDS
+    .file_internal
     libc.hdr.types.FILE
     libc.hdr.stdio_macros
     libc.src.__support.arg_list
@@ -76,6 +79,27 @@ add_entrypoint_object(
     libc.src.errno.errno
 )
 
+add_entrypoint_object(
+  getc
+  SRCS
+    getc.cpp
+  HDRS
+    ../getc.h
+  DEPENDS
+    libc.hdr.types.FILE
+    libc.src.stdio.baremetal.fgetc
+)
+
+add_entrypoint_object(
+  fflush
+  SRCS
+    fflush.cpp
+  HDRS
+    ../fflush.h
+  DEPENDS
+    .file_internal
+)
+
 add_entrypoint_object(
   fgets
   SRCS
@@ -223,6 +247,18 @@ add_entrypoint_object(
     libc.src.errno.errno
 )
 
+add_entrypoint_object(
+  ungetc
+  SRCS
+    ungetc.cpp
+  HDRS
+    ../ungetc.h
+  DEPENDS
+    .file_internal
+    libc.hdr.stdio_macros
+    libc.hdr.types.FILE
+)
+
 add_entrypoint_object(
   puts
   SRCS

diff  --git a/libc/src/stdio/baremetal/fflush.cpp b/libc/src/stdio/baremetal/fflush.cpp
new file mode 100644
index 0000000000000..4a39c25ae11c9
--- /dev/null
+++ b/libc/src/stdio/baremetal/fflush.cpp
@@ -0,0 +1,22 @@
+//===-- Implementation of fflush 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/fflush.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;
+}
+
+} // namespace LIBC_NAMESPACE_DECL

diff  --git a/libc/src/stdio/baremetal/file_internal.cpp b/libc/src/stdio/baremetal/file_internal.cpp
new file mode 100644
index 0000000000000..c12ab1c90d55b
--- /dev/null
+++ b/libc/src/stdio/baremetal/file_internal.cpp
@@ -0,0 +1,52 @@
+//===--- Helpers for file I/O on 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/baremetal/file_internal.h"
+#include "src/__support/CPP/optional.h"
+
+#include "hdr/stdio_macros.h" // for EOF, FILE
+
+namespace LIBC_NAMESPACE_DECL {
+
+// Out of standard streams only stdin supports ungetc,
+// because stdin is readable - ungetc on stdout/stderr is undefined.
+// Only one value is required by the C standard to be stored by ungetc.
+// This minimal implementation only handles stdin and returns error on all
+// other streams.
+// TODO: Shall we have an embedding API for ungetc?
+
+static cpp::optional<unsigned char> ungetc_state_stdin;
+
+bool pop_ungetc_value(::FILE *stream, unsigned char &out) {
+  if (stream != stdin)
+    return false;
+
+  if (!ungetc_state_stdin)
+    return false;
+
+  out = *ungetc_state_stdin;
+  ungetc_state_stdin.reset();
+  return true;
+}
+
+int push_ungetc_value(::FILE *stream, int c) {
+  if (c == EOF || stream == nullptr)
+    return EOF;
+
+  if (stream != stdin)
+    return EOF;
+
+  if (ungetc_state_stdin)
+    return EOF;
+
+  ungetc_state_stdin =
+      cpp::optional<unsigned char>{static_cast<unsigned char>(c)};
+  return c;
+}
+
+} // namespace LIBC_NAMESPACE_DECL

diff  --git a/libc/src/stdio/baremetal/file_internal.h b/libc/src/stdio/baremetal/file_internal.h
index 292a1e22c954b..b2a84ce44e2df 100644
--- a/libc/src/stdio/baremetal/file_internal.h
+++ b/libc/src/stdio/baremetal/file_internal.h
@@ -32,11 +32,35 @@ struct FileIOResult {
   constexpr operator size_t() { return value; }
 };
 
+// ungetc handling.
+int push_ungetc_value(::FILE *stream, int c);
+bool pop_ungetc_value(::FILE *stream, unsigned char &out);
+
+LIBC_INLINE int ungetc_internal(int c, ::FILE *stream) {
+  return push_ungetc_value(stream, c);
+}
+
 LIBC_INLINE FileIOResult read_internal(char *buf, size_t size, ::FILE *stream) {
-  ssize_t ret = __llvm_libc_stdio_read(stream, buf, size);
+  if (size == 0)
+    return 0;
+
+  unsigned char ungetc_value = 0;
+  size_t ungetc_value_copied = 0;
+
+  if (pop_ungetc_value(stream, ungetc_value)) {
+    buf[0] = static_cast<char>(ungetc_value);
+    ungetc_value_copied = 1;
+
+    if (size == 1)
+      return 1;
+  }
+
+  ssize_t ret = __llvm_libc_stdio_read(stream, buf + ungetc_value_copied,
+                                       size - ungetc_value_copied);
   if (ret < 0)
-    return {0, static_cast<int>(-ret)};
-  return ret;
+    return {ungetc_value_copied, static_cast<int>(-ret)};
+
+  return ret + ungetc_value_copied;
 }
 
 LIBC_INLINE FileIOResult write_internal(const char *buf, size_t size,

diff  --git a/libc/src/stdio/baremetal/getc.cpp b/libc/src/stdio/baremetal/getc.cpp
index e69de29bb2d1d..b56d23bc4807a 100644
--- a/libc/src/stdio/baremetal/getc.cpp
+++ b/libc/src/stdio/baremetal/getc.cpp
@@ -0,0 +1,31 @@
+//===-- Implementation of getc 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/getc.h"
+
+#include "hdr/stdio_macros.h" // for EOF.
+#include "hdr/types/FILE.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/stdio/baremetal/file_internal.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, getc, (::FILE * stream)) {
+  unsigned char c;
+  auto result = read_internal(reinterpret_cast<char *>(&c), 1, stream);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (result.value != 1)
+    return EOF;
+  return c;
+}
+
+} // namespace LIBC_NAMESPACE_DECL

diff  --git a/libc/src/stdio/baremetal/ungetc.cpp b/libc/src/stdio/baremetal/ungetc.cpp
new file mode 100644
index 0000000000000..72a25dbf8fb9f
--- /dev/null
+++ b/libc/src/stdio/baremetal/ungetc.cpp
@@ -0,0 +1,20 @@
+//===-- Implementation of ungetc 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/ungetc.h"
+
+#include "src/__support/common.h"
+#include "src/stdio/baremetal/file_internal.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, ungetc, (int c, ::FILE *stream)) {
+  return ungetc_internal(c, stream);
+}
+
+} // namespace LIBC_NAMESPACE_DECL

diff  --git a/libc/src/stdio/baremetal/vfscanf_internal.h b/libc/src/stdio/baremetal/vfscanf_internal.h
index fc90dc3bc044f..41df455f36e7a 100644
--- a/libc/src/stdio/baremetal/vfscanf_internal.h
+++ b/libc/src/stdio/baremetal/vfscanf_internal.h
@@ -18,6 +18,7 @@
 #include "src/__support/common.h"
 #include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
+#include "src/stdio/baremetal/file_internal.h"
 #include "src/stdio/scanf_core/reader.h"
 #include "src/stdio/scanf_core/scanf_main.h"
 
@@ -33,12 +34,12 @@ class StreamReader : public scanf_core::Reader<StreamReader> {
 
   LIBC_INLINE char getc() {
     char c;
-    auto result = __llvm_libc_stdio_read(stream, &c, 1);
+    auto result = read_internal(&c, 1, stream);
     if (result != 1)
       return '\0';
     return c;
   }
-  LIBC_INLINE void ungetc(int) {}
+  LIBC_INLINE void ungetc(int c) { (void)ungetc_internal(c, stream); }
 };
 
 } // namespace internal


        


More information about the libc-commits mailing list