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,10 @@ # errno.h entrypoints libc.src.errno.errno + + # stdio.h entrypoints + libc.src.stdio.puts + libc.src.stdio.fputs ) 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/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,25 @@ +//===--- 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 { + +ErrorOr platform_opendir(const char *name) { + return __llvm_libc::Error(-1); +} + +ErrorOr platform_fetch_dirents(int fd, cpp::span buffer) { + return __llvm_libc::Error(static_cast(-1)); +} + +int platform_closedir(int fd) { return -1; } + +} // 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,106 @@ +//===--- 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); +FileIOResult read_func(File *, void *, size_t) { return -1; } +ErrorOr seek_func(File *, long, int) { return 0; } +void cleanup_file(File *) { return; } +int close_func(File *) { return -1; } +int flush_func(File *) { return -1; } + +} // namespace + +class GPUFile : public File { + uintptr_t file; + +public: + constexpr GPUFile(uintptr_t file, File::ModeFlags modeflags) + : File(&write_func, &read_func, &seek_func, &close_func, flush_func, + &cleanup_file, nullptr, 0, 0, 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 + +// FIXME: Having the 'READ' flag on this variable causes the GPU backend to +// crash with recurisve references. +static GPUFile StdIn(0UL, File::ModeFlags(File::OpenMode::APPEND)); +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};