[libc-commits] [libc] [libc] Allow using sscanf() and vsscanf() on baremetal targets. (PR #130527)

Jesse D via libc-commits libc-commits at lists.llvm.org
Sun Mar 9 16:59:28 PDT 2025


https://github.com/jdeguire updated https://github.com/llvm/llvm-project/pull/130527

>From e9b681a59e47dc5ee8a70ef1415dbac5173b16f5 Mon Sep 17 00:00:00 2001
From: Jesse DeGuire <jesse.a.deguire at gmail.com>
Date: Sun, 9 Mar 2025 18:39:27 -0500
Subject: [PATCH 1/2] [libc] Allow using sscanf() and vsscanf() on baremetal
 targets.

In addition to the normal "cookie" definitions a baremetal user needs to provide to use printf(), a user also needs to add "FILE *stdin;" somehere in their source. I have not updated the default baremetal entrypoints, so a user will currently need to supply their own to add sscanf() and vsscanf(). This might also work for regular sscanf(), but I have not yet tried it.

This updates LLVMCCompileOptionRules.cmake to add a macro the libc build can use to determine if it is doing a baremetal build. This also updates the baremetal getchar() function because it looked like it would return an uninitialized value if __llvm_libc_stdio_read() returned 0. The baremetal build does not provide ungetc(), so the baremetal Reader class tracks the would-be ungot character itself. From what I could see in the code, tracking only one character is sufficient.
---
 .../modules/LLVMLibCCompileOptionRules.cmake  |  3 ++
 libc/src/stdio/baremetal/getchar.cpp          |  2 +-
 libc/src/stdio/scanf_core/CMakeLists.txt      | 18 ++++----
 libc/src/stdio/scanf_core/reader.h            | 42 ++++++++++++++++++-
 4 files changed, 56 insertions(+), 9 deletions(-)

diff --git a/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake b/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
index 0facb0b9be0c1..270a74a6949c5 100644
--- a/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
+++ b/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
@@ -119,6 +119,9 @@ function(_get_common_compile_options output_var flags)
     list(APPEND compile_options "-fpie")
 
     if(LLVM_LIBC_FULL_BUILD)
+      if(LIBC_TARGET_OS_IS_BAREMETAL)
+        list(APPEND compile_options "-DLIBC_TARGET_OS_IS_BAREMETAL")
+      endif()
       # Only add -ffreestanding flag in non-GPU full build mode.
       if(NOT LIBC_TARGET_OS_IS_GPU)
         list(APPEND compile_options "-ffreestanding")
diff --git a/libc/src/stdio/baremetal/getchar.cpp b/libc/src/stdio/baremetal/getchar.cpp
index 0a78a1ff8adf1..8fb7bc32537e7 100644
--- a/libc/src/stdio/baremetal/getchar.cpp
+++ b/libc/src/stdio/baremetal/getchar.cpp
@@ -17,7 +17,7 @@ namespace LIBC_NAMESPACE_DECL {
 LLVM_LIBC_FUNCTION(int, getchar, ()) {
   char buf[1];
   auto result = read_from_stdin(buf, sizeof(buf));
-  if (result < 0)
+  if (result <= 0)
     return EOF;
   return buf[0];
 }
diff --git a/libc/src/stdio/scanf_core/CMakeLists.txt b/libc/src/stdio/scanf_core/CMakeLists.txt
index 014413ccaa8da..79ae87bf3dd75 100644
--- a/libc/src/stdio/scanf_core/CMakeLists.txt
+++ b/libc/src/stdio/scanf_core/CMakeLists.txt
@@ -16,6 +16,10 @@ if(LIBC_TARGET_OS_IS_GPU)
     libc.src.stdio.ungetc
     libc.src.stdio.ferror
   )
+elseif(LIBC_TARGET_OS_IS_BAREMETAL)
+  list(APPEND file_deps
+    libc.src.stdio.getchar
+  )
 elseif(LLVM_LIBC_FULL_BUILD)
   list(APPEND file_deps
     libc.src.__support.File.file
@@ -54,13 +58,6 @@ add_header_library(
     libc.src.__support.CPP.string_view
 )
 
-if(NOT(TARGET libc.src.__support.File.file) AND LLVM_LIBC_FULL_BUILD AND
-  (NOT LIBC_TARGET_OS_IS_GPU))
-  # Not all platforms have a file implementation. If file is unvailable, and a
-  # full build is requested, then we must skip all file based scanf sections.
-  return()
-endif()
-
 add_object_library(
   scanf_main
   SRCS
@@ -117,6 +114,13 @@ add_object_library(
   ${use_system_file}
 )
 
+if(NOT(TARGET libc.src.__support.File.file) AND LLVM_LIBC_FULL_BUILD AND
+  (NOT LIBC_TARGET_OS_IS_GPU))
+  # Not all platforms have a file implementation. If file is unvailable, and a
+  # full build is requested, then we must skip all file based scanf sections.
+  return()
+endif()
+
 #TODO: condense the file-related code as possible.
 add_header_library(
     vfscanf_internal
diff --git a/libc/src/stdio/scanf_core/reader.h b/libc/src/stdio/scanf_core/reader.h
index 1f8ec9695a314..281dabe7faf01 100644
--- a/libc/src/stdio/scanf_core/reader.h
+++ b/libc/src/stdio/scanf_core/reader.h
@@ -11,13 +11,16 @@
 
 #include "hdr/types/FILE.h"
 
-#ifndef LIBC_COPT_STDIO_USE_SYSTEM_FILE
+#if !defined(LIBC_COPT_STDIO_USE_SYSTEM_FILE) && !defined(LIBC_TARGET_OS_IS_BAREMETAL)
 #include "src/__support/File/file.h"
 #endif
 
 #if defined(LIBC_TARGET_ARCH_IS_GPU)
 #include "src/stdio/getc.h"
 #include "src/stdio/ungetc.h"
+#elif defined(LIBC_TARGET_OS_IS_BAREMETAL)
+#include "src/stdio/getchar.h"
+#include "hdr/stdio_macros.h" // for EOF.
 #endif
 
 #include "src/__support/macros/attributes.h" // For LIBC_INLINE
@@ -47,6 +50,28 @@ LIBC_INLINE void ungetc(int c, void *f) {
   LIBC_NAMESPACE::ungetc(c, reinterpret_cast<::FILE *>(f));
 }
 
+#elif defined(LIBC_TARGET_OS_IS_BAREMETAL)
+// The baremetal build does not currently support file operations, but it does
+// declare pointers to the stanard FILE streams. The user just needs to declare
+// "FILE *stdin;" somwhere. That is not much to ask since a user already has to
+// define cookie structures for stdio.
+extern "C" FILE *stdin;
+
+LIBC_INLINE int getc(void *f) {
+  if (f == stdin) {
+    int c = getchar();
+    if (EOF == c)
+      c = '\0';
+
+    return c;
+  }
+
+  return '\0';
+}
+
+// Baremetal does not currently provide an ungetc(), so the Reader will need to
+// handle that for now.
+
 #elif !defined(LIBC_COPT_STDIO_USE_SYSTEM_FILE)
 
 LIBC_INLINE int getc(void *f) {
@@ -89,6 +114,10 @@ class Reader {
   ReadBuffer *rb;
   void *input_stream = nullptr;
   size_t cur_chars_read = 0;
+#if defined(LIBC_TARGET_OS_IS_BAREMETAL)
+  // Baremetal does not provide an ungetc(), so track that ourselves for now.
+  int unget_char = EOF;
+#endif
 
 public:
   // TODO: Set buff_len with a proper constant
@@ -107,6 +136,13 @@ class Reader {
       ++(rb->buff_cur);
       return output;
     }
+#if defined(LIBC_TARGET_OS_IS_BAREMETAL)
+    if (EOF != unget_char) {
+      char output = (char)unget_char;
+      unget_char = EOF;
+      return output;
+    }
+#endif
     // This should reset the buffer if applicable.
     return static_cast<char>(reader_internal::getc(input_stream));
   }
@@ -123,7 +159,11 @@ class Reader {
       --(rb->buff_cur);
       return;
     }
+#if defined(LIBC_TARGET_OS_IS_BAREMETAL)
+    unget_char = c;
+#else
     reader_internal::ungetc(static_cast<int>(c), input_stream);
+#endif
   }
 
   LIBC_INLINE size_t chars_read() { return cur_chars_read; }

>From ad6617dfdc062603ff23178e34f32642828ff199 Mon Sep 17 00:00:00 2001
From: Jesse DeGuire <jesse.a.deguire at gmail.com>
Date: Sun, 9 Mar 2025 18:59:17 -0500
Subject: [PATCH 2/2] fixup! [libc] Allow using sscanf() and vsscanf() on
 baremetal targets.

---
 libc/src/stdio/scanf_core/reader.h | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/libc/src/stdio/scanf_core/reader.h b/libc/src/stdio/scanf_core/reader.h
index 281dabe7faf01..d195dd5170f9f 100644
--- a/libc/src/stdio/scanf_core/reader.h
+++ b/libc/src/stdio/scanf_core/reader.h
@@ -11,7 +11,8 @@
 
 #include "hdr/types/FILE.h"
 
-#if !defined(LIBC_COPT_STDIO_USE_SYSTEM_FILE) && !defined(LIBC_TARGET_OS_IS_BAREMETAL)
+#if !defined(LIBC_COPT_STDIO_USE_SYSTEM_FILE) &&                               \
+    !defined(LIBC_TARGET_OS_IS_BAREMETAL)
 #include "src/__support/File/file.h"
 #endif
 
@@ -19,8 +20,8 @@
 #include "src/stdio/getc.h"
 #include "src/stdio/ungetc.h"
 #elif defined(LIBC_TARGET_OS_IS_BAREMETAL)
-#include "src/stdio/getchar.h"
 #include "hdr/stdio_macros.h" // for EOF.
+#include "src/stdio/getchar.h"
 #endif
 
 #include "src/__support/macros/attributes.h" // For LIBC_INLINE



More information about the libc-commits mailing list