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; +};