[libc-commits] [libc] [libc] Add getc, ungetc, fflush to enable libc++ iostream on baremetal (PR #175530)
Volodymyr Turanskyy via libc-commits
libc-commits at lists.llvm.org
Mon Jan 12 04:43:33 PST 2026
https://github.com/voltur01 created https://github.com/llvm/llvm-project/pull/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.
>From 8e3ed8338e48199a7de66f218804aec85fe08eb8 Mon Sep 17 00:00:00 2001
From: Volodymyr Turanskyy <volodymyr.turanskyy at arm.com>
Date: Fri, 9 Jan 2026 11:45:03 +0000
Subject: [PATCH] [libc] Add getc, ungetc, fflush to enable libc++ iostream
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.
ungetc implementation is very minimal only to cover the current standard streams implementation from the patch above.
---
libc/config/baremetal/aarch64/entrypoints.txt | 3 ++
libc/config/baremetal/arm/entrypoints.txt | 3 ++
libc/src/stdio/baremetal/CMakeLists.txt | 37 ++++++++++++-
libc/src/stdio/baremetal/fflush.cpp | 18 +++++++
libc/src/stdio/baremetal/file_internal.cpp | 54 +++++++++++++++++++
libc/src/stdio/baremetal/file_internal.h | 23 ++++++--
libc/src/stdio/baremetal/getc.cpp | 18 +++++++
libc/src/stdio/baremetal/ungetc.cpp | 19 +++++++
8 files changed, 171 insertions(+), 4 deletions(-)
create mode 100644 libc/src/stdio/baremetal/fflush.cpp
create mode 100644 libc/src/stdio/baremetal/file_internal.cpp
create mode 100644 libc/src/stdio/baremetal/ungetc.cpp
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..6e046cc1a0660 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
@@ -76,6 +78,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 +246,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..046fa1b7693c7
--- /dev/null
+++ b/libc/src/stdio/baremetal/fflush.cpp
@@ -0,0 +1,18 @@
+//===-- 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 */)) { 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..c1b50ad3d959e
--- /dev/null
+++ b/libc/src/stdio/baremetal/file_internal.cpp
@@ -0,0 +1,54 @@
+//===--- 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 "hdr/stdio_macros.h" // for EOF
+
+namespace LIBC_NAMESPACE_DECL {
+
+// Baremetal only exposes three fixed streams now and 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.
+extern "C" ::FILE *stdin;
+
+struct UngetcState {
+ bool has_value;
+ unsigned char value;
+};
+
+static UngetcState ungetc_state_stdin{false, 0};
+
+bool pop_ungetc_value(::FILE *stream, unsigned char &out) {
+ if (stream != stdin)
+ return false;
+
+ if (ungetc_state_stdin.has_value) {
+ out = ungetc_state_stdin.value;
+ ungetc_state_stdin.has_value = false;
+ return true;
+ }
+ return false;
+}
+
+int store_ungetc_value(::FILE *stream, int c) {
+ if (c == EOF || stream == nullptr)
+ return EOF;
+
+ if (stream != stdin)
+ return EOF;
+
+ if (ungetc_state_stdin.has_value)
+ return EOF;
+
+ ungetc_state_stdin.value = static_cast<unsigned char>(c);
+ ungetc_state_stdin.has_value = true;
+ return ungetc_state_stdin.value;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/baremetal/file_internal.h b/libc/src/stdio/baremetal/file_internal.h
index 292a1e22c954b..fccc0a8108ea3 100644
--- a/libc/src/stdio/baremetal/file_internal.h
+++ b/libc/src/stdio/baremetal/file_internal.h
@@ -32,11 +32,28 @@ struct FileIOResult {
constexpr operator size_t() { return value; }
};
+// ungetc handling.
+int store_ungetc_value(::FILE *stream, int c);
+bool pop_ungetc_value(::FILE *stream, unsigned char &out);
+
LIBC_INLINE FileIOResult read_internal(char *buf, size_t size, ::FILE *stream) {
- ssize_t ret = __llvm_libc_stdio_read(stream, buf, size);
+ size_t ungetc_value_copied = 0;
+ if (size > 0) {
+ unsigned char ungetc_value = 0;
+ if (pop_ungetc_value(stream, ungetc_value)) {
+ buf[0] = static_cast<char>(ungetc_value);
+ ungetc_value_copied = 1;
+ }
+ }
+
+ if (ungetc_value_copied == size)
+ return ungetc_value_copied;
+
+ 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..963e125402f1d 100644
--- a/libc/src/stdio/baremetal/getc.cpp
+++ b/libc/src/stdio/baremetal/getc.cpp
@@ -0,0 +1,18 @@
+//===-- 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 "src/stdio/fgetc.h"
+
+#include "src/__support/common.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, getc, (::FILE * stream)) { return fgetc(stream); }
+
+} // 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..7a356feeae660
--- /dev/null
+++ b/libc/src/stdio/baremetal/ungetc.cpp
@@ -0,0 +1,19 @@
+//===-- 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/stdio/baremetal/file_internal.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, ungetc, (int c, ::FILE *stream)) {
+ return store_ungetc_value(stream, c);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
More information about the libc-commits
mailing list