diff --git a/libc/config/gpu/api.td b/libc/config/gpu/api.td --- a/libc/config/gpu/api.td +++ b/libc/config/gpu/api.td @@ -16,3 +16,13 @@ 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 --- a/libc/config/gpu/entrypoints.txt +++ b/libc/config/gpu/entrypoints.txt @@ -72,6 +72,13 @@ # 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 --- a/libc/config/gpu/headers.txt +++ b/libc/config/gpu/headers.txt @@ -4,4 +4,5 @@ libc.include.fenv libc.include.errno libc.include.stdlib + libc.include.stdio ) diff --git a/libc/src/__support/File/file.h b/libc/src/__support/File/file.h --- 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 @@ -37,6 +38,15 @@ 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 @@ static_cast(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 @@ 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/file.cpp b/libc/src/__support/File/file.cpp --- a/libc/src/__support/File/file.cpp +++ b/libc/src/__support/File/file.cpp @@ -25,20 +25,20 @@ prev_op = FileOp::WRITE; - if (bufmode == _IOFBF) { // fully buffered - return write_unlocked_fbf(static_cast(data), len); - } else if (bufmode == _IOLBF) { // line buffered - return write_unlocked_lbf(static_cast(data), len); - } else /*if (bufmode == _IONBF) */ { // unbuffered + if (!ENABLE_BUFFER || bufmode == _IONBF) { // unbuffered. size_t ret_val = write_unlocked_nbf(static_cast(data), len); flush_unlocked(); return ret_val; + } else if (bufmode == _IOFBF) { // fully buffered + return write_unlocked_fbf(static_cast(data), len); + } else /*if (bufmode == _IOLBF) */ { // line buffered + return write_unlocked_lbf(static_cast(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 @@ } 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::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/gpu/CMakeLists.txt b/libc/src/__support/File/gpu/CMakeLists.txt new file mode 100644 --- /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 --- /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 --- /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 + +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(); + port.send_n(data, size); + port.recv([&](rpc::Buffer *buffer) { + ret = reinterpret_cast(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(); + port.send_n(data, size); + port.recv([&](rpc::Buffer *buffer) { + ret = reinterpret_cast(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(); + port.send([&](rpc::Buffer *buffer) { + reinterpret_cast(buffer->data)[0] = file; + }); + port.send_n(data, size); + port.recv([&](rpc::Buffer *buffer) { + ret = reinterpret_cast(buffer->data)[0]; + }); + port.close(); + return ret; +} + +FileIOResult write_func(File *f, const void *data, size_t size) { + auto *gpu_file = reinterpret_cast(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 --- a/libc/src/__support/RPC/rpc.h +++ b/libc/src/__support/RPC/rpc.h @@ -33,11 +33,16 @@ /// 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, - TEST_INCREMENT = 3, - TEST_INTERFACE = 4, - TEST_STREAM = 5, + EXIT = 1, + WRITE_TO_STDOUT = 2, + WRITE_TO_STDERR = 3, + WRITE_TO_STREAM = 4, + + // TODO: These should be internal and not exported. + PRINT_TO_STDERR = 5, + TEST_INCREMENT = 6, + TEST_INTERFACE = 7, + TEST_STREAM = 8, }; /// 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 --- a/libc/test/src/stdio/CMakeLists.txt +++ b/libc/test/src/stdio/CMakeLists.txt @@ -201,7 +201,7 @@ 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 --- a/libc/utils/gpu/loader/Server.h +++ b/libc/utils/gpu/loader/Server.h @@ -31,6 +31,32 @@ 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(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(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(strs[i]); + } + break; + } case rpc::Opcode::PRINT_TO_STDERR: { uint64_t sizes[rpc::MAX_LANE_SIZE] = {0}; void *strs[rpc::MAX_LANE_SIZE] = {nullptr};