Skip to content

Commit 7327014

Browse files
authored
[libc] Implement temporary printf on the GPU (#85331)
Summary: This patch adds a temporary implementation that uses a struct-based interface in lieu of varargs support. Once varargs support exists we will move this implementation to the "real" printf implementation. Conceptually, this patch has the client copy over its format string and arguments to the server. The server will then scan the format string searching for any specifiers that are actually a string. If it is a string then we will send the pointer back to the server to tell it to copy it back. This copied value will then replace the pointer when the final formatting is done. This will require a built-in extension to the varargs support to get access to the underlying struct. The varargs used on the GPU will simply be a struct wrapped in a varargs ABI.
1 parent 89271b4 commit 7327014

File tree

12 files changed

+424
-1
lines changed

12 files changed

+424
-1
lines changed

libc/config/gpu/entrypoints.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ set(TARGET_LIBC_ENTRYPOINTS
211211

212212
# gpu/rpc.h entrypoints
213213
libc.src.gpu.rpc_host_call
214+
libc.src.gpu.rpc_fprintf
214215
)
215216

216217
set(TARGET_LIBM_ENTRYPOINTS

libc/include/llvm-libc-types/rpc_opcodes_t.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ typedef enum {
3131
RPC_FTELL,
3232
RPC_FFLUSH,
3333
RPC_UNGETC,
34+
RPC_PRINTF_TO_STDOUT,
35+
RPC_PRINTF_TO_STDERR,
36+
RPC_PRINTF_TO_STREAM,
3437
RPC_LAST = 0xFFFF,
3538
} rpc_opcode_t;
3639

libc/spec/gpu_ext.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ def GPUExtensions : StandardSpec<"GPUExtensions"> {
1010
RetValSpec<VoidType>,
1111
[ArgSpec<VoidPtr>, ArgSpec<VoidPtr>, ArgSpec<SizeTType>]
1212
>,
13+
FunctionSpec<
14+
"rpc_fprintf",
15+
RetValSpec<IntType>,
16+
[ArgSpec<FILERestrictedPtr>,
17+
ArgSpec<ConstCharRestrictedPtr>,
18+
ArgSpec<VoidPtr>,
19+
ArgSpec<SizeTType>]
20+
>,
1321
]
1422
>;
1523
let Headers = [

libc/src/__support/arg_list.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#include <stdarg.h>
1515
#include <stddef.h>
16+
#include <stdint.h>
1617

1718
namespace LIBC_NAMESPACE {
1819
namespace internal {
@@ -60,6 +61,43 @@ class MockArgList {
6061
size_t read_count() const { return arg_counter; }
6162
};
6263

64+
// Used for the GPU implementation of `printf`. This models a variadic list as a
65+
// simple array of pointers that are built manually by the implementation.
66+
class StructArgList {
67+
void *ptr;
68+
void *end;
69+
70+
public:
71+
LIBC_INLINE StructArgList(void *ptr, size_t size)
72+
: ptr(ptr), end(reinterpret_cast<unsigned char *>(ptr) + size) {}
73+
LIBC_INLINE StructArgList(const StructArgList &other) {
74+
ptr = other.ptr;
75+
end = other.end;
76+
}
77+
LIBC_INLINE StructArgList() = default;
78+
LIBC_INLINE ~StructArgList() = default;
79+
80+
LIBC_INLINE StructArgList &operator=(const StructArgList &rhs) {
81+
ptr = rhs.ptr;
82+
return *this;
83+
}
84+
85+
LIBC_INLINE void *get_ptr() const { return ptr; }
86+
87+
template <class T> LIBC_INLINE T next_var() {
88+
ptr = reinterpret_cast<void *>(
89+
((reinterpret_cast<uintptr_t>(ptr) + alignof(T) - 1) / alignof(T)) *
90+
alignof(T));
91+
92+
if (ptr >= end)
93+
return T(-1);
94+
95+
T val = *reinterpret_cast<T *>(ptr);
96+
ptr = reinterpret_cast<unsigned char *>(ptr) + sizeof(T);
97+
return val;
98+
}
99+
};
100+
63101
} // namespace internal
64102
} // namespace LIBC_NAMESPACE
65103

libc/src/gpu/CMakeLists.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,15 @@ add_entrypoint_object(
88
libc.src.__support.RPC.rpc_client
99
libc.src.__support.GPU.utils
1010
)
11+
12+
add_entrypoint_object(
13+
rpc_fprintf
14+
SRCS
15+
rpc_fprintf.cpp
16+
HDRS
17+
rpc_fprintf.h
18+
DEPENDS
19+
libc.src.stdio.gpu.gpu_file
20+
libc.src.__support.RPC.rpc_client
21+
libc.src.__support.GPU.utils
22+
)

libc/src/gpu/rpc_fprintf.cpp

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//===-- GPU implementation of fprintf -------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "rpc_fprintf.h"
10+
11+
#include "src/__support/CPP/string_view.h"
12+
#include "src/__support/GPU/utils.h"
13+
#include "src/__support/RPC/rpc_client.h"
14+
#include "src/__support/common.h"
15+
#include "src/stdio/gpu/file.h"
16+
17+
namespace LIBC_NAMESPACE {
18+
19+
template <uint16_t opcode>
20+
int fprintf_impl(::FILE *__restrict file, const char *__restrict format,
21+
size_t format_size, void *args, size_t args_size) {
22+
uint64_t mask = gpu::get_lane_mask();
23+
rpc::Client::Port port = rpc::client.open<opcode>();
24+
25+
if constexpr (opcode == RPC_PRINTF_TO_STREAM) {
26+
port.send([&](rpc::Buffer *buffer) {
27+
buffer->data[0] = reinterpret_cast<uintptr_t>(file);
28+
});
29+
}
30+
31+
port.send_n(format, format_size);
32+
port.send_n(args, args_size);
33+
34+
uint32_t ret = 0;
35+
for (;;) {
36+
const char *str = nullptr;
37+
port.recv([&](rpc::Buffer *buffer) {
38+
ret = static_cast<uint32_t>(buffer->data[0]);
39+
str = reinterpret_cast<const char *>(buffer->data[1]);
40+
});
41+
// If any lanes have a string argument it needs to be copied back.
42+
if (!gpu::ballot(mask, str))
43+
break;
44+
45+
uint64_t size = str ? internal::string_length(str) + 1 : 0;
46+
port.send_n(str, size);
47+
}
48+
49+
port.close();
50+
return ret;
51+
}
52+
53+
// TODO: This is a stand-in function that uses a struct pointer and size in
54+
// place of varargs. Once varargs support is added we will use that to
55+
// implement the real version.
56+
LLVM_LIBC_FUNCTION(int, rpc_fprintf,
57+
(::FILE *__restrict stream, const char *__restrict format,
58+
void *args, size_t size)) {
59+
cpp::string_view str(format);
60+
if (stream == stdout)
61+
return fprintf_impl<RPC_PRINTF_TO_STDOUT>(stream, format, str.size() + 1,
62+
args, size);
63+
else if (stream == stderr)
64+
return fprintf_impl<RPC_PRINTF_TO_STDERR>(stream, format, str.size() + 1,
65+
args, size);
66+
else
67+
return fprintf_impl<RPC_PRINTF_TO_STREAM>(stream, format, str.size() + 1,
68+
args, size);
69+
}
70+
71+
} // namespace LIBC_NAMESPACE

libc/src/gpu/rpc_fprintf.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//===-- Implementation header for RPC functions -----------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_LIBC_SRC_GPU_RPC_HOST_CALL_H
10+
#define LLVM_LIBC_SRC_GPU_RPC_HOST_CALL_H
11+
12+
#include <stddef.h>
13+
#include <stdio.h>
14+
15+
namespace LIBC_NAMESPACE {
16+
17+
int rpc_fprintf(::FILE *__restrict stream, const char *__restrict format,
18+
void *argc, size_t size);
19+
20+
} // namespace LIBC_NAMESPACE
21+
22+
#endif // LLVM_LIBC_SRC_GPU_RPC_HOST_CALL_H

libc/test/integration/src/stdio/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
2+
add_subdirectory(${LIBC_TARGET_OS})
3+
endif()
14
add_custom_target(stdio-integration-tests)
25
add_dependencies(libc-integration-tests stdio-integration-tests)
36

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
add_custom_target(stdio-gpu-integration-tests)
2+
add_dependencies(libc-integration-tests stdio-gpu-integration-tests)
3+
4+
# Create an output directory for any temporary test files.
5+
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/testdata)
6+
7+
# These tests are not for correctness testing, but are instead a convenient way
8+
# to generate hermetic binaries for comparitive binary size testing.
9+
add_integration_test(
10+
printf_test
11+
SUITE
12+
stdio-gpu-integration-tests
13+
SRCS
14+
printf.cpp
15+
DEPENDS
16+
libc.src.gpu.rpc_fprintf
17+
libc.src.stdio.fopen
18+
LOADER_ARGS
19+
--threads 32
20+
--blocks 4
21+
)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//===-- RPC test to check args to printf ----------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "test/IntegrationTest/test.h"
10+
11+
#include "src/__support/GPU/utils.h"
12+
#include "src/gpu/rpc_fprintf.h"
13+
#include "src/stdio/fopen.h"
14+
15+
using namespace LIBC_NAMESPACE;
16+
17+
FILE *file = LIBC_NAMESPACE::fopen("testdata/test_data.txt", "w");
18+
19+
TEST_MAIN(int argc, char **argv, char **envp) {
20+
ASSERT_TRUE(file && "failed to open file");
21+
// Check basic printing.
22+
int written = 0;
23+
written = LIBC_NAMESPACE::rpc_fprintf(file, "A simple string\n", nullptr, 0);
24+
ASSERT_EQ(written, 16);
25+
26+
const char *str = "A simple string\n";
27+
written = LIBC_NAMESPACE::rpc_fprintf(file, "%s", &str, sizeof(void *));
28+
ASSERT_EQ(written, 16);
29+
30+
// Check printing a different value with each thread.
31+
uint64_t thread_id = gpu::get_thread_id();
32+
written = LIBC_NAMESPACE::rpc_fprintf(file, "%8ld\n", &thread_id,
33+
sizeof(thread_id));
34+
ASSERT_EQ(written, 9);
35+
36+
struct {
37+
uint32_t x = 1;
38+
char c = 'c';
39+
double f = 1.0;
40+
} args1;
41+
written =
42+
LIBC_NAMESPACE::rpc_fprintf(file, "%d%c%.1f\n", &args1, sizeof(args1));
43+
ASSERT_EQ(written, 6);
44+
45+
struct {
46+
uint32_t x = 1;
47+
const char *str = "A simple string\n";
48+
} args2;
49+
written =
50+
LIBC_NAMESPACE::rpc_fprintf(file, "%032b%s\n", &args2, sizeof(args2));
51+
ASSERT_EQ(written, 49);
52+
53+
// Check that the server correctly handles divergent numbers of arguments.
54+
const char *format = gpu::get_thread_id() % 2 ? "%s" : "%20ld\n";
55+
written = LIBC_NAMESPACE::rpc_fprintf(file, format, &str, sizeof(void *));
56+
ASSERT_EQ(written, gpu::get_thread_id() % 2 ? 16 : 21);
57+
58+
format = gpu::get_thread_id() % 2 ? "%s" : str;
59+
written = LIBC_NAMESPACE::rpc_fprintf(file, format, &str, sizeof(void *));
60+
ASSERT_EQ(written, 16);
61+
62+
// Check that we handle null arguments correctly.
63+
struct {
64+
void *null = nullptr;
65+
} args3;
66+
written = LIBC_NAMESPACE::rpc_fprintf(file, "%p", &args3, sizeof(args3));
67+
ASSERT_EQ(written, 9);
68+
69+
#ifndef LIBC_COPT_PRINTF_NO_NULLPTR_CHECKS
70+
written = LIBC_NAMESPACE::rpc_fprintf(file, "%s", &args3, sizeof(args3));
71+
ASSERT_EQ(written, 6);
72+
#endif // LIBC_COPT_PRINTF_NO_NULLPTR_CHECKS
73+
74+
// Check for extremely abused variable width arguments
75+
struct {
76+
uint32_t x = 1;
77+
uint32_t y = 2;
78+
double f = 1.0;
79+
} args4;
80+
written = LIBC_NAMESPACE::rpc_fprintf(file, "%**d", &args4, sizeof(args4));
81+
ASSERT_EQ(written, 4);
82+
written = LIBC_NAMESPACE::rpc_fprintf(file, "%**d%6d", &args4, sizeof(args4));
83+
ASSERT_EQ(written, 10);
84+
written = LIBC_NAMESPACE::rpc_fprintf(file, "%**.**f", &args4, sizeof(args4));
85+
ASSERT_EQ(written, 7);
86+
87+
return 0;
88+
}

libc/utils/gpu/server/CMakeLists.txt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
add_library(llvmlibc_rpc_server STATIC rpc_server.cpp)
1+
add_library(llvmlibc_rpc_server STATIC
2+
${LIBC_SOURCE_DIR}/src/stdio/printf_core/writer.cpp
3+
${LIBC_SOURCE_DIR}/src/stdio/printf_core/converter.cpp
4+
rpc_server.cpp
5+
)
26

37
# Include the RPC implemenation from libc.
48
target_include_directories(llvmlibc_rpc_server PRIVATE ${LIBC_SOURCE_DIR})
@@ -9,6 +13,10 @@ target_include_directories(llvmlibc_rpc_server PUBLIC ${CMAKE_CURRENT_SOURCE_DIR
913
target_compile_options(llvmlibc_rpc_server PUBLIC
1014
$<$<CXX_COMPILER_ID:GNU>:-Wno-attributes>)
1115
target_compile_definitions(llvmlibc_rpc_server PUBLIC
16+
LIBC_COPT_USE_C_ASSERT
17+
LIBC_COPT_ARRAY_ARG_LIST
18+
LIBC_COPT_PRINTF_DISABLE_WRITE_INT
19+
LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
1220
LIBC_NAMESPACE=${LIBC_NAMESPACE})
1321

1422
# Install the server and associated header.

0 commit comments

Comments
 (0)