[libc-commits] [libc] e6c401b - [libc] Add initial support for 'puts' and 'fputs' to the GPU

Joseph Huber via libc-commits libc-commits at lists.llvm.org
Mon Jun 5 15:57:05 PDT 2023


Author: Joseph Huber
Date: 2023-06-05T17:56:55-05:00
New Revision: e6c401b5e84619c542b3db0d977fe622e872cfa1

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

LOG: [libc] Add initial support for 'puts' and 'fputs' to the GPU

This patch adds the initial support required to support basic priting in
`stdio.h` via `puts` and `fputs`. This is done using the existing LLVM C
library `File` API. In this sense we can think of the RPC interface as
our system call to dump the character string to the file. We carry a
`uintptr_t` reference as our native "file descriptor" as it will be used
as an opaque reference to the host's version once functions like
`fopen` are supported.

For some unknown reason the declaration of the `StdIn` variable causes
both the AMDGPU and NVPTX backends to crash if I use the `READ` flag.
This is not used currently as we only support output now, but it needs
to be fixed

Reviewed By: sivachandra, lntue

Differential Revision: https://reviews.llvm.org/D151282

Added: 
    libc/src/__support/File/gpu/CMakeLists.txt
    libc/src/__support/File/gpu/dir.cpp
    libc/src/__support/File/gpu/file.cpp

Modified: 
    libc/config/gpu/api.td
    libc/config/gpu/entrypoints.txt
    libc/config/gpu/headers.txt
    libc/src/__support/File/file.cpp
    libc/src/__support/File/file.h
    libc/src/__support/RPC/rpc.h
    libc/test/src/stdio/CMakeLists.txt
    libc/utils/gpu/loader/Server.h

Removed: 
    


