[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