blob: 610fb68ead5e90c16cc8ce100f0c36223a3330cd [file] [edit]
// Copyright 2022 The clvk authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <sstream>
#include "printf.hpp"
// Extract the conversion specifier from a format string
char get_fmt_conversion(std::string_view fmt) {
auto conversionSpecPos = fmt.find_first_of("diouxXfFeEgGaAcsp");
return fmt.at(conversionSpecPos);
}
// Read type T from given pointer
template <typename T> T read_buff(const char* data) {
return *(reinterpret_cast<const T*>(data));
}
// Read type T from given pointer then increment the pointer
template <typename T> T read_inc_buff(char*& data) {
T out = *(reinterpret_cast<T*>(data));
data += sizeof(T);
return out;
}
// Extract the optional vector flag and return a modified format string suitable
// for calling snprintf on individual vector elements
std::string get_vector_fmt(std::string fmt, int& vector_size, int& element_size,
std::string& remaining_fmt) {
// Consume flags (skipping initial '%')
auto pos = fmt.find_first_not_of(" +-#0", 1ul);
// Consume precision and field width
pos = fmt.find_first_not_of("123456789.", pos);
if (pos == std::string::npos || fmt.at(pos) != 'v') {
vector_size = 1;
return std::string{fmt};
}
// Trim the data after the conversion specifier and store it in
// `remaining_fmt`
auto pos_conversion = fmt.find_first_of("diouxXfFeEgGaAcsp");
auto fmt_specifier = fmt.substr(0, pos_conversion + 1);
remaining_fmt = fmt.substr(pos_conversion + 1);
fmt = fmt_specifier;
size_t vec_length_pos_start = ++pos;
// Assume vec len is at max 8 in which case we need just one digit.
size_t vec_length_pos_end = pos + 1;
// Check if len is 16 and update accordingly. This is only possible if
// the first char of the size is 1.
if (fmt[vec_length_pos_start] == '1') {
vec_length_pos_end++;
}
auto vec_length_str = fmt.substr(vec_length_pos_start,
vec_length_pos_end - vec_length_pos_start);
int vec_length = std::atoi(vec_length_str.c_str());
auto fmt_pre_vec_len = fmt.substr(0, vec_length_pos_start - 1);
auto fmt_post_vec_len = fmt.substr(vec_length_pos_end, fmt.size());
fmt = fmt_pre_vec_len + fmt_post_vec_len;
// The length modifier is required with vectors
if (fmt_post_vec_len.find("hh") != std::string::npos) {
element_size = 1;
} else if (fmt_post_vec_len.find("hl") != std::string::npos) {
element_size = 4;
} else if (fmt_post_vec_len.find("h") != std::string::npos) {
element_size = 2;
} else if (fmt_post_vec_len.find("l") != std::string::npos) {
element_size = 8;
}
// If 'hl' length modifier is present, strip it as snprintf doesn't
// understand it
size_t hl = fmt.find("hl");
if (hl != std::string::npos) {
fmt.erase(hl, 2);
}
vector_size = vec_length;
return fmt;
}
// Print the format part containing exactly one arg using snprintf
std::string print_part(const std::string& fmt, const char* data, size_t size) {
// We don't know the exact size of the output string, but given we have a
// single argument, the size of the format string plus 1024 bytes is more
// than likely to fit everything. If it doesn't fit, just keep retrying with
// double the output size.
size_t out_size = fmt.size() + 1024;
std::vector<char> out(out_size);
out[0] = '\0';
auto conversion = std::tolower(get_fmt_conversion(fmt));
bool finished = false;
while (!finished) {
int written = 0;
switch (conversion) {
case 's': {
written = snprintf(out.data(), out_size, fmt.c_str(), data);
break;
}
case 'f':
case 'e':
case 'g':
case 'a': {
if (size == 2)
written = snprintf(out.data(), out_size, fmt.c_str(),
cl_half_to_float(read_buff<cl_half>(data)));
else if (size == 4)
written = snprintf(out.data(), out_size, fmt.c_str(),
read_buff<float>(data));
else
written = snprintf(out.data(), out_size, fmt.c_str(),
read_buff<double>(data));
break;
}
default: {
if (size == 1)
written = snprintf(out.data(), out_size, fmt.c_str(),
read_buff<uint8_t>(data));
else if (size == 2)
written = snprintf(out.data(), out_size, fmt.c_str(),
read_buff<uint16_t>(data));
else if (size == 4)
written = snprintf(out.data(), out_size, fmt.c_str(),
read_buff<uint32_t>(data));
else
written = snprintf(out.data(), out_size, fmt.c_str(),
read_buff<uint64_t>(data));
break;
}
}
// Finish if the string fit in the output buffer or snprintf failed,
// otherwise double the output buffer and try again. If snprintf failed,
// set the output to an empty string.
if (written < 0) {
out[0] = '\0';
finished = true;
} else if (written < static_cast<long>(out_size)) {
finished = true;
} else {
out_size *= 2;
out.resize(out_size);
}
}
return std::string(out.data());
}
void process_printf(char*& data, const printf_descriptor_map_t& descs,
char* data_end, cvk_printf_callback_t printf_cb,
void* printf_userdata) {
uint32_t printf_id = read_inc_buff<uint32_t>(data);
auto& format_string = descs.at(printf_id).format_string;
std::stringstream printf_out{};
// Firstly print the part of the format string up to the first '%' if any
// otherwise print the whole string as is and move the data pointer to the
// end.
size_t next_part = format_string.find_first_of('%');
if (next_part == std::string::npos) {
next_part = format_string.size();
data = data_end;
}
printf_out << format_string.substr(0, next_part);
// Decompose the remaining format string into individual strings with
// one format specifier each, handle each one individually
size_t arg_idx = 0;
while (next_part < format_string.size()) {
// Get the part of the format string before the next format specifier
size_t part_start = next_part;
size_t part_end = format_string.find_first_of('%', part_start + 1);
auto part_fmt = format_string.substr(part_start, part_end - part_start);
// Handle special cases
if (part_end == part_start + 1) {
printf_out << "%";
// We also need to print the literals between '%%' and the next '%'
next_part = part_start = part_end + 1;
part_end = format_string.find_first_of('%', part_start);
if (part_end != std::string::npos && part_end > part_start) {
part_fmt =
format_string.substr(part_start, part_end - part_start);
printf_out << part_fmt;
next_part = part_end;
}
continue;
} else if (part_end == std::string::npos &&
arg_idx >= descs.at(printf_id).arg_sizes.size()) {
// If there are no remaining arguments, the rest of the format
// should be printed verbatim
printf_out << part_fmt;
break;
}
// The size of the argument that this format part will consume
auto& size = descs.at(printf_id).arg_sizes[arg_idx];
if (data + size > data_end) {
data += size;
return;
}
// Check to see if we have a vector format specifier
int vec_len = 0;
int el_size = 0;
std::string remaining_str;
part_fmt = get_vector_fmt(part_fmt, vec_len, el_size, remaining_str);
// Scalar argument
if (vec_len < 2) {
// Special case for %s
if (get_fmt_conversion(part_fmt) == 's') {
uint32_t string_id = read_buff<uint32_t>(data);
printf_out << print_part(
part_fmt, descs.at(string_id).format_string.c_str(), size);
} else {
printf_out << print_part(part_fmt, data, size);
}
data += size;
} else {
// Vector argument
if (el_size == 0) {
// 'ele_size == 0' means that no length modifier has been used.
// According to the spec, this is an undefined behavior. Let's
// use the size coming from clspv and the vec_len to figure out
// the element size then.
el_size = size / vec_len;
}
auto* data_start = data;
for (int i = 0; i < vec_len - 1; i++) {
printf_out << print_part(part_fmt, data, size / vec_len) << ",";
data += el_size;
}
printf_out << print_part(part_fmt, data, size / vec_len)
<< remaining_str;
data = data_start + size;
}
// Move to the next format part and prepare to handle the next arg
next_part = part_end;
arg_idx++;
}
auto output = printf_out.str();
if (printf_cb != nullptr) {
auto len = output.size();
printf_cb(output.c_str(), len, data >= data_end, printf_userdata);
} else {
printf("%s", output.c_str());
}
}
cl_int cvk_printf(cvk_mem* printf_buffer,
const printf_descriptor_map_t& descriptors,
cvk_printf_callback_t printf_cb, void* printf_userdata) {
CVK_ASSERT(printf_buffer);
if (!printf_buffer->map()) {
cvk_error("Could not map printf buffer");
return CL_OUT_OF_RESOURCES;
}
char* data = static_cast<char*>(printf_buffer->host_va());
auto buffer_size = printf_buffer->size();
const auto bytes_written_size = sizeof(uint32_t);
const size_t data_size = buffer_size - bytes_written_size;
const size_t bytes_written = read_inc_buff<uint32_t>(data) * 4;
const size_t limit = std::min(bytes_written, data_size);
auto* data_end = data + limit;
while (data < data_end) {
process_printf(data, descriptors, data_end, printf_cb, printf_userdata);
}
if (buffer_size < bytes_written) {
cvk_warn_fn("Could not print all printfs. Around %.1f%% of them have "
"been skipped.",
100.0 * (float)(bytes_written - buffer_size) /
bytes_written);
}
printf_buffer->unmap_read_only();
return CL_SUCCESS;
}