################################################################################
diff  --git a/libc/config/gpu/api.td b/libc/config/gpu/api.td
index c5f555c90178c..f6209c4bebe56 100644
--- a/libc/config/gpu/api.td
+++ b/libc/config/gpu/api.td
@@ -16,3 +16,13 @@ def StdlibAPI : PublicAPI<"stdlib.h"> {
 def FenvAPI: PublicAPI<"fenv.h"> {
   let Types = ["fenv_t"];
 }
+
+def StdIOAPI : PublicAPI<"stdio.h"> {
+  let Macros = [
+    SimpleMacroDef<"_IOFBF", "0">,
+    SimpleMacroDef<"_IOLBF", "1">,
+    SimpleMacroDef<"_IONBF", "2">,
+    SimpleMacroDef<"EOF", "-1">,
+  ];
+  let Types = ["size_t", "FILE"];
+}

diff  --git a/libc/config/gpu/entrypoints.txt b/libc/config/gpu/entrypoints.txt
index 170f21c73f808..650fa3d2affb3 100644
--- a/libc/config/gpu/entrypoints.txt
+++ b/libc/config/gpu/entrypoints.txt
@@ -72,6 +72,13 @@ set(TARGET_LIBC_ENTRYPOINTS
 
     # errno.h entrypoints
     libc.src.errno.errno
+
+    # stdio.h entrypoints
+    libc.src.stdio.puts
+    libc.src.stdio.fputs
+    libc.src.stdio.stdin
+    libc.src.stdio.stdout
+    libc.src.stdio.stderr
 )
 
 set(TARGET_LLVMLIBC_ENTRYPOINTS

diff  --git a/libc/config/gpu/headers.txt b/libc/config/gpu/headers.txt
index 608ff42cbdbc5..73406f2d0539c 100644
--- a/libc/config/gpu/headers.txt
+++ b/libc/config/gpu/headers.txt
@@ -4,4 +4,5 @@ set(TARGET_PUBLIC_HEADERS
     libc.include.fenv
     libc.include.errno
     libc.include.stdlib
+    libc.include.stdio
 )

diff  --git a/libc/src/__support/File/file.cpp b/libc/src/__support/File/file.cpp
index 6a80ce0c8fb89..326123d8b7482 100644
--- a/libc/src/__support/File/file.cpp
+++ b/libc/src/__support/File/file.cpp
@@ -25,20 +25,20 @@ FileIOResult File::write_unlocked(const void *data, size_t len) {
 
   prev_op = FileOp::WRITE;
 
-  if (bufmode == _IOFBF) { // fully buffered
-    return write_unlocked_fbf(static_cast<const uint8_t *>(data), len);
-  } else if (bufmode == _IOLBF) { // line buffered
-    return write_unlocked_lbf(static_cast<const uint8_t *>(data), len);
-  } else /*if (bufmode == _IONBF) */ { // unbuffered
+  if (!ENABLE_BUFFER || bufmode == _IONBF) { // unbuffered.
     size_t ret_val =
         write_unlocked_nbf(static_cast<const uint8_t *>(data), len);
     flush_unlocked();
     return ret_val;
+  } else if (bufmode == _IOFBF) { // fully buffered
+    return write_unlocked_fbf(static_cast<const uint8_t *>(data), len);
+  } else /*if (bufmode == _IOLBF) */ { // line buffered
+    return write_unlocked_lbf(static_cast<const uint8_t *>(data), len);
   }
 }
 
 FileIOResult File::write_unlocked_nbf(const uint8_t *data, size_t len) {
-  if (pos > 0) { // If the buffer is not empty
+  if (ENABLE_BUFFER && pos > 0) { // If the buffer is not empty
     // Flush the buffer
     const size_t write_size = pos;
     auto write_result = platform_write(this, buf, write_size);
@@ -325,6 +325,9 @@ ErrorOr<long> File::tell() {
 }
 
 int File::flush_unlocked() {
+  if constexpr (!ENABLE_BUFFER)
+    return 0;
+
   if (prev_op == FileOp::WRITE && pos > 0) {
     auto buf_result = platform_write(this, buf, pos);
     if (buf_result.has_error() || buf_result.value < pos) {
@@ -339,9 +342,11 @@ int File::flush_unlocked() {
 }
 
 int File::set_buffer(void *buffer, size_t size, int buffer_mode) {
+  if constexpr (!ENABLE_BUFFER)
+    return EINVAL;
+
   // We do not need to lock the file as this method should be called before
   // other operations are performed on the file.
-
   if (buffer != nullptr && size == 0)
     return EINVAL;
 

diff  --git a/libc/src/__support/File/file.h b/libc/src/__support/File/file.h
index 3c035cdd5fc63..46bad008fcf7f 100644
--- a/libc/src/__support/File/file.h
+++ b/libc/src/__support/File/file.h
@@ -11,6 +11,7 @@
 
 #include "src/__support/CPP/new.h"
 #include "src/__support/error_or.h"
+#include "src/__support/macros/properties/architectures.h"
 #include "src/__support/threads/mutex.h"
 
 #include <stddef.h>
@@ -37,6 +38,15 @@ class File {
 public:
   static constexpr size_t DEFAULT_BUFFER_SIZE = 1024;
 
+// Some platforms like the GPU build cannot support buffering due to extra
+// resource usage or hardware constraints. This function allows us to optimize
+// out the buffering portions of the code in the general implementation.
+#if defined(LIBC_TARGET_ARCH_IS_GPU)
+  static constexpr bool ENABLE_BUFFER = false;
+#else
+  static constexpr bool ENABLE_BUFFER = true;
+#endif
+
   using LockFunc = void(File *);
   using UnlockFunc = void(File *);
 
@@ -174,10 +184,14 @@ class File {
                    static_cast<ModeFlags>(OpenMode::PLUS));
   }
 
+  // The GPU build should not emit a destructor because we do not support global
+  // destructors in all cases and it is unneccessary without buffering.
+#if !defined(LIBC_TARGET_ARCH_IS_GPU)
   ~File() {
     if (own_buf)
       delete buf;
   }
+#endif
 
 public:
   // We want this constructor to be constexpr so that global file objects
@@ -197,7 +211,8 @@ class File {
         bufsize(buffer_size), bufmode(buffer_mode), own_buf(owned),
         mode(modeflags), pos(0), prev_op(FileOp::NONE), read_limit(0),
         eof(false), err(false) {
-    adjust_buf();
+    if constexpr (ENABLE_BUFFER)
+      adjust_buf();
   }
 
   // Close |f| and cleanup resources held by it.

diff  --git a/libc/src/__support/File/gpu/CMakeLists.txt b/libc/src/__support/File/gpu/CMakeLists.txt
new file mode 100644
index 0000000000000..9e32af5efc9a7
--- /dev/null
+++ b/libc/src/__support/File/gpu/CMakeLists.txt
@@ -0,0 +1,21 @@
+add_object_library(
+  gpu_file
+  SRCS
+    file.cpp
+  DEPENDS
+    libc.include.stdio
+    libc.src.errno.errno
+    libc.src.__support.CPP.new
+    libc.src.__support.error_or
+    libc.src.__support.File.file
+)
+
+add_object_library(
+  gpu_dir
+  SRCS
+    dir.cpp
+  DEPENDS
+    libc.src.errno.errno
+    libc.src.__support.error_or
+    libc.src.__support.File.dir
+)

diff  --git a/libc/src/__support/File/gpu/dir.cpp b/libc/src/__support/File/gpu/dir.cpp
new file mode 100644
index 0000000000000..31fdf7cf2223c
--- /dev/null
+++ b/libc/src/__support/File/gpu/dir.cpp
@@ -0,0 +1,13 @@
+//===--- GPU implementation of the Dir helpers ----------------------------===//
+//
+// 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/__support/File/dir.h"
+
+#include "src/__support/error_or.h"
+
+namespace __llvm_libc {} // namespace __llvm_libc

diff  --git a/libc/src/__support/File/gpu/file.cpp b/libc/src/__support/File/gpu/file.cpp
new file mode 100644
index 0000000000000..9985a3c76df97
--- /dev/null
+++ b/libc/src/__support/File/gpu/file.cpp
@@ -0,0 +1,99 @@
+//===--- GPU specialization of the File data structure --------------------===//
+//
+// 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/__support/File/file.h"
+
+#include "src/__support/RPC/rpc_client.h"
+#include "src/errno/libc_errno.h" // For error macros
+
+#include <stdio.h>
+
+namespace __llvm_libc {
+
+namespace {
+
+FileIOResult write_func(File *, const void *, size_t);
+
+} // namespace
+
+class GPUFile : public File {
+  uintptr_t file;
+
+public:
+  constexpr GPUFile(uintptr_t file, File::ModeFlags modeflags)
+      : File(&write_func, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
+             0, _IONBF, false, modeflags),
+        file(file) {}
+
+  uintptr_t get_file() const { return file; }
+};
+
+namespace {
+
+int write_to_stdout(const void *data, size_t size) {
+  int ret = 0;
+  rpc::Client::Port port = rpc::client.open<rpc::WRITE_TO_STDOUT>();
+  port.send_n(data, size);
+  port.recv([&](rpc::Buffer *buffer) {
+    ret = reinterpret_cast<int *>(buffer->data)[0];
+  });
+  port.close();
+  return ret;
+}
+
+int write_to_stderr(const void *data, size_t size) {
+  int ret = 0;
+  rpc::Client::Port port = rpc::client.open<rpc::WRITE_TO_STDERR>();
+  port.send_n(data, size);
+  port.recv([&](rpc::Buffer *buffer) {
+    ret = reinterpret_cast<int *>(buffer->data)[0];
+  });
+  port.close();
+  return ret;
+}
+
+int write_to_stream(uintptr_t file, const void *data, size_t size) {
+  int ret = 0;
+  rpc::Client::Port port = rpc::client.open<rpc::WRITE_TO_STREAM>();
+  port.send([&](rpc::Buffer *buffer) {
+    reinterpret_cast<uintptr_t *>(buffer->data)[0] = file;
+  });
+  port.send_n(data, size);
+  port.recv([&](rpc::Buffer *buffer) {
+    ret = reinterpret_cast<int *>(buffer->data)[0];
+  });
+  port.close();
+  return ret;
+}
+
+FileIOResult write_func(File *f, const void *data, size_t size) {
+  auto *gpu_file = reinterpret_cast<GPUFile *>(f);
+  int ret = 0;
+  if (gpu_file == stdout)
+    ret = write_to_stdout(data, size);
+  else if (gpu_file == stderr)
+    ret = write_to_stderr(data, size);
+  else
+    ret = write_to_stream(gpu_file->get_file(), data, size);
+  if (ret < 0)
+    return {0, -ret};
+  return ret;
+}
+
+} // namespace
+
+static GPUFile StdIn(0UL, File::ModeFlags(File::OpenMode::READ));
+File *stdin = &StdIn;
+
+static GPUFile StdOut(0UL, File::ModeFlags(File::OpenMode::APPEND));
+File *stdout = &StdOut;
+
+static GPUFile StdErr(0UL, File::ModeFlags(File::OpenMode::APPEND));
+File *stderr = &StdErr;
+
+} // namespace __llvm_libc

diff  --git a/libc/src/__support/RPC/rpc.h b/libc/src/__support/RPC/rpc.h
index e2b043c98e884..8e8fbd5860001 100644
--- a/libc/src/__support/RPC/rpc.h
+++ b/libc/src/__support/RPC/rpc.h
@@ -33,13 +33,16 @@ namespace rpc {
 /// A list of opcodes that we use to invoke certain actions on the server.
 enum Opcode : uint16_t {
   NOOP = 0,
-  PRINT_TO_STDERR = 1,
-  EXIT = 2,
-  MALLOC = 3,
-  FREE = 4,
-  TEST_INCREMENT = 5,
-  TEST_INTERFACE = 6,
-  TEST_STREAM = 7,
+  EXIT = 1,
+  WRITE_TO_STDOUT = 2,
+  WRITE_TO_STDERR = 3,
+  WRITE_TO_STREAM = 4,
+  MALLOC = 5,
+  FREE = 6,
+  PRINT_TO_STDERR = 7,
+  TEST_INCREMENT = 8,
+  TEST_INTERFACE = 9,
+  TEST_STREAM = 10,
 };
 
 /// A fixed size channel used to communicate between the RPC client and server.

diff  --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt
index 5b66cadcdc127..cc71bf5a57bae 100644
--- a/libc/test/src/stdio/CMakeLists.txt
+++ b/libc/test/src/stdio/CMakeLists.txt
@@ -201,7 +201,7 @@ add_libc_unittest(
     LibcFPTestHelpers
 )
 
-add_libc_unittest(
+add_libc_test(
   puts_test
   SUITE
     libc_stdio_unittests

diff  --git a/libc/utils/gpu/loader/Server.h b/libc/utils/gpu/loader/Server.h
index a8dffb67a7868..a2bce068407b4 100644
--- a/libc/utils/gpu/loader/Server.h
+++ b/libc/utils/gpu/loader/Server.h
@@ -32,6 +32,32 @@ void handle_server(Alloc allocator, Dealloc deallocator) {
       return;
 
     switch (port->get_opcode()) {
+    case rpc::Opcode::WRITE_TO_STREAM:
+    case rpc::Opcode::WRITE_TO_STDERR:
+    case rpc::Opcode::WRITE_TO_STDOUT: {
+      uint64_t sizes[rpc::MAX_LANE_SIZE] = {0};
+      void *strs[rpc::MAX_LANE_SIZE] = {nullptr};
+      FILE *files[rpc::MAX_LANE_SIZE] = {nullptr};
+      if (port->get_opcode() == rpc::Opcode::WRITE_TO_STREAM)
+        port->recv([&](rpc::Buffer *buffer, uint32_t id) {
+          files[id] = reinterpret_cast<FILE *>(buffer->data[0]);
+        });
+      port->recv_n(strs, sizes, [&](uint64_t size) { return new char[size]; });
+      port->send([&](rpc::Buffer *buffer, uint32_t id) {
+        FILE *file = port->get_opcode() == rpc::Opcode::WRITE_TO_STDOUT
+                         ? stdout
+                         : (port->get_opcode() == rpc::Opcode::WRITE_TO_STDERR
+                                ? stderr
+                                : files[id]);
+        int ret = fwrite(strs[id], sizes[id], 1, file);
+        reinterpret_cast<int *>(buffer->data)[0] = ret >= 0 ? sizes[id] : ret;
+      });
+      for (uint64_t i = 0; i < rpc::MAX_LANE_SIZE; ++i) {
+        if (strs[i])
+          delete[] reinterpret_cast<uint8_t *>(strs[i]);
+      }
+      break;
+    }
     case rpc::Opcode::PRINT_TO_STDERR: {
       uint64_t sizes[rpc::MAX_LANE_SIZE] = {0};
       void *strs[rpc::MAX_LANE_SIZE] = {nullptr};


        


More information about the libc-commits mailing list