From e56000111e13ae2aaa27839b32904400cdd70ff5 Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Tue, 8 Jan 2019 10:32:41 +1000 Subject: [PATCH] vrend: add new string buffer implementation (v1.2) This adds a string buffer implementation + unit tests with the following features: - growing reallocation on append - variadic printf-like append function - indenting - error state tracking (stop appending on errors) This will be used in the GLSL shader construction to clean up the bad string length issues we have now. The variadic append is based on one Erik Faye-Lund wrote. v1.1: improve indenting v1.2: fix includes + use memcpy Reviewed-by: Erik Faye-Lund Signed-off-by: Dave Airlie --- src/Makefile.am | 1 + src/vrend_strbuf.h | 151 +++++++++++++++++++++ tests/Makefile.am | 6 +- tests/test_virgl_strbuf.c | 274 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 431 insertions(+), 1 deletion(-) create mode 100644 src/vrend_strbuf.h create mode 100644 tests/test_virgl_strbuf.c diff --git a/src/Makefile.am b/src/Makefile.am index ee5fc27..a9fb39a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -33,6 +33,7 @@ libvrend_la_SOURCES = \ vrend_formats.c \ vrend_blitter.c \ vrend_blitter.h \ + vrend_strbuf.h \ iov.c if HAVE_EPOXY_EGL diff --git a/src/vrend_strbuf.h b/src/vrend_strbuf.h new file mode 100644 index 0000000..5d023f2 --- /dev/null +++ b/src/vrend_strbuf.h @@ -0,0 +1,151 @@ +/************************************************************************** + * + * Copyright (C) 2019 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + **************************************************************************/ +#ifndef VREND_STRBUF_H +#define VREND_STRBUF_H + +#include +#include +#include +#include +#include "util/u_math.h" + +/* shader string buffer */ +struct vrend_strbuf { + /* NULL terminated string storage */ + char *buf; + /* allocation size (must be >= strlen(str) + 1) */ + size_t alloc_size; + /* size of string stored without terminating NULL */ + size_t size; + bool error_state; + int indent_level; +}; + +static inline void strbuf_set_error(struct vrend_strbuf *sb) +{ + sb->error_state = true; +} + +static inline bool strbuf_get_error(struct vrend_strbuf *sb) +{ + return sb->error_state; +} + +static inline void strbuf_indent(struct vrend_strbuf *sb) +{ + sb->indent_level++; +} + +static inline void strbuf_outdent(struct vrend_strbuf *sb) +{ + if (sb->indent_level <= 0) { + strbuf_set_error(sb); + return; + } + sb->indent_level--; +} + +static inline size_t strbuf_get_len(struct vrend_strbuf *sb) +{ + return sb->size; +} + +static inline void strbuf_free(struct vrend_strbuf *sb) +{ + free(sb->buf); +} + +static inline bool strbuf_alloc(struct vrend_strbuf *sb, int initial_size) +{ + sb->buf = malloc(initial_size); + if (!sb->buf) + return false; + sb->alloc_size = initial_size; + sb->buf[0] = 0; + sb->error_state = false; + sb->size = 0; + sb->indent_level = 0; + return true; +} + +/* this might need tuning */ +#define STRBUF_MIN_MALLOC 1024 + +static inline void strbuf_append(struct vrend_strbuf *sb, const char *addstr) +{ + int new_len = strlen(addstr) + sb->indent_level; + if (strbuf_get_error(sb)) + return; + if (sb->size + new_len + 1 > sb->alloc_size) { + /* Reallocate to the larger size of current alloc + min realloc, + * or the resulting string size if larger. + */ + size_t new_size = MAX2(sb->size + new_len + 1, sb->alloc_size + STRBUF_MIN_MALLOC); + char *new = realloc(sb->buf, new_size); + if (!new) { + strbuf_set_error(sb); + return; + } + sb->buf = new; + sb->alloc_size = new_size; + } + if (sb->indent_level) { + memset(sb->buf + sb->size, '\t', sb->indent_level); + sb->size += sb->indent_level; + sb->buf[sb->size] = '\0'; + new_len -= sb->indent_level; + } + memcpy(sb->buf + sb->size, addstr, new_len); + sb->size += new_len; + sb->buf[sb->size] = '\0'; +} + +static inline void strbuf_vappendf(struct vrend_strbuf *sb, const char *fmt, va_list ap) +{ + char buf[512]; + int len = vsnprintf(buf, sizeof(buf), fmt, ap); + if (len < (int)sizeof(buf)) { + strbuf_append(sb, buf); + return; + } + + char *tmp = malloc(len); + if (!tmp) { + strbuf_set_error(sb); + return; + } + vsnprintf(tmp, len, fmt, ap); + strbuf_append(sb, tmp); + free(tmp); +} + +__attribute__((format(printf, 2, 3))) +static inline void strbuf_appendf(struct vrend_strbuf *sb, const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + strbuf_vappendf(sb, fmt, va); + va_end(va); +} +#endif diff --git a/tests/Makefile.am b/tests/Makefile.am index 0aa7c77..e918ab1 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -6,7 +6,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/gallium/include $(CHECK_CF TEST_LIBS = libvrtest.la $(top_builddir)/src/gallium/auxiliary/libgallium.la $(top_builddir)/src/libvirglrenderer.la $(CHECK_LIBS) -run_tests = test_virgl_init test_virgl_transfer test_virgl_resource test_virgl_cmd +run_tests = test_virgl_init test_virgl_transfer test_virgl_resource test_virgl_cmd test_virgl_strbuf noinst_LTLIBRARIES = libvrtest.la libvrtest_la_SOURCES = testvirgl.c \ @@ -33,6 +33,10 @@ test_virgl_cmd_SOURCES = test_virgl_cmd.c large_shader.h test_virgl_cmd_LDADD = $(TEST_LIBS) test_virgl_cmd_LDFLAGS = -no-install +test_virgl_strbuf_SOURCES = test_virgl_strbuf.c +test_virgl_strbuf_LDADD = $(CHECK_LIBS) +test_virgl_strbuf_LDFLAGS = -no-install + if HAVE_VALGRIND VALGRIND_FLAGS= \ --leak-check=full \ diff --git a/tests/test_virgl_strbuf.c b/tests/test_virgl_strbuf.c new file mode 100644 index 0000000..ef5f902 --- /dev/null +++ b/tests/test_virgl_strbuf.c @@ -0,0 +1,274 @@ +/************************************************************************** + * + * Copyright (C) 2019 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + **************************************************************************/ +#include +#include +#include "../src/vrend_strbuf.h" + +/* Test the vrend strbuf implementation */ + +START_TEST(strbuf_init) +{ + struct vrend_strbuf sb; + bool ret; + ret = strbuf_alloc(&sb, 1024); + ck_assert_int_eq(ret, true); + ck_assert_int_eq(sb.alloc_size, 1024); + strbuf_free(&sb); +} +END_TEST + +START_TEST(strbuf_add_small_string) +{ + struct vrend_strbuf sb; + bool ret; + char str[27] = {}; + ret = strbuf_alloc(&sb, 1024); + ck_assert_int_eq(ret, true); + + for (int i = 0; i < 26; i++) + str[i] = 'a' + i; + str[26] = 0; + strbuf_append(&sb, str); + ck_assert_int_eq(strbuf_get_error(&sb), false); + ck_assert_int_eq(sb.size, strlen(sb.buf)); + strbuf_free(&sb); +} +END_TEST + +START_TEST(strbuf_add_small_string_twice) +{ + struct vrend_strbuf sb; + bool ret; + char str[27] = {}; + ret = strbuf_alloc(&sb, 1024); + ck_assert_int_eq(ret, true); + + for (int i = 0; i < 26; i++) + str[i] = 'a' + i; + str[26] = 0; + strbuf_append(&sb, str); + strbuf_append(&sb, str); + ck_assert_int_eq(strbuf_get_error(&sb), false); + ck_assert_int_eq(strbuf_get_len(&sb), strlen(sb.buf)); + strbuf_free(&sb); +} +END_TEST + +START_TEST(strbuf_add_large_string) +{ + struct vrend_strbuf sb; + bool ret; + char str[256]; + ret = strbuf_alloc(&sb, 128); + ck_assert_int_eq(ret, true); + + for (int i = 0; i < 255; i++) + str[i] = 'a' + (i % 26); + str[255] = 0; + strbuf_append(&sb, str); + + ck_assert_int_eq(strbuf_get_error(&sb), false); + ck_assert_int_eq(strbuf_get_len(&sb), strlen(sb.buf)); + ck_assert_int_eq(sb.alloc_size, 128 + STRBUF_MIN_MALLOC); + strbuf_free(&sb); +} +END_TEST + +START_TEST(strbuf_add_huge_string) +{ + struct vrend_strbuf sb; + bool ret; + char str[2048]; + ret = strbuf_alloc(&sb, 128); + ck_assert_int_eq(ret, true); + + for (int i = 0; i < 2047; i++) + str[i] = 'a' + (i % 26); + str[2047] = 0; + strbuf_append(&sb, str); + + ck_assert_int_eq(strbuf_get_error(&sb), false); + ck_assert_int_eq(strbuf_get_len(&sb), strlen(sb.buf)); + ck_assert_int_eq(sb.alloc_size, 2048); + ck_assert_int_ge(sb.alloc_size, strbuf_get_len(&sb) + 1); + strbuf_free(&sb); +} +END_TEST + +START_TEST(strbuf_test_boundary) +{ + struct vrend_strbuf sb; + bool ret; + char str[128]; + ret = strbuf_alloc(&sb, 128); + ck_assert_int_eq(ret, true); + + for (int i = 0; i < 127; i++) + str[i] = 'a' + (i % 26); + str[127] = 0; + strbuf_append(&sb, str); + ck_assert_int_eq(strbuf_get_error(&sb), false); + ck_assert_int_eq(strbuf_get_len(&sb), strlen(sb.buf)); + ck_assert_int_eq(sb.alloc_size, 128); + ck_assert_int_ge(sb.alloc_size, strbuf_get_len(&sb) + 1); + strbuf_free(&sb); +} +END_TEST + +START_TEST(strbuf_test_boundary2) +{ + struct vrend_strbuf sb; + bool ret; + char str[513]; + ret = strbuf_alloc(&sb, 1024); + ck_assert_int_eq(ret, true); + + for (int i = 0; i < 512; i++) + str[i] = 'a' + (i % 26); + str[512] = 0; + strbuf_append(&sb, str); + strbuf_append(&sb, str); + ck_assert_int_eq(strbuf_get_error(&sb), false); + ck_assert_int_eq(strbuf_get_len(&sb), strlen(sb.buf)); + /* we should have 512 + 512 + 1 at least */ + ck_assert_int_ge(sb.alloc_size, strbuf_get_len(&sb) + 1); + ck_assert_int_gt(sb.alloc_size, 1024); + + strbuf_free(&sb); +} +END_TEST + +START_TEST(strbuf_test_indent) +{ + struct vrend_strbuf sb; + bool ret; + char str[9]; + ret = strbuf_alloc(&sb, 1024); + ck_assert_int_eq(ret, true); + + for (int i = 0; i < 8; i++) + str[i] = 'a' + i; + str[8] = 0; + + strbuf_indent(&sb); + strbuf_append(&sb, str); + strbuf_outdent(&sb); + strbuf_append(&sb, str); + /* make sure the TAB got added */ + ck_assert_int_eq(strbuf_get_error(&sb), false); + + ck_assert_str_eq(sb.buf, "\tabcdefghabcdefgh"); + strbuf_free(&sb); +} +END_TEST + +START_TEST(strbuf_test_indent2) +{ + struct vrend_strbuf sb; + bool ret; + char str[513]; + ret = strbuf_alloc(&sb, 1024); + ck_assert_int_eq(ret, true); + + strbuf_indent(&sb); + for (int i = 0; i < 512; i++) + str[i] = 'a' + (i % 26); + str[512] = 0; + strbuf_append(&sb, str); + strbuf_outdent(&sb); + /* make sure the TAB got added */ + ck_assert_int_eq(strbuf_get_len(&sb), strlen(str) + 1); + strbuf_append(&sb, str); + ck_assert_int_eq(strbuf_get_error(&sb), false); + ck_assert_int_eq(strbuf_get_len(&sb), strlen(sb.buf)); + /* we should have 512 + 512 + 1 at least */ + ck_assert_int_ge(sb.alloc_size, strbuf_get_len(&sb) + 1); + ck_assert_int_gt(sb.alloc_size, 1024); + + strbuf_free(&sb); +} +END_TEST + +START_TEST(strbuf_test_appendf) +{ + struct vrend_strbuf sb; + bool ret; + ret = strbuf_alloc(&sb, 1024); + ck_assert_int_eq(ret, true); + strbuf_appendf(&sb, "%d", 5); + ck_assert_str_eq(sb.buf, "5"); + strbuf_free(&sb); +} +END_TEST + +START_TEST(strbuf_test_appendf_str) +{ + struct vrend_strbuf sb; + bool ret; + ret = strbuf_alloc(&sb, 1024); + ck_assert_int_eq(ret, true); + strbuf_appendf(&sb, "%s5", "hello"); + ck_assert_str_eq(sb.buf, "hello5"); + strbuf_free(&sb); +} +END_TEST + +static Suite *init_suite(void) +{ + Suite *s; + TCase *tc_core; + + s = suite_create("vrend_strbuf"); + tc_core = tcase_create("strbuf"); + + suite_add_tcase(s, tc_core); + + tcase_add_test(tc_core, strbuf_init); + tcase_add_test(tc_core, strbuf_add_small_string); + tcase_add_test(tc_core, strbuf_add_small_string_twice); + tcase_add_test(tc_core, strbuf_add_large_string); + tcase_add_test(tc_core, strbuf_add_huge_string); + tcase_add_test(tc_core, strbuf_test_boundary); + tcase_add_test(tc_core, strbuf_test_boundary2); + tcase_add_test(tc_core, strbuf_test_indent); + tcase_add_test(tc_core, strbuf_test_indent2); + tcase_add_test(tc_core, strbuf_test_appendf); + tcase_add_test(tc_core, strbuf_test_appendf_str); + return s; +} + +int main(void) +{ + Suite *s; + SRunner *sr; + int number_failed; + + s = init_suite(); + sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return number_failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +};