├── .gitignore ├── startle.h ├── startle.mk ├── stats_types.h ├── types.h ├── README.md ├── gen.mk ├── stats.c ├── test.c ├── dispatch.h ├── error.c ├── LICENSE ├── macros.h ├── log.c ├── support.c └── map.c /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | /html 3 | /latex 4 | /bin 5 | /doc 6 | -------------------------------------------------------------------------------- /startle.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2012-2018 Dustin M. DeWeese 2 | 3 | This file is part of the Startle library. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | #include "gen/startle/map.h" 19 | #include "gen/startle/support.h" 20 | #include "gen/startle/log.h" 21 | #include "gen/startle/error.h" 22 | #include "gen/startle/test.h" 23 | -------------------------------------------------------------------------------- /startle.mk: -------------------------------------------------------------------------------- 1 | include startle/gen.mk 2 | 3 | print-%: 4 | @echo $* = $($*) 5 | 6 | # modified from http://scottmcpeak.com/autodepend/autodepend.html 7 | 8 | # pull in dependency info for *existing* .o files 9 | -include $(DEPS) 10 | 11 | # generate dependency info and headers 12 | $(BUILD_DIR)/%.d: %.c 13 | @mkdir -p $(dir $@) 14 | @$(CC) $(INCLUDE) -MM $(CFLAGS) $*.c -MG -MT $(BUILD_DIR)/$*.o -o- 2>/dev/null | \ 15 | sed -E -e 's/ ([a-zA-Z][^ .]*)\.h/ .gen\/\1.h/g' > $(BUILD_DIR)/$*.d 16 | 17 | LOCAL_HEADERS += $(shell find -L . -not -path './.gen/*' -name '*.h') 18 | LOCAL_HEADERS := $(sort $(LOCAL_HEADERS)) 19 | GEN_LOCAL_HEADERS := $(patsubst ./%.h, .gen/%.h, $(LOCAL_HEADERS)) 20 | 21 | # hack to catch any dependencies in .gen that are local headers 22 | $(GEN_LOCAL_HEADERS): .gen/%.h: %.h 23 | @mkdir -p $(dir $@) 24 | ln -fs $(PWD)/$*.h .gen/$*.h 25 | 26 | # compile 27 | $(BUILD_DIR)/%.o: %.c $(BUILD_DIR)/%.d 28 | @echo $*.o 29 | $(CC) -c $(CFLAGS) $*.c -o $(BUILD_DIR)/$*.o 30 | 31 | .SECONDARY: $(GEN) 32 | -------------------------------------------------------------------------------- /stats_types.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2012-2019 Dustin M. DeWeese 2 | 3 | This file is part of the Startle library. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | #ifndef __STATS_TYPES__ 19 | #define __STATS_TYPES__ 20 | 21 | #define COUNTER__ITEM(name, _) long long int name; 22 | #define COUNTER_MAX__ITEM(name, _) long long int name; 23 | typedef struct stats_counter { 24 | #include "counter_list.h" 25 | } stats_counter; 26 | #undef COUNTER_MAX__ITEM 27 | #undef COUNTER__ITEM 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /types.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2012-2018 Dustin M. DeWeese 2 | 3 | This file is part of the Startle library. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | #ifndef __STARTLE_TYPES__ 19 | #define __STARTLE_TYPES__ 20 | 21 | /** @file 22 | * @brief Types used throughout Startle 23 | */ 24 | 25 | /** A pair of things */ 26 | typedef struct pair_t { 27 | uintptr_t first, second; 28 | } pair_t; 29 | 30 | /** A string segment */ 31 | typedef struct seg_t { 32 | const char *s; /**< pointer to the first character */ 33 | size_t n; /**< length in bytes */ 34 | } seg_t; 35 | 36 | #define UNUSED __attribute__((unused)) 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Startle Library 2 | =============== 3 | 4 | [Startle](https://github.com/HackerFoo/startle) is a library of useful and efficient algorithms and facilities for developing C programs. 5 | 6 | Library features include: 7 | 8 | * Low overhead in-memory structured logging: deferred string formatting, indented with context, stored to a ring buffer, minimal overhead allowing use in release builds 9 | * Macros: for common tasks such as iteration, bit/flag operations, intrusive lists 10 | * Dispatch macros: Simulate overloaded functions for variable arguments 11 | * Error handling 12 | * Algorithms: Quicksort, binary search, string segments, simple mmap'ing, integer log, hash set 13 | * Zero space overhead map: O(log(n)) insertion, O(log(n)^2) lookup, O(log(n)) in-place conversion to a sorted array for O(log(n)) lookup afterwards. 14 | 15 | Makefile features include: 16 | 17 | * Automatic dependency handling 18 | * Integration with [`makeheaders`](https://www.hwaci.com/sw/mkhdr) to automatically generate and update headers for .c files 19 | * Simple code generation: Collect macro-like annotations into lists for x-macros 20 | - Used for: Unit tests, handling commands and flags 21 | 22 | Documentation can be found [here](https://hackerfoo.github.io/startle-docs/). 23 | 24 | See the [example program](https://github.com/HackerFoo/startle_example) to get started. 25 | 26 | Startle was originally developed as part of [PoprC](https://github.com/HackerFoo/poprc). 27 | -------------------------------------------------------------------------------- /gen.mk: -------------------------------------------------------------------------------- 1 | # subset of Makefile used to generate headers on demand 2 | 3 | -include $(OBJS:.o=.d) 4 | 5 | SHELL := bash 6 | 7 | # collects NAME(...) macros into a sorted list of NAME__ITEM(...) in .gen/name_list.h 8 | # NAME can optionally be followed by more uppercase letters: NAME_MORE_WORDS(...) 9 | # which adds NAME_MORE_WORDS__ITEM(...) to the list 10 | # The list will be sorted on everything after the first '(' 11 | .gen/%_list.h.new: NAME=$(shell echo $(notdir $*) | tr a-z A-Z) 12 | .gen/%_list.h.new: $(SRC) 13 | @mkdir -p $(dir $@) 14 | sed -E -n -e 's/^ *'"$(NAME)"'(_[A-Z][A-Z_]*)?\((.*)\).*/'"$(NAME)\1__ITEM"'( \2 )/p' $(SRC) | sed -e 's/,/ , /g' | LC_ALL=C sort -u -k 2,2 > $@ 15 | 16 | # store the current git commit 17 | .gen/git_log.h.new: LOG = $(shell git log -1 --oneline) 18 | .gen/git_log.h.new: $(SRC) 19 | @mkdir -p $(dir $@) 20 | @if git diff-index --quiet HEAD --; then \ 21 | echo "#define GIT_LOG \"$(LOG)\"" > $@; \ 22 | else \ 23 | echo "#define GIT_LOG \"$(LOG) [DIRTY]\"" > $@; \ 24 | fi 25 | 26 | .gen/%-local.h.new: %.c startle/bin/makeheaders 27 | @mkdir -p $(dir $@) 28 | startle/bin/makeheaders -local $<:$@ 29 | 30 | .gen/%.h.new: %.c startle/bin/makeheaders 31 | @mkdir -p $(dir $@) 32 | startle/bin/makeheaders $<:$@ 33 | 34 | .gen/%.h: .gen/%.h.new $(SRC) 35 | @cmp -s $< $@ || cp $< $@ 36 | 37 | startle/bin/makeheaders: startle/makeheaders/makeheaders.c 38 | @mkdir -p startle/bin 39 | $(CC) -O -w startle/makeheaders/makeheaders.c -o startle/bin/makeheaders 40 | -------------------------------------------------------------------------------- /stats.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2012-2019 Dustin M. DeWeese 2 | 3 | This file is part of the Startle library. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "startle/types.h" 25 | #include "startle/macros.h" 26 | #include "startle/error.h" 27 | #include "startle/log.h" 28 | #include "startle/support.h" 29 | #include "startle/stats_types.h" 30 | 31 | #if INTERFACE 32 | #if STATS 33 | #define COUNTER(name, n) (__stats_counter.name += (n)) 34 | #define COUNTER_MAX(name, n) (__stats_counter.name = max(__stats_counter.name, (n))) 35 | #define GET_COUNTER(name) (__stats_counter.name) 36 | #define TAKE(name) \ 37 | ({ \ 38 | long long int tmp = __stats_counter.name; \ 39 | __stats_counter.name = 0; \ 40 | tmp; \ 41 | }) 42 | #else 43 | #define COUNTER(name, n) 44 | #define COUNTER_MAX(name, n) 45 | #define GET_COUNTER(name) (0) 46 | #define TAKE(name) (0) 47 | #endif 48 | #endif 49 | 50 | #if STATS 51 | stats_counter __stats_counter; 52 | #endif 53 | 54 | void stats_reset_counters() { 55 | #if STATS 56 | zero(__stats_counter); 57 | #endif 58 | } 59 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2012-2018 Dustin M. DeWeese 2 | 3 | This file is part of the Startle library. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "startle/types.h" 24 | #include "startle/macros.h" 25 | #include "startle/support.h" 26 | #include "startle/error.h" 27 | #include "startle/test.h" 28 | #include "startle/log.h" 29 | 30 | /** @file 31 | * @brief Unit testing 32 | */ 33 | 34 | typedef struct test_entry { 35 | char *name; 36 | int (*run)(); 37 | } test_entry_t; 38 | 39 | #define TEST__ITEM(name) extern int test_##name(); 40 | #include "test_list.h" 41 | #undef TEST__ITEM 42 | 43 | #define TEST__ITEM(_name) \ 44 | { \ 45 | .name = #_name, \ 46 | .run = &test_##_name \ 47 | }, 48 | 49 | static test_entry_t tests[] = { 50 | #include "test_list.h" 51 | }; 52 | 53 | #undef TEST__ITEM 54 | 55 | /** Run all tests matching the name. */ 56 | int run_test(seg_t name) { 57 | int fail = 0; 58 | FOREACH(i, tests) { 59 | test_entry_t *entry = &tests[i]; 60 | if(strncmp(entry->name, name.s, name.n) == 0) { 61 | printf("@ %s\n", entry->name); 62 | int result = entry->run(); 63 | printf("%s => %d\n", entry->name, result); 64 | if(result && !fail) fail = result; 65 | } 66 | } 67 | return fail; 68 | } 69 | 70 | // Macro tests 71 | 72 | TEST(loops) { 73 | /** [loops] */ 74 | COUNTUP(i, 3) { 75 | printf("up: %d\n", (unsigned int)i); 76 | } 77 | COUNTDOWN(i, 3) { 78 | printf("down: %d\n", (unsigned int)i); 79 | } 80 | COUNTUP(i, 0) { 81 | printf("down: shouldn't print this\n"); 82 | } 83 | COUNTDOWN(i, 0) { 84 | printf("down: shouldn't print this\n"); 85 | } 86 | 87 | unsigned int arr[] = {1, 4, 9}; 88 | FOREACH(i, arr) { 89 | printf("arr[%d] = %d\n", (unsigned int)i, arr[i]); 90 | } 91 | LOOP(3) { 92 | LOOP(3) { 93 | putchar('x'); 94 | } 95 | putchar('\n'); 96 | } 97 | 98 | RANGEUP(i, 3, 7) { 99 | printf("range up: i = %d, REVI(i) = %d\n", (int)i, (int)REVI(i)); 100 | } 101 | RANGEDOWN(i, 3, 7) { 102 | printf("range down: i = %d, REVI(i) = %d\n", (int)i, (int)REVI(i)); 103 | } 104 | /** [loops] */ 105 | return 0; 106 | } 107 | 108 | TEST(formask) { 109 | unsigned int mask = 0x11af; 110 | unsigned int prev_mask = (mask << 1) + 1; 111 | FORMASK(i, j, 0x11af) { 112 | printf("%d, %d\n", (int)i, (int)j); 113 | if((prev_mask - (__mask << (__z + 1))) != 1) return -1; 114 | prev_mask = __mask; 115 | } 116 | return 0; 117 | } 118 | 119 | TEST(next_bit) { 120 | uintptr_t mask = 0x11af, m = mask; 121 | while(m) { 122 | int x = next_bit(&m); 123 | if(x < 0) return -1; 124 | mask &= ~(1 << x); 125 | printf("bit = %d\n", x); 126 | } 127 | return mask ? -2 : 0; 128 | } 129 | 130 | /** [macro_dispatch] */ 131 | 132 | #define TEST_0() printf("TEST_0()\n") 133 | #define TEST_1(x0) printf("TEST_1(" x0 ")\n") 134 | #define TEST_2(x0, x1) printf("TEST_2(" x0 ", " x1 ")\n") 135 | 136 | TEST(macro_dispatch) { 137 | DISPATCH(TEST); 138 | DISPATCH(TEST, "1"); 139 | DISPATCH(TEST, "1", "2"); 140 | return 0; 141 | } 142 | 143 | /** [macro_dispatch] */ 144 | 145 | TEST(inrange) { 146 | if(!INRANGE(1, -3, 3)) return -1; 147 | if(!INRANGE(12, -3, 3, 10, 20)) return -2; 148 | if(!INRANGE(42, -3, 3, 10, 20, 40, 100)) return -3; 149 | return 0; 150 | } 151 | 152 | TEST(oneof) { 153 | if(!ONEOF(1, 1)) return -1; 154 | if(!ONEOF(2, 1, 2)) return -2; 155 | if(!ONEOF(3, 1, 2, 3)) return -3; 156 | return 0; 157 | } 158 | 159 | static int shadow_x = 1; 160 | TEST(shadow) { 161 | if(shadow_x != 1) return -1; 162 | SHADOW(shadow_x, 2) { 163 | if(shadow_x != 2) return -2; 164 | SHADOW(shadow_x, 3) { 165 | if(shadow_x != 3) return -3; 166 | } 167 | if(shadow_x != 2) return -4; 168 | } 169 | if(shadow_x != 1) return -5; 170 | return 0; 171 | } 172 | 173 | TEST(macro_math) { 174 | if(min(3, 4) != 3) return -1; 175 | if(max(3, 4) != 4) return -2; 176 | if(DIV_UP(4, 3) != 2) return -3; 177 | if(DIV_UP(3, 3) != 1) return -4; 178 | if(csub(3, 4) != 0) return -5; 179 | if(csub(4, 3) != 1) return -6; 180 | if(SNAP_UP(1, 3) != 3) return -7; 181 | if(SNAP_UP(3, 3) != 3) return -8; 182 | return 0; 183 | } 184 | -------------------------------------------------------------------------------- /dispatch.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2012-2018 Dustin M. DeWeese 2 | 3 | This file is part of the Startle library. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | #ifndef __DISPATCH__ 19 | #define __DISPATCH__ 20 | 21 | /** @file 22 | * @brief Function dispatch for variable arguments 23 | */ 24 | 25 | // GET ________________________________________________________________________________ 26 | 27 | /** Accessor macro for simulated tuples. 28 | * GET(N, (x0, x1, ..., xN)) returns xN 29 | */ 30 | #define GET(n, t) CONCAT(GET, n) t 31 | #define GET0(x0, ...) x0 32 | #define GET1(x0, x1, ...) x1 33 | #define GET2(x0, x1, x2, ...) x2 34 | #define GET3(x0, x1, x2, x3, ...) x3 35 | #define GET4(x0, x1, x2, x3, x4, ...) x4 36 | #define GET5(x0, x1, x2, x3, x4, x5, ...) x5 37 | #define GET6(x0, x1, x2, x3, x4, x5, x6, ...) x6 38 | #define GET7(x0, x1, x2, x3, x4, x5, x6, x7, ...) x7 39 | #define GET8(x0, x1, x2, x3, x4, x5, x6, x7, x8, ...) x8 40 | #define GET20(x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, ...) x20 41 | 42 | // DISPATCH ________________________________________________________________________________ 43 | 44 | /** Return the number of arguments */ 45 | #define ARG_COUNT(...) GET20(_X, ##__VA_ARGS__, _19, _18, _17, _16, _15, _14, _13, _12, _11, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, _0) 46 | 47 | /** macro to allow handling optional macro arguments 48 | * DISPATCH(MACRO_NAME, ...) calls MACRO_NAME_N(...) where N is the number of arguments other than MACRO_NAME. 49 | * The typical use is: 50 | * #define MACRO_NAME(...) DISPATCH(MACRO_NAME, __VA_ARGS__) 51 | * to define an overloaded macro that takes a variable number of arguments 52 | * @snippet test.c macro_dispatch 53 | */ 54 | #define DISPATCH(m, ...) CONCAT(m, ARG_COUNT(__VA_ARGS__))(__VA_ARGS__) 55 | 56 | // DISPATCH cannot be nested, so this is a workaround 57 | #define DISPATCH2(m, ...) CONCAT(m, ARG_COUNT(__VA_ARGS__))(__VA_ARGS__) 58 | #define DISPATCH3(m, ...) CONCAT(m, ARG_COUNT(__VA_ARGS__))(__VA_ARGS__) 59 | 60 | // FORARG ________________________________________________________________________________ 61 | 62 | #define FORARG_2(name, x0) \ 63 | CONCAT(name, _pre) \ 64 | CONCAT(name, _only)(GET(0, CONCAT(name, _args)), x0) \ 65 | CONCAT(name, _post) 66 | #define FORARG_3(name, x0, x1) \ 67 | CONCAT(name, _pre) \ 68 | CONCAT(name, _first)(GET(1, CONCAT(name, _args)), x0) \ 69 | CONCAT(name, _last)(x1) \ 70 | CONCAT(name, _post) 71 | #define FORARG_4(name, x0, x1, x2) \ 72 | CONCAT(name, _pre) \ 73 | CONCAT(name, _first)(GET(2, CONCAT(name, _args)), x0) \ 74 | CONCAT(name, _middle)(x1) \ 75 | CONCAT(name, _last)(x2) \ 76 | CONCAT(name, _post) 77 | #define FORARG_5(name, x0, x1, x2, x3) \ 78 | CONCAT(name, _pre) \ 79 | CONCAT(name, _first)(GET(3, CONCAT(name, _args)), x0) \ 80 | CONCAT(name, _middle)(x1) \ 81 | CONCAT(name, _middle)(x2) \ 82 | CONCAT(name, _last)(x3) \ 83 | CONCAT(name, _post) 84 | #define FORARG_6(name, x0, x1, x2, x3, x4) \ 85 | CONCAT(name, _pre) \ 86 | CONCAT(name, _first)(GET(4, CONCAT(name, _args)), x0) \ 87 | CONCAT(name, _middle)(x1) \ 88 | CONCAT(name, _middle)(x2) \ 89 | CONCAT(name, _middle)(x3) \ 90 | CONCAT(name, _last)(x4) \ 91 | CONCAT(name, _post) 92 | #define FORARG_7(name, x0, x1, x2, x3, x4, x5) \ 93 | CONCAT(name, _pre) \ 94 | CONCAT(name, _first)(GET(5, CONCAT(name, _args)), x0) \ 95 | CONCAT(name, _middle)(x1) \ 96 | CONCAT(name, _middle)(x2) \ 97 | CONCAT(name, _middle)(x3) \ 98 | CONCAT(name, _middle)(x4) \ 99 | CONCAT(name, _last)(x5) \ 100 | CONCAT(name, _post) 101 | #define FORARG_8(name, x0, x1, x2, x3, x4, x5, x6) \ 102 | CONCAT(name, _pre) \ 103 | CONCAT(name, _first)(GET(6, CONCAT(name, _args)), x0) \ 104 | CONCAT(name, _middle)(x1) \ 105 | CONCAT(name, _middle)(x2) \ 106 | CONCAT(name, _middle)(x3) \ 107 | CONCAT(name, _middle)(x4) \ 108 | CONCAT(name, _middle)(x5) \ 109 | CONCAT(name, _last)(x6) \ 110 | CONCAT(name, _post) 111 | #define FORARG_9(name, x0, x1, x2, x3, x4, x5, x6, x7) \ 112 | CONCAT(name, _pre) \ 113 | CONCAT(name, _first)(GET(7, CONCAT(name, _args)), x0) \ 114 | CONCAT(name, _middle)(x1) \ 115 | CONCAT(name, _middle)(x2) \ 116 | CONCAT(name, _middle)(x3) \ 117 | CONCAT(name, _middle)(x4) \ 118 | CONCAT(name, _middle)(x5) \ 119 | CONCAT(name, _middle)(x6) \ 120 | CONCAT(name, _last)(x7) \ 121 | CONCAT(name, _post) 122 | #define FORARG_10(name, x0, x1, x2, x3, x4, x5, x6, x7, x8) \ 123 | CONCAT(name, _pre) \ 124 | CONCAT(name, _first)(GET(8, CONCAT(name, _args)), x0) \ 125 | CONCAT(name, _middle)(x1) \ 126 | CONCAT(name, _middle)(x2) \ 127 | CONCAT(name, _middle)(x3) \ 128 | CONCAT(name, _middle)(x4) \ 129 | CONCAT(name, _middle)(x5) \ 130 | CONCAT(name, _middle)(x6) \ 131 | CONCAT(name, _middle)(x7) \ 132 | CONCAT(name, _last)(x8) \ 133 | CONCAT(name, _post) 134 | 135 | /** Create a macro that iterates over its arguments. 136 | * FORARG is a flexible iteration macro that can expand its 137 | * arguments into repeated actions. 138 | * 139 | * FORARG(NAME, ...) where 140 | * #define NAME_pre 141 | * expanded before FORARG(NAME, ...) 142 | * #define NAME_first(s, x) 143 | * s is selected from NAME_args based on the number of arguments 144 | * x is the first argument 145 | * #define NAME_middle(x) 146 | * x is an argument that is neither first nor last 147 | * #define NAME_last(x) 148 | * x is the last argument 149 | * #define NAME_only(x) 150 | * x is the only argument 151 | * #define NAME_post 152 | * expanded after FORARG(NAME, ...) 153 | * #define NAME_args (x0, x1, ..., xN) 154 | * used in NAME_first for `s` 155 | */ 156 | #define FORARG(name, ...) DISPATCH(FORARG, name, __VA_ARGS__) 157 | 158 | #define DUMMY_args (x, x, x, x, x, x, x, x, x) 159 | 160 | // drops the first argument, useful with __VA_ARGS__ 161 | #define DROP_pre 162 | #define DROP_first(s, x) 163 | #define DROP_middle(x) ,x 164 | #define DROP_last(x) ,x 165 | #define DROP_only(s, x) 166 | #define DROP_post 167 | #define DROP_args DUMMY_args 168 | #define DROP(...) FORARG(DROP, __VA_ARGS__) 169 | 170 | #endif 171 | -------------------------------------------------------------------------------- /error.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2012-2018 Dustin M. DeWeese 2 | 3 | This file is part of the Startle library. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "startle/types.h" 29 | #include "startle/macros.h" 30 | #include "startle/error.h" 31 | #include "startle/log.h" 32 | 33 | static bool breakpoint_disabled = false; 34 | 35 | #if INTERFACE 36 | #include 37 | 38 | /** @file 39 | * @brief Error handling 40 | */ 41 | 42 | typedef enum error_type_e { 43 | ERROR_TYPE_NONE = 0, 44 | ERROR_TYPE_UNEXPECTED = 1, /**< An assumption was violated, indicating incorrect operation. */ 45 | ERROR_TYPE_LIMITATION = 2 /**< A known limitation has been reached. */ 46 | } error_type_t; 47 | 48 | /** Used to throw and catch errors. */ 49 | typedef struct { 50 | jmp_buf env; 51 | error_type_t type; 52 | bool quiet; 53 | } error_t; 54 | 55 | #define assert_msg(...) DISPATCH(assert_msg, ##__VA_ARGS__) 56 | #define assert_msg_1(cond, ...) "Assertion `" #cond "' failed." 57 | #define assert_msg_2(cond, fmt, ...) "Assertion `" #cond "' failed: " fmt 58 | #define assert_msg_3(cond, fmt, ...) assert_msg_2(cond, fmt) 59 | #define assert_msg_4(cond, fmt, ...) assert_msg_2(cond, fmt) 60 | #define assert_msg_5(cond, fmt, ...) assert_msg_2(cond, fmt) 61 | #define assert_msg_6(cond, fmt, ...) assert_msg_2(cond, fmt) 62 | #define assert_msg_7(cond, fmt, ...) assert_msg_2(cond, fmt) 63 | #define assert_msg_8(cond, fmt, ...) assert_msg_2(cond, fmt) 64 | #define assert_msg_9(cond, fmt, ...) assert_msg_2(cond, fmt) 65 | #define assert_msg_10(cond, fmt, ...) assert_msg_2(cond, fmt) 66 | 67 | /** Assert a limitation. 68 | * Throw a limitation error if the condition is violated, logging the following arguments. 69 | */ 70 | #define assert_throw(cond, ...) \ 71 | do { \ 72 | if(!(cond)) { \ 73 | throw_error(ERROR_TYPE_LIMITATION, \ 74 | assert_msg(cond, ##__VA_ARGS__), \ 75 | ##__VA_ARGS__); \ 76 | } \ 77 | } while(0) 78 | 79 | /** Assert an assumption. 80 | * Throw an unexpected error if the condition is violated, logging the following arguments. 81 | */ 82 | #ifdef NDEBUG 83 | #define assert_error(...) ((void)0) 84 | #else 85 | #define assert_error(...) _assert_error(__VA_ARGS__) 86 | #endif 87 | 88 | #define _assert_error(cond, ...) \ 89 | do { \ 90 | if(!(cond)) { \ 91 | throw_error(ERROR_TYPE_UNEXPECTED, \ 92 | assert_msg(cond, ##__VA_ARGS__), \ 93 | ##__VA_ARGS__); \ 94 | } \ 95 | } while(0) 96 | 97 | /** Run following code and then throw if the condition is violated. */ 98 | #ifdef NDEBUG 99 | #define on_assert_error(...) if(0) 100 | #else 101 | #define on_assert_error(...) _on_assert_error(__VA_ARGS__) 102 | #endif 103 | 104 | /* Some explanation: 105 | * 106 | * This: 107 | * for(; !cond; throw_error()) { 108 | * ... 109 | * } 110 | * 111 | * Is equivalent to this: 112 | * if(!cond) { 113 | * ... 114 | * throw_error(); 115 | * } 116 | * 117 | * There will be no looping since throw_error() will longjmp out at the end. 118 | */ 119 | #define _on_assert_error(cond, ...) \ 120 | for(;!(cond); \ 121 | ({throw_error(ERROR_TYPE_UNEXPECTED, \ 122 | assert_msg(cond, ##__VA_ARGS__), \ 123 | ##__VA_ARGS__);})) 124 | 125 | 126 | /** Warn when an assumption doesn't hold. 127 | * If the condition is violated, log the following arguments and print them. 128 | */ 129 | #ifdef NDEBUG 130 | #define assert_warn(...) ((void)0) 131 | #else 132 | #define assert_warn(...) _assert_warn(__VA_ARGS__) 133 | #endif 134 | 135 | #define _assert_warn(cond, ...) \ 136 | do { \ 137 | if(!(cond)) { \ 138 | LOG(MARK("WARN") " " \ 139 | assert_msg(cond, ##__VA_ARGS__) \ 140 | DROP(__VA_ARGS__)); \ 141 | breakpoint(); \ 142 | } \ 143 | } while(0) 144 | 145 | /** Throw an unexpected error after being called `n` times. 146 | * A quick way to prevent unexpected infinite/long loops. 147 | * `n` should be some really large number, because this counter 148 | * cannot be reset. 149 | */ 150 | #ifdef NDEBUG 151 | #define assert_counter(n) ((void)0) 152 | #else 153 | #define assert_counter(n) _assert_counter(n) 154 | #endif 155 | 156 | #define _assert_counter(n) \ 157 | do { \ 158 | static unsigned int *counter = NULL; \ 159 | if(!counter) counter = alloc_counter(); \ 160 | if(*counter > (n)) { \ 161 | throw_error(ERROR_TYPE_UNEXPECTED, \ 162 | "Assertion counter exhausted: %d > " #n, X, *counter); \ 163 | } \ 164 | (*counter)++; \ 165 | } while(0) 166 | 167 | #ifdef NDEBUG 168 | #define assert_op(op, x, y) ((void)0) 169 | #else 170 | #define assert_op(op, x, y) \ 171 | do { \ 172 | __typeof__(x) _x = (x); \ 173 | __typeof__(y) _y = (y); \ 174 | if(!(_x op _y)) { \ 175 | throw_error(ERROR_TYPE_UNEXPECTED, \ 176 | "Assertion `" #x " " #op " " #y \ 177 | "' failed: %d, %d", X, (int)_x, (int)_y); \ 178 | } \ 179 | } while(0) 180 | #endif 181 | 182 | #define assert_eq(x, y) assert_op(==, x, y) 183 | #define assert_neq(x, y) assert_op(!=, x, y) 184 | #define assert_lt(x, y) assert_op(<, x, y) 185 | #define assert_le(x, y) assert_op(<=, x, y) 186 | #define assert_gt(x, y) assert_op(>, x, y) 187 | #define assert_ge(x, y) assert_op(>=, x, y) 188 | 189 | /** Catch errors. 190 | * `e` is a pointer to an `error_t` that will be set after an error. 191 | * @snippet error.c error 192 | */ 193 | #define catch_error_1(e) catch_error_2(e, false) 194 | #define catch_error_2(e, q) (current_error = (e), current_error->quiet = (q), !!setjmp((e)->env)) 195 | #define catch_error(...) DISPATCH(catch_error, __VA_ARGS__) 196 | 197 | /** Throw an error of a particular type. 198 | * Returns the error type and logs the following arguments. 199 | * @snippet error.c error 200 | */ 201 | #define throw_error(type, fmt, ...) \ 202 | do { \ 203 | LOG_NO_POS(MARK("!!!") " " __FILE__ ":" STRINGIFY(__LINE__) \ 204 | ": %s: " fmt, __func__ DROP(__VA_ARGS__)); \ 205 | breakpoint(); \ 206 | return_error(type); \ 207 | } while(0) 208 | 209 | #endif 210 | 211 | error_t *current_error = NULL; 212 | 213 | /** Return the error type to `catch_error`. */ 214 | void return_error(error_type_t type) { 215 | if(current_error) { 216 | current_error->type = type; 217 | longjmp(current_error->env, type); 218 | } else { 219 | exit(-1); 220 | } 221 | } 222 | 223 | TEST(error) { 224 | error_t *prev_error = current_error; 225 | /** [error] */ 226 | error_t test_error; 227 | if(catch_error(&test_error)) { 228 | printf(NOTE("TEST") " "); 229 | print_last_log_msg(); 230 | } else { 231 | COUNTUP(i, 5) { 232 | assert_throw(i < 3, "Don't worry, it's okay."); 233 | printf("i = %d\n", (int)i); 234 | } 235 | } 236 | /** [error] */ 237 | current_error = prev_error; 238 | return 0; 239 | } 240 | 241 | /** A hook that can will be called in breakpoint() */ 242 | void __attribute__((weak)) breakpoint_hook(); 243 | void breakpoint_hook() {} 244 | 245 | /** Convenient place to set a breakpoint for debugger integration. */ 246 | void breakpoint() { 247 | if(breakpoint_disabled) return; 248 | SHADOW(breakpoint_disabled, true) { // avoid error loops 249 | if(!maybe_get(current_error, quiet, false)) { 250 | print_context(5); 251 | printf(NOTE("BREAKPOINT") " "); 252 | print_last_log_msg(); 253 | } 254 | breakpoint_hook(); 255 | } 256 | } 257 | 258 | static unsigned int counters[8] = {0}; 259 | static unsigned int counters_n = 0; 260 | 261 | unsigned int *alloc_counter() { 262 | assert_error(counters_n < LENGTH(counters)); 263 | return &counters[counters_n++]; 264 | } 265 | void reset_counters() { 266 | zero(counters); 267 | } 268 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /macros.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2012-2018 Dustin M. DeWeese 2 | 3 | This file is part of the Startle library. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | #ifndef __STARTLE_MACROS__ 19 | #define __STARTLE_MACROS__ 20 | 21 | #include "startle/dispatch.h" 22 | 23 | /** @file 24 | * @brief Generally useful macros 25 | */ 26 | 27 | // MAKE COMPILERS HAPPY ________________________________________ 28 | 29 | #ifdef __clang__ 30 | #pragma clang diagnostic ignored "-Wgnu-statement-expression" 31 | #endif 32 | 33 | 34 | 35 | // MACRO UTILITIES ________________________________________ 36 | 37 | /** concatenate tokens. */ 38 | #define _CONCAT(x, y) x##y 39 | #define CONCAT(x, y) _CONCAT(x, y) 40 | 41 | #define _CONCAT_UNDERSCORE(x, y) x##_##y 42 | #define CONCAT_UNDERSCORE(x, y) _CONCAT_UNDERSCORE(x, y) 43 | 44 | /** Make a unique identifier. */ 45 | #define UNIQUE CONCAT(__unique_, __LINE__) 46 | 47 | /** Convert a token to a string. */ 48 | #define _STRINGIFY(x) #x 49 | #define STRINGIFY(x) _STRINGIFY(x) 50 | 51 | #define LET(x, y) __typeof__(y) x = (y) 52 | 53 | 54 | // SIZES & OFFSETS ________________________________________ 55 | 56 | /** Size of a field. */ 57 | #define sizeof_field(s, f) sizeof(((s *)0)->f) 58 | 59 | /** Offset of a field. */ 60 | #if !defined(offsetof) 61 | #define offsetof(s, f) ((uintptr_t)&(((s *)0)->f)) 62 | #endif 63 | 64 | /** Number of bytes per array element. */ 65 | #define WIDTH(a) (sizeof((a)[0])) 66 | 67 | /** Number of array elements. */ 68 | #define LENGTH(a) (sizeof(a) / WIDTH(a)) 69 | 70 | 71 | 72 | // ITERATION MACROS ________________________________________ 73 | 74 | /** Iterate `i` from `lower` up to `upper-1`. */ 75 | #define RANGEUP(i, lower, upper) for(size_t i = (lower), __lower = (lower), __upper = ((void) __lower, (upper)); i < __upper; i++) 76 | 77 | /** Iterate `i` from `upper-1` down to `lower`. */ 78 | #define RANGEDOWN(i, lower, upper) for(size_t i = (upper), __upper = (upper), __lower = ((void) __upper, (lower)); i-- > __lower; ) 79 | 80 | /** Iterator over the same range in reverse order. */ 81 | #define REVI(i) (__upper - 1 - (i) + __lower) 82 | 83 | /** Iterate `i` from `n-1` to `0`. 84 | * @snippet test.c loops 85 | */ 86 | #define COUNTDOWN(i, n) RANGEDOWN(i, 0, n) 87 | 88 | /** Iterate `i` from `0` to `n-1`. */ 89 | #define COUNTUP(i, n) RANGEUP(i, 0, n) 90 | 91 | /** Iterate `n` times. */ 92 | #define LOOP(n) COUNTDOWN(UNIQUE, n) 93 | 94 | /** Iterate `i` over each index of the array `a`. */ 95 | #define FOREACH(i, a) COUNTUP(i, LENGTH(a)) 96 | 97 | /** Iterate `i` over each index of map `m`. */ 98 | #define FORMAP(i, m) for(size_t i = 1; i <= *map_cnt(m); i++) 99 | 100 | #define FORMASK(i, j, mask) \ 101 | for(uintptr_t i = 0, j = 0, __z = 0, __mask = (mask); __mask; \ 102 | __mask >>= 1, \ 103 | __z = ctz(__mask), j += __z + 1, i++, __mask >>= __z) 104 | 105 | // DATA STRUCTURES ________________________________________ 106 | 107 | /** Declare a bit set named `name` to store `size` bits. */ 108 | #define BITSET(name, size) uint8_t name[((size)+7)/8] = {0} 109 | 110 | /** Declare a bit set to hold a bit for each element of the array. */ 111 | #define BITSET_INDEX(name, array) BITSET(name, LENGTH(array)) 112 | 113 | #if !defined(static_assert) 114 | #define static_assert(expr, msg) _Static_assert(expr, msg) 115 | #endif 116 | 117 | /** String segment initializer. 118 | * Example: seg_t s = SEG("Hello"); 119 | */ 120 | #define SEG(x) {(x), sizeof(x) - 1} 121 | 122 | /** printf that prepends a string segment 123 | * @param pre print this string first 124 | * @param seg string segment to print next 125 | * @param fmt format string for remaining arguments 126 | */ 127 | #define printseg(pre, seg, fmt, ...) \ 128 | do { \ 129 | seg_t __seg = seg; \ 130 | printf(pre "%.*s" fmt, (int)__seg.n, __seg.s , ##__VA_ARGS__); \ 131 | } while(0) 132 | 133 | // building embedded lists 134 | 135 | /** Add to tail of an intrusive list. 136 | * @param f field to store link. 137 | * @param l pointer to list tail pointer to update. 138 | * @param v struct to add. 139 | */ 140 | #define LIST_ADD(f, l, v) \ 141 | ({ \ 142 | LET(__v, v); \ 143 | *(l) = __v; \ 144 | (l) = &__v->f; \ 145 | }) 146 | 147 | /** Add to head of an intrusive list. 148 | * @param f field to store link. 149 | * @param l pointer to list head pointer to update. 150 | * @param v struct to add. 151 | */ 152 | #define CONS(f, l, v) \ 153 | ({ \ 154 | LET(__l, l); \ 155 | LET(__v, v); \ 156 | __v->f = *__l; \ 157 | *__l = __v; \ 158 | }) 159 | 160 | 161 | 162 | // MATH ________________________________________ 163 | 164 | #define min(a, b) \ 165 | ({ \ 166 | LET(__a, a); \ 167 | LET(__b, b); \ 168 | __a <= __b ? __a : __b; \ 169 | }) 170 | 171 | #define max(a, b) \ 172 | ({ \ 173 | LET(__a, a); \ 174 | LET(__b, b); \ 175 | __a >= __b ? __a : __b; \ 176 | }) 177 | 178 | /** Non-negative saturating subtraction. */ 179 | #define csub(a, b) \ 180 | ({ \ 181 | LET(__a, a); \ 182 | LET(__b, b); \ 183 | __b > __a ? 0 : __a - __b; \ 184 | }) 185 | 186 | /** Integer division rounding up. */ 187 | #define DIV_UP(n, d) \ 188 | ({ \ 189 | LET(__d, d); \ 190 | (__d - 1 + (n)) / __d; \ 191 | }) 192 | 193 | #define INRANGE_3(x, lo0, hi0) \ 194 | ({ \ 195 | LET(__x, x); \ 196 | __x >= (lo0) && __x <= (hi0); \ 197 | }) 198 | 199 | #define INRANGE_5(x, lo0, hi0, lo1, hi1) \ 200 | ({ \ 201 | LET(__x, x); \ 202 | (__x >= (lo0) && __x <= (hi0)) \ 203 | || (__x >= (lo1) && __x <= (hi1)); \ 204 | }) 205 | 206 | #define INRANGE_7(x, lo0, hi0, lo1, hi1, lo2, hi2) \ 207 | ({ \ 208 | LET(__x, x); \ 209 | (__x >= (lo0) && __x <= (hi0)) \ 210 | || (__x >= (lo1) && __x <= (hi1)) \ 211 | || (__x >= (lo2) && __x <= (hi2)); \ 212 | }) 213 | 214 | #define INRANGE(...) DISPATCH(INRANGE, __VA_ARGS__) 215 | 216 | #define ONEOF_2(x, y0) \ 217 | ((x) == (y0)) 218 | 219 | #define ONEOF_3(x, y0, y1) \ 220 | ({ \ 221 | LET(__x, x); \ 222 | __x == (y0) \ 223 | || __x == (y1); \ 224 | }) 225 | 226 | #define ONEOF_4(x, y0, y1, y2) \ 227 | ({ \ 228 | LET(__x, x); \ 229 | __x == (y0) \ 230 | || __x == (y1) \ 231 | || __x == (y2); \ 232 | }) 233 | 234 | #define ONEOF_5(x, y0, y1, y2, y3) \ 235 | ({ \ 236 | LET(__x, x); \ 237 | __x == (y0) \ 238 | || __x == (y1) \ 239 | || __x == (y2) \ 240 | || __x == (y3); \ 241 | }) 242 | 243 | #define ONEOF_6(x, y0, y1, y2, y3, y4) \ 244 | ({ \ 245 | LET(__x, x); \ 246 | __x == (y0) \ 247 | || __x == (y1) \ 248 | || __x == (y2) \ 249 | || __x == (y3) \ 250 | || __x == (y4); \ 251 | }) 252 | 253 | #define ONEOF_7(x, y0, y1, y2, y3, y4, y5) \ 254 | ({ \ 255 | LET(__x, x); \ 256 | __x == (y0) \ 257 | || __x == (y1) \ 258 | || __x == (y2) \ 259 | || __x == (y3) \ 260 | || __x == (y4) \ 261 | || __x == (y5); \ 262 | }) 263 | 264 | #define ONEOF(...) DISPATCH(ONEOF, __VA_ARGS__) 265 | 266 | #define SNAP_UP(x, d) \ 267 | ({ \ 268 | LET(__x, x); \ 269 | LET(__d, d); \ 270 | LET(__m, __x % __d); \ 271 | __m ? __x + __d - __m : __x; \ 272 | }) 273 | 274 | 275 | 276 | // UM... OTHER STUFF ________________________________________ 277 | 278 | /** Zero an array or struct. */ 279 | #define zero(a) memset(&(a), 0, sizeof(a)) 280 | 281 | /** Trace an integer variable. 282 | * `show(name)` will print `name = ` 283 | */ 284 | #define show(x) printf(#x " = %d\n", (int)(x)) 285 | 286 | #define FLAG_(x, flag) (((x) & (flag)) != 0) 287 | #define FLAG_FIELD(s, t) ((s).GET(0, CONCAT(FLAG_, t)).flags) 288 | #define FLAG_BIT(flag, t) CONCAT(CONCAT(GET(1, CONCAT(FLAG_, t)), _), flag) 289 | 290 | /** Return `true` if the flag is set. */ 291 | #define FLAG(s, t, flag) FLAG_(FLAG_FIELD(s, t), FLAG_BIT(flag, t)) 292 | 293 | /** Return `true` if the flag is NOT set. */ 294 | #define NOT_FLAG(s, t, flag) (!FLAG(s, t, flag)) 295 | 296 | #define FLAG_SET_(x, flag) ((x) |= (flag)) 297 | #define FLAG_SET(s, t, flag) FLAG_SET_(FLAG_FIELD(s, t), FLAG_BIT(flag, t)) 298 | 299 | #define FLAG_CLEAR_(x, flag) ((x) &= ~(flag)) 300 | #define FLAG_CLEAR(s, t, flag) FLAG_CLEAR_(FLAG_FIELD(s, t), FLAG_BIT(flag, t)) 301 | 302 | #define FLAG_SET_TO_(x, flag, val) ((val) ? FLAG_SET_(x, flag) : FLAG_CLEAR_(x, flag)) 303 | #define FLAG_SET_TO(s, t, flag, val) FLAG_SET_TO_(FLAG_FIELD(s, t), FLAG_BIT(flag, t), val) 304 | 305 | /** Shift elements in the array to the right. */ 306 | #define ARRAY_SHIFTR(array, offset, length) memmove(&(array) + (offset), &(array), (length) * sizeof(array)) 307 | 308 | /** Shift elements in the array to the left. */ 309 | #define ARRAY_SHIFTL(array, offset, length) memmove(&(array), &(array) + (offset), (length) * sizeof(array)) 310 | 311 | #define ARRAY_COPY(dst, src, length) memcpy(&(dst), &(src), (length) * sizeof(dst)) 312 | 313 | #if !defined(EMSCRIPTEN) 314 | #define COLOR(c, str) "\x1b[" CONCAT(COLOR_, c) "m" str "\x1b[0m" 315 | #define COLORs(str) "\x1b[%sm" str "\x1b[0m" 316 | #define COLOR_red "37;41" 317 | #define COLOR_blue "37;44" 318 | #define COLOR_gray "38;5;8" 319 | #define COLOR_normal "0" 320 | #else 321 | #define COLOR(c, str) "[[;" CONCAT(COLOR_, c) ";]" str "]" 322 | #define COLORs(str) "[[;%s;]" str "]" 323 | #define COLOR_red "#fff;#f00" 324 | #define COLOR_blue "#fff;#00f" 325 | #define COLOR_gray "#999;#000" 326 | #define COLOR_normal ";" 327 | #endif 328 | 329 | #define MARK(x) COLOR(red, x) 330 | #define NOTE(x) COLOR(blue, x) 331 | #define FADE(x) COLOR(gray, x) 332 | #define TODO MARK("TODO") 333 | #define HACK MARK("HACK") 334 | 335 | #define DISABLE(...) \ 336 | do { \ 337 | LOG(MARK("DISABLED") " %s", __func__); \ 338 | return __VA_ARGS__; \ 339 | } while(0) \ 340 | 341 | /** Hint that the condition is unlikely. 342 | * if unlikely(...) { ... 343 | */ 344 | #define unlikely(c) (__builtin_expect((c), 0)) 345 | 346 | /** Hint that the condition is likely. 347 | * if likely(...) { ... 348 | */ 349 | #define likely(c) (__builtin_expect((c), 1)) 350 | 351 | /** Define a test. */ 352 | #define TEST(name) int test_##name() 353 | 354 | /** Define a new format string specifier (for logging). */ 355 | #define FORMAT(name, c) void format_##name(intptr_t i) 356 | 357 | /** Call `f` for each of the following arguments. */ 358 | #define EACH_2(f, x0) f(x0) 359 | #define EACH_3(f, x0, x1) f(x0); f(x1) 360 | #define EACH_4(f, x0, x1, x2) f(x0); f(x1); f(x2) 361 | #define EACH_5(f, x0, x1, x2) f(x0); f(x1); f(x2); f(x3) 362 | #define EACH(...) \ 363 | do { \ 364 | DISPATCH(EACH, __VA_ARGS__); \ 365 | } while(0) 366 | 367 | /** Reassign a variable for the duration of the following block. */ 368 | #define SHADOW(var, val) \ 369 | for(const __typeof__(var) __tmp = (var), \ 370 | *__tmpp = (((var) = (val)), &__tmp); \ 371 | __tmpp; \ 372 | ((var) = __tmp), __tmpp = NULL) 373 | 374 | #define maybe_get(s, f, d) \ 375 | ({ \ 376 | LET(__s, s); \ 377 | __s && __s->f ? __s->f : (d); \ 378 | }) 379 | 380 | #endif 381 | -------------------------------------------------------------------------------- /log.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2012-2018 Dustin M. DeWeese 2 | 3 | This file is part of the Startle library. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "startle/types.h" 25 | #include "startle/macros.h" 26 | #include "startle/error.h" 27 | #include "startle/log.h" 28 | #include "startle/support.h" 29 | 30 | /** @file 31 | * @brief Structured in-memory logging 32 | */ 33 | 34 | 35 | #define FORMAT__ITEM(name, c) \ 36 | void format_##name(intptr_t) __attribute__((weak)); \ 37 | void format_##name(intptr_t x) { printf("?0x%lx", x); } 38 | #include "format_list.h" 39 | #undef FORMAT__ITEM 40 | 41 | #define REVERSE 0x80 42 | #define START_CONTEXT 0x40 43 | #define END_CONTEXT '\xff' 44 | #define MASK (~(REVERSE | START_CONTEXT)) 45 | 46 | #define LOG_SIZE (1 << 12) 47 | static intptr_t log[LOG_SIZE]; 48 | static unsigned int log_head = 0; 49 | static unsigned int log_tail = 0; 50 | static unsigned int log_watch = ~0; 51 | static unsigned int log_watch_to = ~0; 52 | static intptr_t log_watch_fmt = 0; 53 | static bool set_log_watch_fmt = false; 54 | static bool watching = false; 55 | static unsigned int msg_head = 0; 56 | 57 | static bool tweak_enabled = false; 58 | static bool set_tweak_fmt = false; 59 | static char *tweak_fmt = NULL; 60 | static unsigned int tweak_trigger = ~0; 61 | static intptr_t tweak_value = 0; 62 | 63 | static uintptr_t hash_tag_set[63]; 64 | 65 | log_context_t *__log_context = NULL; 66 | 67 | /** Call this first to initialize the log. */ 68 | void log_init() { 69 | log[0] = 0; 70 | log_head = 0; 71 | log_tail = 0; 72 | __log_context = NULL; 73 | log_watch = ~0; 74 | log_watch_to = ~0; 75 | log_watch_fmt = 0; 76 | set_log_watch_fmt = false; 77 | watching = false; 78 | tweak_enabled = false; 79 | tweak_trigger = ~0; 80 | set_tweak_fmt = false; 81 | tweak_fmt = NULL; 82 | msg_head = 0; 83 | zero(hash_tag_set); 84 | } 85 | 86 | /** Set a tag to break on. 87 | * Break when this tag is reached. 88 | * @param after break on at the same point after hitting the tag. 89 | */ 90 | void set_log_watch(const tag_t tag, bool after) { 91 | log_watch = log_watch_to = read_tag(tag); 92 | set_log_watch_fmt = after; 93 | log_watch_fmt = 0; 94 | watching = false; 95 | } 96 | 97 | /** Set a range to break. 98 | * @param tag_from first tag to break on. 99 | * @param tag_to last tag to break on. 100 | */ 101 | void set_log_watch_range(const tag_t tag_from, const char *tag_to) { 102 | log_watch = read_tag(tag_from); 103 | log_watch_to = tag_to ? read_tag(tag_to) : ~0; 104 | log_watch_fmt = 0; 105 | watching = false; 106 | } 107 | 108 | /** Re-initialize the log without clearing it. */ 109 | void log_soft_init() { 110 | if(log_head != log_tail) { 111 | log_add((intptr_t)"\xff\xff"); // reset indentation 112 | } 113 | __log_context = NULL; 114 | } 115 | 116 | static 117 | uint8_t log_entry_len(unsigned int idx) { 118 | const char *fmt = (const char *)log[idx]; 119 | if(!fmt) return 0; 120 | char len = fmt[0]; 121 | if(len == END_CONTEXT) return 0; 122 | return (uint8_t)(len & MASK); 123 | } 124 | 125 | static 126 | unsigned int log_next(unsigned int i) { 127 | uint8_t len = log_entry_len(i); 128 | return (i + len + 1) % LOG_SIZE; 129 | } 130 | 131 | // not the most efficient 132 | static 133 | char *strchrnul(const char *s, int c) { 134 | char *res = strchr(s, c); 135 | return res ? res : strchr(s, 0); 136 | } 137 | 138 | static 139 | unsigned int log_printf(unsigned int idx, unsigned int *depth, bool event) { 140 | unsigned int msg_id = idx; 141 | const char *fmt = (const char *)log[idx++]; 142 | tag_t tag; 143 | //printf("%d %d %x %s\n", idx, *depth, fmt[0], fmt + 1); 144 | uint8_t len = fmt[0] & MASK; 145 | intptr_t x; 146 | const char 147 | *p = fmt + 1, 148 | *n = strpbrk(p, "%#@"); 149 | LOOP(*depth * 2) putchar(' '); 150 | if(fmt[0] & START_CONTEXT) (*depth)++; 151 | while(n) { 152 | printf("%.*s", (int)(n-p), p); // print the text 153 | if(!n[1]) break; 154 | if(n[0] != '%') { 155 | p = strchrnul(n, ' '); 156 | uintptr_t key = nonzero_hash(n+1, p-n-1); 157 | if(n[0] == '@' || set_member(key, hash_tag_set, LENGTH(hash_tag_set))) { 158 | printf(NOTE("%.*s"), (int)(p-n), n); 159 | } else { 160 | printf("%.*s", (int)(p-n), n); 161 | } 162 | } else { 163 | switch(n[1]) { 164 | #define CASE_PRINT(c, print) \ 165 | case c: \ 166 | if(len) { \ 167 | idx = idx % LOG_SIZE; \ 168 | x = log[idx++]; \ 169 | print; \ 170 | len--; \ 171 | } else { \ 172 | printf("X"); \ 173 | } \ 174 | break; 175 | #define CASE(c, cast, fmt) \ 176 | CASE_PRINT(c, printf(fmt, cast(x))) 177 | #define FORMAT__ITEM(name, c) CASE_PRINT(c, format_##name(x)) 178 | #include "format_list.h" 179 | #undef FORMAT__ITEM 180 | CASE('d', (int), "%d"); 181 | CASE('u', (unsigned int), "%u"); 182 | CASE('x', (int), "%x"); 183 | CASE('s', (const char *), "%s"); 184 | CASE('p', (void *), "%p"); 185 | #undef CASE 186 | #undef CASE_PRINT 187 | case '.': 188 | if(n[2] == '*' && n[3] == 's') { 189 | if(len > 1) { 190 | idx = idx % LOG_SIZE; 191 | int size = log[idx++]; 192 | idx = idx % LOG_SIZE; 193 | printf("%.*s", size, (const char *)log[idx++]); 194 | len -= 2; 195 | } else { 196 | printf("X"); 197 | } 198 | n += 2; 199 | break; 200 | } 201 | case '%': 202 | printf("%%"); 203 | break; 204 | default: 205 | printf("!?"); 206 | break; 207 | } 208 | p = n + 2; 209 | } 210 | n = strpbrk(p, "%#@"); 211 | } 212 | idx = idx % LOG_SIZE; 213 | if(event) { 214 | write_tag(tag, msg_id); 215 | printf("%s " FADE(FORMAT_TAG) "\n", p, tag); 216 | } else { 217 | printf("%s\n", p); 218 | } 219 | return idx; 220 | } 221 | 222 | void log_add(intptr_t x) { 223 | log[log_head] = x; 224 | log_head = (log_head + 1) % LOG_SIZE; 225 | if(log_head == log_tail) { 226 | unsigned int len = log_entry_len(log_tail); 227 | log_tail = (log_tail + 1 + len) % LOG_SIZE; 228 | } 229 | } 230 | 231 | void log_add_first(intptr_t x) { 232 | msg_head = log_head; 233 | log_add(x); 234 | } 235 | 236 | bool log_add_last(intptr_t x) { 237 | log_add(x); 238 | if unlikely(msg_head == log_watch || 239 | (watching && 240 | (!set_log_watch_fmt || 241 | log[msg_head] == log_watch_fmt))) { 242 | watching = true; 243 | if(set_log_watch_fmt) { 244 | log_watch_fmt = log[msg_head]; 245 | } else if(msg_head == log_watch_to) { 246 | watching = false; 247 | } 248 | return true; 249 | } 250 | return false; 251 | } 252 | 253 | bool log_add_only(intptr_t x) { 254 | msg_head = log_head; 255 | return log_add_last(x); 256 | } 257 | 258 | unsigned int log_depth() { 259 | unsigned int 260 | i = log_tail, 261 | depth = 0; 262 | while(i != log_head) { 263 | const char *fmt = (const char *)log[i]; 264 | if(*fmt == END_CONTEXT) { 265 | if(fmt[1] == END_CONTEXT) { // log break 266 | depth = 0; 267 | } else if(depth) { 268 | depth--; 269 | } 270 | i = (i + 1) % LOG_SIZE; 271 | } else { 272 | if(*fmt & START_CONTEXT) { 273 | depth++; 274 | } 275 | uint8_t len = *fmt & MASK; 276 | i = (i + len + 1) % LOG_SIZE; 277 | } 278 | } 279 | return depth; 280 | } 281 | 282 | #ifdef DEBUG 283 | static 284 | void print_ints(unsigned int *ar, unsigned int n, unsigned int x) { 285 | COUNTUP(i, n) { 286 | if(i == x) { 287 | printf("[%d] ", ar[i]); 288 | } else { 289 | printf(" %d ", ar[i]); 290 | } 291 | } 292 | printf("\n"); 293 | } 294 | #else 295 | #define print_ints(...) ((void)0) 296 | #endif 297 | 298 | static 299 | unsigned int count_reversed(unsigned int start) { 300 | unsigned int 301 | i = start, 302 | cnt = 0; 303 | while(i != log_head) { 304 | const char *fmt = (const char *)log[i]; 305 | if(!(*fmt & REVERSE)) break; 306 | i = log_next(i); 307 | cnt++; 308 | } 309 | return cnt; 310 | } 311 | 312 | unsigned int log_find_context(unsigned int *ca, unsigned int size) { 313 | unsigned int 314 | i = log_tail, 315 | depth = 0, 316 | current_depth = log_depth(), 317 | low = current_depth > size ? current_depth - size : 0, 318 | high = low + size - 1; 319 | memset(ca, 0, sizeof(ca[0]) * size); 320 | while(i != log_head) { 321 | const char *fmt = (const char *)log[i]; 322 | if(*fmt == END_CONTEXT) { 323 | if(fmt[1] == END_CONTEXT) { // log break 324 | depth = 0; 325 | } else if(depth) { 326 | depth--; 327 | } 328 | } else { 329 | if(*fmt & START_CONTEXT) { 330 | if(*fmt & REVERSE) { 331 | unsigned int cnt = count_reversed(i); 332 | COUNTDOWN(j, cnt) { 333 | unsigned int depth_r = depth + j; 334 | if(INRANGE(depth_r, low, high)) { 335 | unsigned int d = depth_r - low; 336 | ca[d] = i; 337 | print_ints(ca, size, d); 338 | } 339 | i = log_next(i); 340 | } 341 | depth += cnt; 342 | continue; 343 | } else { 344 | if(INRANGE(depth, low, high)) { 345 | unsigned int d = depth - low; 346 | ca[d] = i; 347 | print_ints(ca, size, d); 348 | } 349 | depth++; 350 | } 351 | } 352 | } 353 | i = log_next(i); 354 | } 355 | return min(size, current_depth); 356 | } 357 | 358 | void print_context(size_t s) { 359 | unsigned int ca[s]; 360 | unsigned int n = log_find_context(ca, s); 361 | COUNTUP(i, n) { 362 | unsigned int depth = i; 363 | #ifdef DEBUG 364 | printf("%d ", ca[i]); 365 | #endif 366 | log_printf(ca[i], &depth, false); 367 | } 368 | } 369 | 370 | void print_last_log_msg() { 371 | unsigned int depth = 0; 372 | log_printf(msg_head, &depth, true); 373 | } 374 | 375 | static 376 | bool end_context(unsigned int idx, unsigned int *depth) { 377 | const char *fmt = (const char *)log[idx]; 378 | // TODO match the ends so that dropping entries doesn't break indentation 379 | if(fmt[0] != END_CONTEXT) return false; 380 | if(fmt[1] == END_CONTEXT) { 381 | *depth = 0; 382 | putchar('\n'); 383 | } else if(*depth > 0) { 384 | (*depth)--; 385 | } 386 | return true; 387 | } 388 | 389 | static 390 | unsigned int print_contexts(unsigned int idx, unsigned int *depth) { 391 | if(idx == log_head) return idx; 392 | const char *fmt = (const char *)log[idx]; 393 | if(fmt[0] == END_CONTEXT || 394 | (fmt[0] & REVERSE) == 0) return idx; 395 | uint8_t len = (fmt[0] & MASK) + 1; 396 | unsigned int ret = print_contexts((idx + len) % LOG_SIZE, depth); 397 | log_printf(idx, depth, false); 398 | return ret; 399 | } 400 | 401 | /** Print all stored log entries. */ 402 | void log_print_all() { 403 | log_scan_tags(); 404 | unsigned int 405 | depth = 0, 406 | i = log_tail; 407 | while(i != log_head) { 408 | i = print_contexts(i, &depth); 409 | if(i == log_head) break; 410 | if(end_context(i, &depth)) { 411 | i = (i + 1) % LOG_SIZE; 412 | } else { 413 | i = log_printf(i, &depth, true); 414 | } 415 | } 416 | } 417 | 418 | void log_scan_tags() { 419 | zero(hash_tag_set); 420 | unsigned int i = log_tail; 421 | while(i != log_head) { 422 | const char *fmt = (const char *)log[i]; 423 | if(*fmt == END_CONTEXT) { 424 | i = (i + 1) % LOG_SIZE; 425 | continue; 426 | } 427 | uint8_t len = *fmt & MASK; 428 | i = (i + len + 1) % LOG_SIZE; 429 | const char *p = fmt; 430 | while((p = strchr(p, '@'))) { 431 | p++; 432 | const char *e = strchrnul(p, ' '); 433 | if(p == e) continue; 434 | uintptr_t key = nonzero_hash(p, e-p); 435 | set_insert(key, hash_tag_set, LENGTH(hash_tag_set)); 436 | p = e; 437 | } 438 | } 439 | } 440 | 441 | #if INTERFACE 442 | #define LOG_pre \ 443 | do { \ 444 | log_add_context(); 445 | #define LOG_first(s, fmt) log_add_first((intptr_t)(s fmt)); 446 | #define LOG_middle(x) log_add((intptr_t)(x)); 447 | #define LOG_last(x) if(log_add_last((intptr_t)(x))) breakpoint(); 448 | #define LOG_only(s, fmt) if(log_add_only((intptr_t)(s fmt))) breakpoint(); 449 | #define LOG_post \ 450 | } while(0) 451 | #define LOG_args ("\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08") 452 | #define LOG_NO_POS(...) FORARG(LOG, __VA_ARGS__) 453 | 454 | /** Log the format string and arguments. 455 | * @snippet log.c log 456 | */ 457 | #define LOG(fmt, ...) LOG_NO_POS(__FILE__ ":" STRINGIFY(__LINE__) ": " fmt, ##__VA_ARGS__) 458 | 459 | /** Log when `test` is true. */ 460 | #define LOG_WHEN(test, fmt, ...) ((test) && (({ LOG(fmt, ##__VA_ARGS__); }), true)) 461 | 462 | /** Log unless `test` is true. */ 463 | #define LOG_UNLESS(test, fmt, ...) ((test) || (({ LOG(fmt, ##__VA_ARGS__); }), false)) 464 | 465 | // same as LOG, but don't call log_add_{last, only} to avoid calling breakpoint() 466 | #define LOG_NOBREAK_pre \ 467 | do { \ 468 | log_add_context(); 469 | #define LOG_NOBREAK_first(s, fmt) log_add_first((intptr_t)(s fmt)); 470 | #define LOG_NOBREAK_middle(x) log_add((intptr_t)(x)); 471 | #define LOG_NOBREAK_last(x) log_add_last((intptr_t)(x)); 472 | #define LOG_NOBREAK_only(s, fmt) log_add_only((intptr_t)(s fmt)); 473 | #define LOG_NOBREAK_post \ 474 | } while(0) 475 | #define LOG_NOBREAK_args LOG_args 476 | #define LOG_NOBREAK(fmt, ...) FORARG(LOG_NOBREAK, __FILE__ ":" STRINGIFY(__LINE__) ": " fmt, ##__VA_ARGS__) 477 | 478 | #endif 479 | 480 | TEST(log) { 481 | /** [log] */ 482 | log_init(); 483 | LOG("test %d + %d = %d", 1, 2, 3); 484 | LOG("WAZZUP %s", "d00d"); 485 | LOG("[%.*s]", 3, "12345"); 486 | log_print_all(); 487 | /** [log] */ 488 | return 0; 489 | } 490 | 491 | #if INTERFACE 492 | typedef struct log_context log_context_t; 493 | struct log_context { 494 | struct log_context *next; 495 | char const *fmt; 496 | intptr_t arg[0]; 497 | }; 498 | 499 | #define CONTEXT_pre \ 500 | __attribute__((cleanup(log_cleanup_context))) \ 501 | intptr_t __context[] = { \ 502 | (intptr_t)__log_context, 503 | #define CONTEXT_first(s, fmt) (intptr_t)(s fmt) + 1, 504 | #define CONTEXT_middle(x) (intptr_t)(x), 505 | #define CONTEXT_last(x) (intptr_t)(x)}; 506 | #define CONTEXT_only(s, fmt) (intptr_t)(s fmt) + 1}; 507 | #define CONTEXT_post \ 508 | __log_context = (log_context_t *)__context; 509 | #define CONTEXT_args ("\xff\xc0", "\xff\xc1", "\xff\xc2", "\xff\xc3", "\xff\xc4", "\xff\xc5", "\xff\xc6", "\xff\xc7", "\xff\xc8") 510 | 511 | /** Store log context. 512 | * Context is logged if a message is logged within the context's scope. 513 | * This allows for more non-local information to be logged about an event. 514 | * @snippet log.c context 515 | */ 516 | #define CONTEXT(fmt, ...) FORARG(CONTEXT, __FILE__ ":" STRINGIFY(__LINE__) ": " fmt, ##__VA_ARGS__) 517 | #endif 518 | 519 | #if INTERFACE 520 | #define CONTEXT_LOG_pre \ 521 | const char *__context __attribute__((cleanup(log_cleanup_context_log))); \ 522 | do { \ 523 | log_add_context(); 524 | #define CONTEXT_LOG_first(s, fmt) \ 525 | __context = s fmt; \ 526 | log_add_first((intptr_t)(__context + 1)); 527 | #define CONTEXT_LOG_middle(x) \ 528 | log_add((intptr_t)(x)); 529 | #define CONTEXT_LOG_last(x) \ 530 | if(log_add_last((intptr_t)(x))) breakpoint(); 531 | #define CONTEXT_LOG_only(s, fmt) \ 532 | __context = s fmt; \ 533 | if(log_add_only((intptr_t)(__context + 1))) breakpoint(); 534 | #define CONTEXT_LOG_post \ 535 | } while(0) 536 | 537 | /** Store context to log. 538 | * Like CONTEXT, but always logged. 539 | */ 540 | #define CONTEXT_LOG_args ("\xff\x40", "\xff\x41", "\xff\x42", "\xff\x43", "\xff\x44", "\xff\x45", "\xff\x46", "\xff\x47", "\xff\x48") 541 | #define CONTEXT_LOG(fmt, ...) FORARG(CONTEXT_LOG, __FILE__ ":" STRINGIFY(__LINE__) ": " fmt, ##__VA_ARGS__) 542 | #endif 543 | 544 | void log_cleanup_context(void *p) { 545 | log_context_t *ctx = p; 546 | if(ctx->fmt[0] == END_CONTEXT) { 547 | // add end marker 548 | log_add((intptr_t)ctx->fmt); 549 | } 550 | __log_context = ctx->next; 551 | } 552 | 553 | void log_cleanup_context_log(const char **fmt) { 554 | log_add((intptr_t)*fmt); 555 | } 556 | 557 | void log_add_context() { 558 | log_context_t *p = __log_context; 559 | while(p && 560 | p->fmt[0] != END_CONTEXT) { 561 | log_add((intptr_t)p->fmt); 562 | uint8_t len = p->fmt[0] & MASK; 563 | COUNTUP(i, len) { 564 | log_add(p->arg[i]); 565 | } 566 | p->fmt--; 567 | p = p->next; 568 | } 569 | } 570 | 571 | /** [log_context] */ 572 | static 573 | void __test_context_c(int x) { 574 | CONTEXT_LOG("C %d", x); 575 | } 576 | 577 | static 578 | void __test_context_b(int x) { 579 | CONTEXT("B %d", x); 580 | LOG_WHEN(x == 0, "(b) zero x"); 581 | if(x == 0) { 582 | LOG("printing context"); 583 | printf("__ print_context() __\n"); 584 | print_context(4); 585 | printf("__ end print_context() __\n"); 586 | } 587 | } 588 | 589 | static 590 | void __test_context_a(int x) { 591 | CONTEXT("A %d", x); 592 | __test_context_b(x - 1); 593 | LOG_WHEN(x > 0, "(a) nonzero x"); 594 | } 595 | 596 | static 597 | void __test_context_e(int x) { 598 | CONTEXT_LOG("E %d", x); 599 | LOG("printing context"); 600 | printf("__ print_context() __\n"); 601 | print_context(4); 602 | printf("__ end print_context() __\n"); 603 | } 604 | 605 | static 606 | void __test_context_d(int x) { 607 | CONTEXT_LOG("D %d", x); 608 | __test_context_e(x); 609 | LOG("exiting d"); 610 | } 611 | 612 | TEST(log_context) { 613 | log_init(); 614 | __test_context_a(2); 615 | __test_context_a(1); 616 | __test_context_a(0); 617 | __test_context_c(3); 618 | __test_context_d(42); 619 | log_print_all(); 620 | return 0; 621 | } 622 | /** [log_context] */ 623 | 624 | #if INTERFACE 625 | typedef char tag_t[4]; 626 | #define FORMAT_TAG "%.4s" 627 | #endif 628 | 629 | char to_tag_char(int x) { 630 | x &= 31; 631 | if(x < 24) { 632 | return 'a' + x; 633 | } else { 634 | return '2' + x - 24; 635 | } 636 | } 637 | 638 | int from_tag_char(char c) { 639 | if(c >= 'a') { 640 | if(c <= 'x') { 641 | return c - 'a'; 642 | } else { 643 | return -1; 644 | } 645 | } else if(c >= '0') { 646 | return c - '2' + 24; 647 | } else { 648 | return -1; 649 | } 650 | } 651 | 652 | int spread_bits(int x) { 653 | int y = 0; 654 | COUNTUP(i, 4) { 655 | int t = 0; 656 | COUNTUP(j, 5) { 657 | t <<= 4; 658 | t |= x & 1; 659 | x >>= 1; 660 | } 661 | t <<= i; 662 | y |= t; 663 | } 664 | return y; 665 | } 666 | 667 | int gather_bits(int y) { 668 | int x = 0; 669 | COUNTDOWN(i, 4) { 670 | int t = y >> i; 671 | COUNTUP(j, 5) { 672 | x <<= 1; 673 | x |= t & 1; 674 | t >>= 4; 675 | } 676 | } 677 | return x; 678 | } 679 | 680 | TEST(spread_gather_bits) { 681 | int x = 0x9AC35; 682 | int spread = spread_bits(x); 683 | int gather = gather_bits(spread); 684 | return x == gather ? 0 : -1; 685 | } 686 | 687 | // modular multiplicative inverses 688 | const unsigned int tag_factor = 510199; 689 | const unsigned int tag_factor_inverse = 96455; 690 | const unsigned int tag_mask = 0x7ffff; 691 | 692 | /** Write log tag for the given value. 693 | * @snippet log.c tag 694 | */ 695 | void write_tag(tag_t tag, unsigned int val) { 696 | val += 1; 697 | val *= tag_factor; 698 | val &= tag_mask; 699 | COUNTDOWN(i, sizeof(tag_t)) { 700 | tag[i] = to_tag_char(val); 701 | val >>= 5; 702 | } 703 | } 704 | 705 | /** Return value from the given log tag. 706 | * @snippet log.c tag 707 | */ 708 | int read_tag(const tag_t tag) { 709 | unsigned int val = 0; 710 | COUNTUP(i, sizeof(tag_t)) { 711 | int x = from_tag_char(tag[i]); 712 | if(x < 0) return x; 713 | val = (val << 5) | x; 714 | } 715 | val *= tag_factor_inverse; 716 | val &= tag_mask; 717 | return val - 1; 718 | } 719 | 720 | TEST(tag) { 721 | /** [tag] */ 722 | tag_t tag = "good"; 723 | int x = read_tag(tag); 724 | write_tag(tag, x); 725 | printf("tag: %d = " FORMAT_TAG "\n", x, tag); 726 | return strncmp("good", tag, sizeof(tag)) == 0 ? 0 : -1; 727 | /** [tag] */ 728 | } 729 | 730 | /** Get the most recent log tag. */ 731 | void get_tag(tag_t tag) { 732 | write_tag(tag, msg_head); 733 | } 734 | 735 | #if INTERFACE 736 | /** Tweaks are values that can be changed at a specific point. 737 | * Tweaks allow modifying something at a specific time, identified 738 | * by looking up the log tag in the log. 739 | * @param default_value value returned when not "tweaked." 740 | * Remaining arguments are logged. 741 | */ 742 | #define TWEAK(default_value, fmt, ...) \ 743 | ({ \ 744 | const char *__c; \ 745 | intptr_t __x; \ 746 | bool __t; \ 747 | if unlikely(__t = log_do_tweak(&__x, fmt)) { \ 748 | __c = COLOR_blue; \ 749 | } else { \ 750 | __x = (default_value); \ 751 | __c = COLOR_normal; \ 752 | } \ 753 | LOG(COLORs("TWEAK(%d)") " " fmt, __c, __x, ##__VA_ARGS__); \ 754 | if(__t) breakpoint(); \ 755 | __x; \ 756 | }) 757 | #endif 758 | 759 | bool log_do_tweak(intptr_t *x, char *fmt) { 760 | log_add_context(); 761 | if unlikely(tweak_enabled && 762 | (log_head == tweak_trigger || 763 | (set_tweak_fmt && tweak_fmt == fmt))) { 764 | *x = tweak_value; 765 | if(set_tweak_fmt && !tweak_fmt) tweak_fmt = fmt; 766 | return true; 767 | } else { 768 | return false; 769 | } 770 | } 771 | 772 | /** Tweak to the value at the given log tag. 773 | * Only one tweak can be set at a time. 774 | */ 775 | void log_set_tweak(const tag_t tag, intptr_t value, bool after) { 776 | tweak_enabled = true; 777 | tweak_trigger = read_tag(tag); 778 | tweak_value = value; 779 | set_tweak_fmt = after; 780 | } 781 | 782 | /** Clear the tweak. */ 783 | void log_unset_tweak() { 784 | tweak_enabled = false; 785 | } 786 | -------------------------------------------------------------------------------- /support.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2012-2018 Dustin M. DeWeese 2 | 3 | This file is part of the Startle library. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "startle/types.h" 29 | #include "startle/macros.h" 30 | #include "startle/error.h" 31 | #include "startle/log.h" 32 | #include "startle/support.h" 33 | #include "startle/stats_types.h" 34 | #include "startle/stats.h" 35 | 36 | /** @file 37 | * @brief Generally useful functions 38 | */ 39 | 40 | /** Estimate the median of an array */ 41 | unsigned int median3(pair_t *array, unsigned int lo, unsigned int hi) { 42 | unsigned int mid = lo + (hi - lo) / 2; 43 | uint32_t 44 | a = array[lo].first, 45 | b = array[mid].first, 46 | c = array[hi].first; 47 | if(a < b) { 48 | if(a < c) { 49 | if (b < c) { 50 | return mid; 51 | } else { // b >= c 52 | return hi; 53 | } 54 | } else { // a >= c 55 | return lo; 56 | } 57 | } else { // a >= b 58 | if(a < c) { 59 | return lo; 60 | } else { // a >= c 61 | if(b < c) { 62 | return hi; 63 | } else { 64 | return mid; 65 | } 66 | } 67 | } 68 | } 69 | 70 | /** Swap two pairs. */ 71 | void swap(pair_t *x, pair_t *y) { 72 | pair_t tmp = *x; 73 | *x = *y; 74 | *y = tmp; 75 | } 76 | 77 | /** Print the pairs in an array. */ 78 | void print_pairs(pair_t *array, size_t len) { 79 | if(!len) { 80 | printf("{}\n"); 81 | } else { 82 | printf("{{%" PRIuPTR ", %" PRIuPTR "}", array[0].first, array[0].second); 83 | RANGEUP(i, 1, len) { 84 | printf(", {%" PRIuPTR ", %" PRIuPTR "}", array[i].first, array[i].second); 85 | } 86 | printf("}\n"); 87 | } 88 | } 89 | 90 | /** Print the pairs in an array, where the first item is a string. */ 91 | void print_string_pairs(pair_t *array, size_t len) { 92 | if(!len) { 93 | printf("{}\n"); 94 | } else { 95 | printf("{{%s, %" PRIuPTR "}", (char *)array[0].first, array[0].second); 96 | RANGEUP(i, 1, len) { 97 | printf(", {%s, %" PRIuPTR "}", (char *)array[i].first, array[i].second); 98 | } 99 | printf("}\n"); 100 | } 101 | } 102 | 103 | TEST(sort) { 104 | /** [sort] */ 105 | pair_t array[] = {{3, 0}, {7, 1}, {2, 2}, {4, 3}, {500, 4}, {0, 5}, {8, 6}, {4, 7}}; 106 | quicksort(array, LENGTH(array)); 107 | uintptr_t last = array[0].first; 108 | printf("{{%d, %d}", (int)array[0].first, (int)array[0].second); 109 | RANGEUP(i, 1, LENGTH(array)) { 110 | printf(", {%d, %d}", (int)array[i].first, (int)array[i].second); 111 | if(array[i].first < last) { 112 | printf(" <- ERROR\n"); 113 | return -1; 114 | } 115 | } 116 | printf("}\n"); 117 | 118 | pair_t *p1 = find(array, LENGTH(array), 7); 119 | bool r1 = p1 && p1->second == 1; 120 | printf("index find existing: %s\n", r1 ? "PASS" : "FAIL"); 121 | bool r2 = !find(array, LENGTH(array), 5); 122 | printf("index find missing: %s\n", r2 ? "PASS" : "FAIL"); 123 | /** [sort] */ 124 | return r1 && r2 ? 0 : -1; 125 | } 126 | 127 | /** Use the Quicksort algorithm to sort an array of pairs by the first element. 128 | * @snippet support.c sort 129 | */ 130 | void quicksort(pair_t *array, unsigned int size) { 131 | if(size <= 1) return; 132 | 133 | unsigned int lo = 0, hi = size-1; 134 | struct frame { 135 | unsigned int lo, hi; 136 | } stack[size-1]; 137 | struct frame *top = stack; 138 | 139 | for(;;) { 140 | pair_t 141 | *pivot = &array[median3(array, lo, hi)], 142 | *right = &array[hi], 143 | *left = &array[lo], 144 | *x = left; 145 | pair_t pivot_value = *pivot; 146 | *pivot = *right; 147 | unsigned int fill_index = lo; 148 | 149 | RANGEUP(i, lo, hi) { 150 | if(x->first < pivot_value.first) { 151 | swap(x, left); 152 | left++; 153 | fill_index++; 154 | } 155 | x++; 156 | } 157 | *right = *left; 158 | 159 | *left = pivot_value; 160 | 161 | if(hi > fill_index + 1) { 162 | top->lo = fill_index+1; 163 | top->hi = hi; 164 | top++; 165 | } 166 | 167 | if(fill_index > lo + 1) { 168 | hi = fill_index-1; 169 | } else if(top > stack) { 170 | top--; 171 | lo = top->lo; 172 | hi = top->hi; 173 | } else break; 174 | } 175 | } 176 | 177 | /** Find a pair with matching key in a sorted array of pairs using binary search. 178 | * O(log n) time. 179 | */ 180 | pair_t *find(pair_t *array, size_t size, uintptr_t key) { 181 | size_t low = 0, high = size; 182 | while(high > low) { 183 | const size_t pivot = low + ((high - low) / 2); 184 | const uintptr_t pivot_key = array[pivot].first; 185 | if(pivot_key == key) { 186 | return &array[pivot]; 187 | } else if(pivot_key < key) { 188 | low = pivot + 1; 189 | } else { 190 | high = pivot; 191 | } 192 | } 193 | return NULL; 194 | } 195 | 196 | /** Like `find`, but find the last match. 197 | * O(log n) time. 198 | */ 199 | pair_t *find_last(pair_t *array, size_t size, uintptr_t key, size_t *est) { 200 | size_t low = 0, high = size; 201 | size_t pivot = est ? *est : low + ((high - low + 1) / 2); 202 | while(high > low + 1) { 203 | COUNTER(find_last, 1); 204 | if(high - low <= 0) { 205 | size_t l = low; 206 | low = high - 1; 207 | RANGEUP(i, l + 1, high) { 208 | uintptr_t k = array[i].first; 209 | if(k > key) { 210 | low = i - 1; 211 | break; 212 | } 213 | } 214 | break; 215 | } 216 | const uintptr_t pivot_key = array[pivot].first; 217 | if(pivot_key <= key) { 218 | low = pivot; 219 | } else { 220 | high = pivot; 221 | } 222 | pivot = low + ((high - low + 1) / 2); 223 | } 224 | pair_t *p = &array[low]; 225 | if(est) *est = low; 226 | return p->first == key ? p : NULL; 227 | } 228 | 229 | /** Like `find`, but find the last match, with a string key. 230 | * O(log n) time. 231 | */ 232 | pair_t *find_last_string(pair_t *array, size_t size, const char *key) { 233 | size_t low = 0, high = size; 234 | while(high > low + 1) { 235 | const size_t pivot = low + ((high - low + 1) / 2); 236 | const char *pivot_key = (char *)array[pivot].first; 237 | if(strcmp(pivot_key, key) <= 0) { 238 | low = pivot; 239 | } else { 240 | high = pivot; 241 | } 242 | } 243 | pair_t *p = &array[low]; 244 | return strcmp((char *)p->first, key) == 0 ? p : NULL; 245 | } 246 | 247 | /** Compare a string to a seg_t */ 248 | int segcmp(const char *str, seg_t seg) { 249 | size_t cnt = seg.n; 250 | const char *a = str, *b = seg.s; 251 | while(cnt--) { 252 | int diff = *a - *b; 253 | if(diff || !*a) return diff; 254 | a++; 255 | b++; 256 | } 257 | return *a; 258 | } 259 | 260 | /** Like `find`, but find the last match, with a string segment key. 261 | * O(log n) time. 262 | */ 263 | pair_t *find_last_seg(pair_t *array, size_t size, seg_t key) { 264 | size_t low = 0, high = size; 265 | while(high > low + 1) { 266 | const size_t pivot = low + ((high - low + 1) / 2); 267 | const char *pivot_key = (char *)array[pivot].first; 268 | if(segcmp(pivot_key, key) <= 0) { 269 | low = pivot; 270 | } else { 271 | high = pivot; 272 | } 273 | } 274 | pair_t *p = &array[low]; 275 | return segcmp((char *)p->first, key) == 0 ? p : NULL; 276 | } 277 | 278 | /** Return a pointer after the end of the string segment. */ 279 | const char *seg_end(seg_t seg) { 280 | return seg.s ? seg.s + seg.n : NULL; 281 | } 282 | 283 | /** Copy a string segment into a character array, as a zero terminated C string. */ 284 | size_t seg_read(seg_t seg, char *str, size_t size) { 285 | size_t n = size - 1 < seg.n ? size - 1 : seg.n; 286 | memcpy(str, seg.s, n); 287 | str[n] = 0; 288 | return n; 289 | } 290 | 291 | /** Return the segment after the first character matching `c`. */ 292 | seg_t seg_after(seg_t s, char c) { 293 | const char *p = s.s; 294 | const char *end = p + s.n; 295 | while(p < end) { 296 | if(*p == c) s.s = p; 297 | p++; 298 | } 299 | s.n = end - s.s; 300 | return s; 301 | } 302 | 303 | /** Create a string segment from a C string. */ 304 | seg_t string_seg(const char *str) { 305 | return (seg_t) {str, str ? strlen(str) : 0}; 306 | } 307 | 308 | /** Look up the string segment key in a sorted table. 309 | * Each row starts with a C string key. 310 | * Binary search, O(log n) time. 311 | * @param table the table 312 | * @param width width of each row in bytes 313 | * @param rows the number of rows 314 | * @param key_seg the key string segment 315 | * @snippet support.c lookup 316 | */ 317 | void *lookup(void *table, size_t width, size_t rows, seg_t key_seg) { 318 | size_t low = 0, high = rows, pivot; 319 | char const *key = key_seg.s; 320 | size_t key_length = key_seg.n; 321 | void *entry, *ret = 0; 322 | if(key_length > width) key_length = width; 323 | while(high > low) { 324 | pivot = low + ((high - low) >> 1); 325 | entry = (uint8_t *)table + width * pivot; 326 | int c = strncmp(key, entry, key_length); 327 | if(c == 0) { 328 | /* keep looking for a lower key */ 329 | ret = entry; 330 | high = pivot; 331 | } else if(c < 0) high = pivot; 332 | else low = pivot + 1; 333 | } 334 | return ret; 335 | } 336 | 337 | TEST(lookup) { 338 | /** [lookup] */ 339 | struct row { 340 | char name[8]; // must be large enough for the key and terminating null byte 341 | int count; 342 | double cost_per; 343 | }; 344 | 345 | /* The rows must be sorted by the key. */ 346 | struct row table[] = { 347 | { "apple", 3, 0.95 }, 348 | { "grape", 5, 0.50 }, 349 | { "pear", 2, 1.50 } 350 | }; 351 | 352 | struct row *result = lookup(table, WIDTH(table), LENGTH(table), string_seg("grape")); 353 | if(result) { 354 | printf("The total for %d %ss at $%.2f each is $%.2f.\n", 355 | result->count, 356 | result->name, 357 | result->cost_per, 358 | result->count * result->cost_per); 359 | return 0; 360 | } else { 361 | printf("Sorry, we don't have any of that.\n"); 362 | return -1; 363 | } 364 | /** [lookup] */ 365 | } 366 | 367 | /** Look up the string segment key in an unsorted table. 368 | * Each row starts with a C string key. 369 | * Linear search, O(n) time. 370 | * @param table the table 371 | * @param width width of each row in bytes 372 | * @param rows the number of rows 373 | * @param key_seg the key string segment 374 | */ 375 | void *lookup_linear(void *table, size_t width, size_t rows, seg_t key_seg) { 376 | char const *key = key_seg.s; 377 | size_t key_length = key_seg.n; 378 | uint8_t *entry = table; 379 | unsigned int rows_left = rows; 380 | if(key_length > width) key_length = width; 381 | while(rows_left-- && *entry) { 382 | if(!strncmp(key, (void *)entry, key_length)) return entry; 383 | entry += width; 384 | } 385 | return NULL; 386 | } 387 | 388 | #if INTERFACE 389 | struct mmfile { 390 | const char *path; /**< File path */ 391 | int fd; /**< File descriptor */ 392 | size_t size; /**< Size in bytes, set by mmap_file */ 393 | char *data; /**< Pointer to char data, set by mmap_file */ 394 | bool read_only; /**< True if read-only */ 395 | }; 396 | #endif 397 | 398 | /** Map a file to memory 399 | * Use `path` and `read_only` from the `mmfile` referenced by `f`. 400 | * Sets `data` and `size`. 401 | * @return `true` on success, otherwise `false`. 402 | * @snippet support.c mmap_file 403 | */ 404 | bool mmap_file(struct mmfile *f) { 405 | f->fd = open(f->path, f->read_only ? O_RDONLY : O_RDWR); 406 | if(f->fd < 0) return false; 407 | struct stat file_info; 408 | if(fstat(f->fd, &file_info) < 0) return false; 409 | f->size = file_info.st_size; 410 | if(f->size == 0) { 411 | f->data = NULL; 412 | return true; 413 | } 414 | f->data = mmap(f->data, 415 | f->size, 416 | f->read_only ? PROT_READ : PROT_READ | PROT_WRITE, 417 | MAP_SHARED, 418 | f->fd, 419 | 0); 420 | 421 | if(f->data == MAP_FAILED) { 422 | f->data = NULL; 423 | return false; 424 | } else { 425 | return true; 426 | } 427 | } 428 | 429 | /** Un-map a file from memory using the data set by `mmap_file`. 430 | * @snippet support.c mmap_file 431 | */ 432 | bool munmap_file(struct mmfile *f) { 433 | bool success = true; 434 | success &= munmap(f->data, f->size) == 0; 435 | success &= close(f->fd) == 0; 436 | memset(f, 0, sizeof(*f)); 437 | return success; 438 | } 439 | 440 | TEST(mmap_file) { 441 | /** [mmap_file] */ 442 | struct mmfile f = { 443 | .path = "eval.c", 444 | .read_only = true 445 | }; 446 | if(!mmap_file(&f)) return -1; 447 | char *c = f.data + 3; 448 | size_t n = f.size; 449 | while(n--) { 450 | putchar(*c); 451 | if(*c == '\n') break; 452 | c++; 453 | } 454 | if(!munmap_file(&f)) return -1; 455 | /** [mmap_file] */ 456 | return 0; 457 | } 458 | 459 | /** Count lines to reach `e` starting from `s`. */ 460 | size_t line_number(const char *s, const char *e) { 461 | if(!s || !e) return 0; 462 | size_t cnt = 1; 463 | while(s < e) { 464 | if(*s++ == '\n') cnt++; 465 | } 466 | return cnt; 467 | } 468 | 469 | /** Find a line containing the address. 470 | * @param x address into a line of text 471 | * @s [in,out] start of text/line 472 | * @size [in,out] size of text/line 473 | * @return true if the line was found 474 | */ 475 | bool find_line(const char *x, const char **s, size_t *size) { 476 | if(!x || !s || !size || !*s || x < *s || (size_t)(x - *s) >= *size) return false; 477 | const char *e = *s + *size; 478 | const char *p = *s; 479 | const char *ls = p; 480 | const char *le = e; 481 | while(p < x) { 482 | if(*p == '\n') { 483 | ls = p; 484 | } 485 | p++; 486 | } 487 | while(p < e) { 488 | if(*p == '\n') { 489 | le = p; 490 | break; 491 | } 492 | p++; 493 | } 494 | *s = ls; 495 | *size = le - ls; 496 | return true; 497 | } 498 | 499 | /** Integer log2 */ 500 | unsigned int int_log2(unsigned int x) { 501 | return x <= 1 ? 0 : (sizeof(x) * 8) - __builtin_clz(x - 1); 502 | } 503 | 504 | /** Long integer log2 */ 505 | unsigned int int_log2l(long unsigned int x) { 506 | return x <= 1 ? 0 : (sizeof(x) * 8) - __builtin_clzl(x - 1); 507 | } 508 | 509 | /** Insert into a set. 510 | * O(1) time. 511 | * @param x to insert 512 | * @param set the set 513 | * @param size number of entries the set can hold 514 | * @return true if `x` was already in the set 515 | * @snippet support.c set 516 | */ 517 | bool set_insert(uintptr_t x, uintptr_t *set, size_t size) { 518 | assert_error(x); 519 | size_t j = x % size; 520 | size_t d = 0; 521 | COUNTUP(i, size) { 522 | uintptr_t *p = &set[j]; 523 | if(*p == x) return true; 524 | if(*p) { 525 | /* Robin Hood hash */ 526 | size_t dx = (size + j - *p % size) % size; 527 | if(dx < d) { 528 | d = dx; 529 | uintptr_t tmp = *p; 530 | *p = x; 531 | x = tmp; 532 | } 533 | } else { 534 | *p = x; 535 | return false; 536 | } 537 | d++; 538 | j = (j + 1) % size; 539 | } 540 | 541 | assert_throw(false, "set is full"); 542 | return false; 543 | } 544 | 545 | 546 | /** Return if an item is in the set. 547 | * O(1) time. 548 | * @param x item to test 549 | * @param set the set 550 | * @param size number of entries the set can hold 551 | * @return true if `x` is in the set 552 | * @snippet support.c set 553 | */ 554 | bool set_member(uintptr_t x, uintptr_t *set, size_t size) { 555 | if(!x) return false; 556 | size_t offset = x % size; 557 | COUNTUP(i, size) { 558 | uintptr_t *p = &set[(i + offset) % size]; 559 | if(*p == x) return true; 560 | } 561 | return false; 562 | } 563 | 564 | /** Remove an item from the set. 565 | * O(1) time. 566 | * @param x to remove 567 | * @param set the set 568 | * @param size number of entries the set can hold 569 | * @return true if `x` was in the set 570 | * @snippet support.c set 571 | */ 572 | bool set_remove(uintptr_t x, uintptr_t *set, size_t size) { 573 | if(!x) return false; 574 | size_t offset = x % size; 575 | COUNTUP(i, size) { 576 | uintptr_t *p = &set[(i + offset) % size]; 577 | if(*p == x) { 578 | *p = 0; 579 | return true; 580 | } 581 | } 582 | return false; 583 | } 584 | 585 | TEST(set) { 586 | /** [set] */ 587 | uintptr_t set[7] = {0}; 588 | const size_t size = LENGTH(set); 589 | uintptr_t data[] = {7, 8, 9, 14, 21, 28}; 590 | FOREACH(i, data) { 591 | if(set_insert(data[i], set, size)) return -1; 592 | } 593 | FOREACH(i, set) { 594 | if(set[i]) printf("set[%d] = %d\n", (int)i, (int)set[i]); 595 | } 596 | FOREACH(i, data) { 597 | if(!set_member(data[i], set, size)) return -2; 598 | } 599 | FOREACH(i, data) { 600 | if(!set_remove(data[i], set, size)) return -3; 601 | } 602 | /** [set] */ 603 | return 0; 604 | } 605 | 606 | /** Swap two pointers. */ 607 | void swap_ptrs(void **x, void **y) { 608 | void *tmp = *x; 609 | *x = *y; 610 | *y = tmp; 611 | } 612 | 613 | /** Reverse an array of pointers. 614 | * @param n number of pointers. 615 | */ 616 | void reverse_ptrs(void **a, size_t n) { 617 | size_t m = n / 2; 618 | void **b = a + n; 619 | while(m--) swap_ptrs(a++, --b); 620 | } 621 | 622 | char *arguments(int argc, char **argv) { 623 | if(argc == 0) return NULL; 624 | size_t len = argc + 1; 625 | COUNTUP(i, argc) { 626 | len += strlen(argv[i]); 627 | } 628 | char *result = malloc(len), *p = result; 629 | COUNTUP(i, argc) { 630 | char *a = argv[i]; 631 | char *np = stpcpy(p, a); 632 | if(*a == '-') { 633 | *p = ':'; 634 | if(i) { 635 | *(p-1) = '\n'; 636 | } 637 | } 638 | *np = ' '; 639 | p = np + 1; 640 | } 641 | *(p-1) = '\n'; 642 | *p = '\0'; 643 | return result; 644 | } 645 | 646 | /** Hash the string to a non-zero number. */ 647 | uintptr_t nonzero_hash(const char *str, size_t len) 648 | { 649 | uintptr_t hash = 5381; 650 | LOOP(len) { 651 | hash = hash * 33 + (unsigned char)*str++; 652 | } 653 | return hash ? hash : -1; 654 | } 655 | 656 | int ctz(uintptr_t x) { 657 | return x ? __builtin_ctzll(x) : 0; 658 | } 659 | 660 | int next_bit(uintptr_t *mask) { 661 | if(*mask) { 662 | int res = __builtin_ctzll(*mask); 663 | *mask &= ~(1 << res); 664 | return res; 665 | } else { 666 | return -1; 667 | } 668 | } 669 | 670 | char *replace_char(char *s, char c_old, char c_new) { 671 | char *r = strchr(s, c_old); 672 | if(r) *r = c_new; 673 | return r; 674 | } 675 | 676 | bool is_whitespace(char c) { 677 | return ONEOF(c, ' ', '\n', '\r', '\t'); 678 | } 679 | 680 | seg_t seg_trim(seg_t s) { 681 | while(s.n && is_whitespace(*s.s)) { 682 | s.n--; 683 | s.s++; 684 | } 685 | const char *e = seg_end(s) - 1; 686 | while(s.n && is_whitespace(*e)) { 687 | e--; 688 | s.n--; 689 | } 690 | return s; 691 | } 692 | 693 | TEST(seg_trim) { 694 | seg_t s = string_seg(" \t hi \n"); 695 | s = seg_trim(s); 696 | printf("trimmed: \"%.*s\"\n", (int)s.n, s.s); 697 | return strncmp("hi", s.s, s.n) == 0 ? 0 : -1; 698 | } 699 | 700 | int digit_value(char c) { 701 | if(INRANGE(c, '0', '9')) { 702 | return c - '0'; 703 | } else if(INRANGE(c, 'A', 'Z')) { 704 | return c - 'A' + 10; 705 | } else if(INRANGE(c, 'a', 'z')) { 706 | return c - 'a' + 10; 707 | } else { 708 | return -1; 709 | } 710 | } 711 | 712 | char digit(int n) { 713 | if(!INRANGE(n, 0, 35)) return '?'; 714 | if(n <= 9) return '0' + n; 715 | return 'a' + (n - 10); 716 | } 717 | 718 | int strnum(const char *s, size_t n, int base) { 719 | if(!INRANGE(base, 1, 36)) return -1; 720 | int v = 0; 721 | LOOP(n) { 722 | if(*s == '\0') break; 723 | int d = digit_value(*s++); 724 | if(!INRANGE(d, 0, base - 1)) return -1; 725 | v = v * base + d; 726 | } 727 | return v; 728 | } 729 | 730 | TEST(strnum) { 731 | if(strnum("1234", 3, 8) != 0123) return -1; 732 | if(strnum("80", 2, 8) != -1) return -2; 733 | if(strnum("cAFe", 4, 16) != 0xcafe) return -3; 734 | return 0; 735 | } 736 | 737 | size_t unescape_string(char *dst, size_t n, seg_t str) { 738 | char *d = dst; 739 | const char 740 | *s = str.s, 741 | *s_end = seg_end(str), 742 | *d_end = d + n; 743 | while(*s && s < s_end && d < d_end) { 744 | if(s[0] == '\\' && s + 1 < s_end) { 745 | switch(s[1]) { 746 | case '\'': *d++ = '\''; break; 747 | case '\"': *d++ = '\"'; break; 748 | case '\\': *d++ = '\\'; break; 749 | case '0': *d++ = '\0'; break; // just handle \0 750 | case '?': *d++ = '?'; break; 751 | case 'a': *d++ = '\a'; break; 752 | case 'b': *d++ = '\b'; break; 753 | case 'f': *d++ = '\f'; break; 754 | case 'n': *d++ = '\n'; break; 755 | case 'r': *d++ = '\r'; break; 756 | case 't': *d++ = '\t'; break; 757 | case 'v': *d++ = '\v'; break; 758 | case 'x': { 759 | if(s + 3 >= s_end) goto copy_char; 760 | int v = strnum(s + 2, 2, 16); 761 | if(v < 0) goto copy_char; 762 | *d++ = v; 763 | s += 4; 764 | } continue; 765 | // TODO Unicode 766 | default: 767 | goto copy_char; 768 | } 769 | s += 2; 770 | } else { 771 | copy_char: 772 | *d++ = *s++; 773 | } 774 | } 775 | if(d < d_end) *d = '\0'; 776 | return d - dst; 777 | } 778 | 779 | TEST(unescape_string) { 780 | char out[32]; 781 | seg_t escaped = SEG("test\\n\\\\string\\bG\\x21\\0stuff"); 782 | seg_t unescaped = SEG("test\n\\string\bG\x21\0stuff"); 783 | unescape_string(out, sizeof(out), escaped); 784 | printf("%s\n", out); 785 | return segcmp(out, unescaped) == 0 ? 0 : -1; 786 | } 787 | 788 | size_t escape_string(char *dst, size_t n, seg_t str) { 789 | char *d = dst; 790 | const char 791 | *s = str.s, 792 | *s_end = seg_end(str), 793 | *d_end = d + n; 794 | while(s < s_end && d < d_end) { 795 | char c = *s++; 796 | if(INRANGE(c, 32, 126) && c != '\\') { 797 | *d++ = c; 798 | } else { 799 | if(d + 1 >= d_end) break; 800 | switch(c) { 801 | case '\'': 802 | case '\"': 803 | case '\\': 804 | break; 805 | case '\0': c = '0'; break; 806 | case '\a': c = 'a'; break; 807 | case '\b': c = 'b'; break; 808 | case '\f': c = 'f'; break; 809 | case '\n': c = 'n'; break; 810 | case '\r': c = 'r'; break; 811 | case '\t': c = 't'; break; 812 | case '\v': c = 'v'; break; 813 | default: 814 | if(d + 3 >= d_end) break; 815 | *d++ = '\\'; 816 | *d++ = 'x'; 817 | *d++ = digit(c >> 4); 818 | *d++ = digit(c & 0x0f); 819 | continue; 820 | }; 821 | *d++ = '\\'; 822 | *d++ = c; 823 | } 824 | } 825 | if(d < d_end) *d = '\0'; 826 | return d - dst; 827 | } 828 | 829 | TEST(escape_string) { 830 | char out[32]; 831 | seg_t escaped = SEG("test\\n\\\\string\\bG\\0stuff\\x1b"); 832 | seg_t unescaped = SEG("test\n\\string\bG\0stuff\x1b"); 833 | escape_string(out, sizeof(out), unescaped); 834 | printf("%s\n", out); 835 | return segcmp(out, escaped) == 0 ? 0 : -1; 836 | } 837 | 838 | void print_escaped_string(seg_t str) { 839 | char buf[64]; 840 | while(str.n > 16) { 841 | escape_string(buf, sizeof(buf), (seg_t) { .s = str.s, .n = 16 }); 842 | printf("%s", buf); 843 | str.s += 16; 844 | str.n -= 16; 845 | } 846 | escape_string(buf, sizeof(buf), str); 847 | printf("%s", buf); 848 | } 849 | 850 | TEST(print_escaped_string) { 851 | seg_t unescaped = SEG("test\n\\string\bG\0stuff"); 852 | print_escaped_string(unescaped); 853 | printf("\n"); 854 | return 0; 855 | } 856 | 857 | bool eq_seg(seg_t a, seg_t b) { 858 | return a.n == b.n && memcmp(a.s, b.s, a.n) == 0; 859 | } 860 | 861 | #if INTERFACE 862 | typedef struct ring_buffer { 863 | size_t head, tail, size; 864 | char data[0]; 865 | } ring_buffer_t; 866 | 867 | #define APPEND_DATA_TO(_type, _size) \ 868 | struct { \ 869 | _type hdr; \ 870 | char data[_size]; \ 871 | } 872 | 873 | #define RING_BUFFER(_size) \ 874 | ((ring_buffer_t *) \ 875 | &(APPEND_DATA_TO(ring_buffer_t, _size)) \ 876 | {{ .size = _size }, {0}}) 877 | #endif 878 | 879 | TEST(append_data_to) { 880 | APPEND_DATA_TO(ring_buffer_t, 1) x; 881 | return x.data == x.hdr.data ? 0 : -1; 882 | } 883 | 884 | size_t rb_available(const ring_buffer_t *rb) { 885 | return (rb->size + rb->head - rb->tail) % rb->size; 886 | } 887 | 888 | size_t rb_capacity(const ring_buffer_t *rb) { 889 | return rb->size - rb_available(rb) - 1; 890 | } 891 | 892 | size_t rb_write(ring_buffer_t *rb, const char *src, size_t size) { 893 | size = min(size, rb_capacity(rb)); 894 | size_t right = rb->size - rb->head; 895 | if(size <= right) { 896 | memcpy(&rb->data[rb->head], src, size); 897 | rb->head += size; 898 | } else { 899 | memcpy(&rb->data[rb->head], src, right); 900 | size_t left = size - right; 901 | memcpy(&rb->data[0], src + right, left); 902 | rb->head = left; 903 | } 904 | return size; 905 | } 906 | 907 | size_t rb_read(ring_buffer_t *rb, char *dst, size_t size) { 908 | size = min(size, rb_available(rb)); 909 | size_t right = rb->size - rb->tail; 910 | if(size <= right) { 911 | memcpy(dst, &rb->data[rb->tail], size); 912 | rb->tail += size; 913 | } else { 914 | memcpy(dst, &rb->data[rb->tail], right); 915 | size_t left = size - right; 916 | memcpy(dst + right, &rb->data[0], left); 917 | rb->tail = left; 918 | } 919 | return size; 920 | } 921 | 922 | #define WRITE(str) rb_write(rb, (str), sizeof(str)-1) 923 | #define PRINT(n) \ 924 | do { \ 925 | size_t _size = rb_read(rb, out, min(sizeof(out), (n))); \ 926 | printf("%.*s\n", (int)_size, out); \ 927 | } while(0) 928 | 929 | TEST(ring_buffer) { 930 | char out[8]; 931 | ring_buffer_t *rb = RING_BUFFER(8); 932 | 933 | WRITE("hello"); 934 | PRINT(2); 935 | PRINT(2); 936 | WRITE(" world!"); 937 | PRINT(8); 938 | return 0; 939 | } 940 | 941 | const char *seg_find(seg_t haystack, seg_t needle) { 942 | if(needle.n > haystack.n) return NULL; 943 | if(needle.n == 0) return haystack.s; 944 | const char *end = haystack.s + (haystack.n - needle.n + 1); 945 | const char *p = haystack.s; 946 | while(p < end) { 947 | if(memcmp(p, needle.s, needle.n) == 0) return p; 948 | p++; 949 | } 950 | return NULL; 951 | } 952 | 953 | TEST(seg_find) { 954 | seg_t hello = SEG("Hello. My name is Inigo Montoya. You killed my father. Prepare to die."); 955 | seg_t father = SEG("father"); 956 | seg_t mother = SEG("mother"); 957 | if(!seg_find(hello, father)) return -1; 958 | if(seg_find(hello, mother)) return -2; 959 | return 0; 960 | } 961 | 962 | const char *seg_find_char(seg_t haystack, char needle) { 963 | if(haystack.n == 0) return NULL; 964 | const char *end = seg_end(haystack); 965 | const char *p = haystack.s; 966 | while(p < end) { 967 | if(*p == needle) return p; 968 | p++; 969 | } 970 | return NULL; 971 | } 972 | 973 | TEST(seg_find_char) { 974 | seg_t hello = SEG("hello"); 975 | if(!seg_find_char(hello, 'l')) return -1; 976 | if(seg_find_char(hello, '!')) return -2; 977 | return 0; 978 | } 979 | 980 | char capitalize(char c) { 981 | return INRANGE(c, 'a', 'z') ? c - ('a' - 'A') : c; 982 | } 983 | -------------------------------------------------------------------------------- /map.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2012-2018 Dustin M. DeWeese 2 | 3 | This file is part of the Startle library. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "startle/types.h" 27 | #include "startle/stats_types.h" 28 | #include "startle/macros.h" 29 | #include "startle/error.h" 30 | #include "startle/log.h" 31 | #include "startle/support.h" 32 | #include "startle/map.h" 33 | #include "startle/stats.h" 34 | 35 | /** @file 36 | * @brief Zero space overhead maps 37 | */ 38 | 39 | #if INTERFACE 40 | 41 | /** A map for key-value pairs. */ 42 | typedef pair_t * map_t; 43 | 44 | /** Declare a map to hold `size` key-value pairs. */ 45 | #define MAP(name, size) pair_t name[(size) + 1] = {{(size), 0}} 46 | 47 | /** A key comparison function. */ 48 | typedef bool (cmp_t)(uintptr_t, uintptr_t); 49 | 50 | #endif 51 | 52 | #define DEBUG 0 53 | 54 | /** Scan for a pair with a lesser key. */ 55 | static 56 | size_t scan(pair_t *start, pair_t *end, uintptr_t key, cmp_t cmp) { 57 | size_t cnt = 0; 58 | pair_t *x = start; 59 | while(x < end && cmp(x->first, key)) { 60 | ++cnt; 61 | ++x; 62 | } 63 | return cnt; 64 | } 65 | 66 | /** Swap two non-overlapping blocks of `n` pairs. */ 67 | void swap_block(pair_t *a, pair_t *b, size_t n) { 68 | if(a == b) return; 69 | COUNTER(swap, n); 70 | while(n--) { 71 | swap(a++, b++); 72 | } 73 | } 74 | 75 | /** Reverse `n` pairs. */ 76 | void reverse(pair_t *a, size_t n) { 77 | size_t m = n / 2; 78 | COUNTER(swap, m); 79 | pair_t *b = a + n; 80 | while(m--) swap(a++, --b); 81 | } 82 | 83 | /** O(n) in-place rotation by `n` pairs. */ 84 | void rotate(pair_t *a, pair_t *b, size_t n) { 85 | if(a == b) return; 86 | const size_t m = b - a; 87 | reverse(a, m); 88 | reverse(b, n - m); 89 | reverse(a, n); 90 | } 91 | 92 | /* 93 | // alternate implementation 94 | void rotate(pair_t *a, pair_t *b, size_t n) { 95 | size_t offset = b - a; 96 | while(offset) { 97 | pair_t 98 | *p = b, 99 | *q = a + n; 100 | assert_error(a <= b); 101 | assert_error(b <= q); 102 | while(q > b) { 103 | swap(--p, --q); 104 | if(p <= a) p = b; 105 | } 106 | if(offset > 1) { 107 | b = a + (offset - (n % offset)); 108 | n = offset; 109 | offset = b - a; 110 | } else break; 111 | } 112 | } 113 | */ 114 | 115 | size_t isqrt(size_t x) { 116 | if(x < 2) return x; 117 | size_t a = x / 2, b; 118 | b = x / a; a = (a + b) / 2; 119 | b = x / a; a = (a + b) / 2; 120 | b = x / a; a = (a + b) / 2; 121 | b = x / a; a = (a + b) / 2; 122 | return a; 123 | } 124 | 125 | #if(DEBUG) 126 | static 127 | bool is_ordered(pair_t *x, size_t r, size_t n, cmp_t cmp) { 128 | if(n <= 1) return true; 129 | r = r % n; 130 | uintptr_t prev = x[r].first; 131 | RANGEUP(i, r + 1, n) { 132 | uintptr_t next = x[i].first; 133 | if(cmp(next, prev)) return false; 134 | prev = next; 135 | } 136 | COUNTUP(i, r) { 137 | uintptr_t next = x[i].first; 138 | if(cmp(next, prev)) return false; 139 | prev = next; 140 | } 141 | return true; 142 | } 143 | #endif 144 | 145 | /** Merge an array of two sorted sequences of pairs into one. 146 | * @param arr the array 147 | * @b pointer to second sequence 148 | * @n number of pairs in the array 149 | * @param cmp key comparison function. 150 | */ 151 | void merge(pair_t *arr, pair_t *b, size_t n, cmp_t cmp) { 152 | if(GET_COUNTER(merge_depth) == 0) { 153 | COUNTER(merge, n); 154 | } 155 | #if DEBUG 156 | printf("merge "); 157 | print_pairs(arr, n); 158 | #endif 159 | // |== output ==|== a ==|== q ==|== b ==| 160 | // head--^ 161 | pair_t 162 | *a = arr, // sorted region a 163 | *q = b, // queuing region 164 | *h = b, // head (and tail) of the queue 165 | *end = arr + n; 166 | 167 | while(a < b) { 168 | #if DEBUG 169 | printf("\n"); 170 | print_pairs(arr, a-arr); 171 | printf("a[%" PRIuPTR "]: ", a-arr); 172 | print_pairs(a, q-a); 173 | printf("q[%" PRIuPTR ", %" PRIuPTR "]: ", q-arr, h-q); 174 | print_pairs(q, b-q); 175 | printf("b[%" PRIuPTR "]: ", b-arr); 176 | print_pairs(b, n-(b-arr)); 177 | assert_error(is_ordered(a, 0, q-a, cmp)); 178 | assert_error(is_ordered(q, h-q, b-q, cmp)); 179 | assert_error(is_ordered(b, 0, n-(b-arr), cmp)); 180 | #endif 181 | // non-empty queue 182 | if(q < b) { 183 | // count b < h 184 | // limit the scan to the size of a 185 | size_t b_run = scan(b, min(b + (q - a), end), h->first, cmp); 186 | if(b_run) { // choose minimum copying to preserve queue 187 | swap_block(a, b, b_run); // swap the run into the output replacing a block of 'a' 188 | // |== output b_left ==|== a_right ==|== q a_left ==|== b_right ==| 189 | size_t swaps; 190 | if((ptrdiff_t)b_run >= min(b - h, h - q)) { 191 | // rotate h back to q 192 | // leave new items on end 193 | rotate(q, h, b - q); 194 | swaps = b - q; 195 | h = q; 196 | // ==|== q a_left ==|== 197 | // ^-- h 198 | } else { 199 | // rotate h over new items 200 | // push b_run 201 | // swaps: b_run + min(b - h, h - q), avg. b_run + q size / 2 202 | if(b - h < h - q) { 203 | // move right side 204 | rotate(h, b, b_run + (b - h)); 205 | swaps = b_run + (b - h); 206 | h += b_run; 207 | } else { 208 | // swap left side 209 | swap_block(q, b, b_run); 210 | rotate(q, q + b_run, h - q); 211 | swaps = b_run + h - q; 212 | } 213 | 214 | // ==|== q_left a_left q_right ==|== 215 | // ^-- h 216 | } 217 | b += b_run; 218 | a += b_run; 219 | 220 | // re-balance 221 | if(swaps > (1 << 7) && 222 | swaps > isqrt(b - q) / 2) { 223 | 224 | rotate(q, h, b - q); 225 | h = q; 226 | 227 | COUNTER(merge_depth, 1); 228 | merge(q, b, end - q, cmp); 229 | b = q; 230 | COUNTER(merge_depth, -1); 231 | COUNTER_MAX(merge_depth_max, GET_COUNTER(merge_depth)); 232 | } 233 | } else { // b >= h 234 | swap(a++, h++); 235 | COUNTER(swap, 1); 236 | if(h >= b) { // queue wrap around 237 | h = q; 238 | } 239 | } 240 | } else { 241 | // empty queue 242 | if(b < end && cmp(b->first, a->first)) { 243 | swap(a++, b++); 244 | COUNTER(swap, 1); 245 | } else { 246 | a++; 247 | } 248 | } 249 | 250 | // if a runs into the queue, just steal the queue 251 | if(a >= q) { 252 | rotate(q, h, b-q); 253 | a = q; 254 | q = b; 255 | h = q; 256 | } 257 | } 258 | } 259 | 260 | /** Direct key comparison. */ 261 | bool key_cmp(uintptr_t a, uintptr_t b) { 262 | return a < b; 263 | } 264 | 265 | /** String key comparison. */ 266 | static 267 | bool string_cmp(uintptr_t a, uintptr_t b) { 268 | return strcmp((char *)a, (char *)b) < 0; 269 | } 270 | 271 | void merge_left_short(pair_t *a, pair_t *b, size_t n, cmp_t cmp) { 272 | pair_t *end = a + n; 273 | while(a < b && b < end) { 274 | pair_t *x = b; 275 | while(x < end && cmp(x->first, a->first)) x++; 276 | 277 | // all [b...x) < a[0]; 278 | rotate(a, b, x - a); 279 | a += x - b + 1; 280 | b = x; 281 | } 282 | } 283 | 284 | TEST(merge_left_short) { 285 | pair_t arr[] = 286 | {{1,1}, {3,3}, {6,6}, 287 | {0,0}, {2,2}, {4,4}, 288 | {5,5}, {7,7}, {8,8}}; 289 | merge_left_short(arr, arr + 3, 9, key_cmp); 290 | if(!is_ordered(arr, LENGTH(arr), key_cmp)) return -1; 291 | return 0; 292 | } 293 | 294 | void merge_right_short(pair_t *a, pair_t *b, size_t n, cmp_t cmp) { 295 | if(n <= 1 || a >= b) return; 296 | pair_t *end = a + n; 297 | while(a < b && b < end) { 298 | pair_t *x = b, *last = end - 1; 299 | while(x > a && cmp(last->first, x[-1].first)) x--; 300 | 301 | // all [x...b) > last 302 | rotate(x, b, end - x); 303 | size_t r = b - x; 304 | b -= r; 305 | end -= r + 1; 306 | } 307 | } 308 | 309 | TEST(merge_right_short) { 310 | pair_t arr[] = 311 | {{1, 1}, {2, 2}, {3, 3}, 312 | {4, 4}, {5, 5}, {6, 6}, 313 | {0, 0}, {7, 7}, {8, 8}}; 314 | merge_right_short(arr, arr + 6, 9, key_cmp); 315 | if(!is_ordered(arr, LENGTH(arr), key_cmp)) return -1; 316 | return 0; 317 | } 318 | 319 | #define NEXT(x, r) \ 320 | ({ \ 321 | LET(__px, &(x)); \ 322 | LET(__x, *__px); \ 323 | LET(__xn, __x + 1); \ 324 | *__px = __xn < r##_end ? __xn: r; \ 325 | __x; \ 326 | }) 327 | 328 | // preserves, but scrambles, the buffer 329 | void merge_with_buffer(pair_t *a, pair_t *b, size_t n, cmp_t cmp, pair_t *buf) { 330 | pair_t 331 | *const a_end = b, 332 | *const b_end = a + n, 333 | *const buf_end = buf + min(a_end - a, b_end - b), 334 | *h = buf, 335 | *t = buf; 336 | size_t buf_n = 0; 337 | while(a < a_end && b < b_end) { 338 | if(!buf_n || cmp(b->first, t->first)) { 339 | if(cmp(b->first, a->first)) { 340 | swap(a, b); 341 | if(b < b_end - 1) { 342 | swap(b++, NEXT(h, buf)); // b 343 | buf_n++; 344 | } 345 | } 346 | } else { // h > t 347 | if(!cmp(a->first, t->first)) { 348 | swap(a, t); 349 | swap(NEXT(h, buf), NEXT(t, buf)); 350 | } 351 | } 352 | a++; 353 | } 354 | if(a == a_end) { 355 | while(b < b_end) { 356 | if(buf_n && !cmp(b->first, t->first)) { 357 | swap(a, NEXT(t, buf)); 358 | buf_n--; 359 | } else { 360 | swap(a, b++); 361 | } 362 | a++; 363 | } 364 | } else { 365 | while(a < a_end) { 366 | if(buf_n && !cmp(a->first, t->first)) { 367 | swap(a, t); 368 | swap(NEXT(h, buf), NEXT(t, buf)); 369 | } 370 | a++; 371 | } 372 | } 373 | while(buf_n) { 374 | swap(a++, NEXT(t, buf)); 375 | buf_n--; 376 | } 377 | } 378 | 379 | TEST(merge_with_buffer) { 380 | pair_t arr[] = 381 | {{0,0}, {4,4}, {8,8}, 382 | {1,1}, {2,2}, {3,3}, 383 | {5,5}, {6,6}, {7,7}}; 384 | pair_t buf[3]; 385 | memset(buf, -1, sizeof(buf)); 386 | merge_with_buffer(arr, arr+3, 9, key_cmp, buf); 387 | if(!is_ordered(arr, LENGTH(arr), key_cmp)) return -1; 388 | return 0; 389 | } 390 | 391 | TEST(merge_with_buffer2) { 392 | pair_t arr[] = {{11, 11}, {3, 3}, {8, 8}, {10, 10}}; 393 | pair_t buf[4]; 394 | memset(buf, -1, sizeof(buf)); 395 | merge_with_buffer(arr, arr+1, 4, key_cmp, buf); 396 | if(!is_ordered(arr, LENGTH(arr), key_cmp)) return -1; 397 | return 0; 398 | } 399 | 400 | // on return, *pa points to unmerged elements, and *pbuffer points to the location of the buffer 401 | // returns number of merged elements 402 | size_t merge_into_buffer(pair_t **pbuffer, pair_t **pa, pair_t *b, size_t n, cmp_t cmp) { 403 | pair_t 404 | *buffer = *pbuffer, 405 | *a = *pa; 406 | size_t 407 | buffer_n = a - buffer, 408 | a_n = b - a, 409 | b_n = n - buffer_n - a_n; 410 | 411 | assert_ge(buffer_n, min(a_n, b_n)); 412 | while(a_n) { 413 | if(b_n && cmp(b->first, a->first)) { 414 | swap(buffer++, b++); 415 | b_n--; 416 | } else { 417 | swap(buffer++, a++); 418 | a_n--; 419 | } 420 | } 421 | *pbuffer = buffer; 422 | *pa = b; 423 | return n - b_n - buffer_n; 424 | } 425 | 426 | TEST(merge_into_buffer) { 427 | pair_t arr[20]; 428 | zero(arr); 429 | pair_t 430 | *buffer = arr, 431 | *a = buffer + 5, 432 | *b = a + 10, 433 | *end = arr + LENGTH(arr); 434 | test_pairs(a, 15, 10, 42); 435 | size_t n = merge_into_buffer(&buffer, &a, b, LENGTH(arr), key_cmp); 436 | if(!is_ordered(arr, n, key_cmp)) return -1; 437 | if(!is_ordered(a, end - a, key_cmp)) return -2; 438 | return 0; 439 | } 440 | 441 | // overwrites the buffer 442 | void merge_with_buffer_fast(pair_t *a, pair_t *b, size_t n, cmp_t cmp, pair_t *buf) { 443 | pair_t 444 | *const a_end = b, 445 | *const b_end = a + n, 446 | *const buf_end = buf + min(a_end - a, b_end - b), 447 | *h = buf, 448 | *t = buf; 449 | while(a < a_end && b < b_end) { 450 | if(h == t || cmp(b->first, t->first)) { 451 | if(cmp(b->first, a->first)) { 452 | if(b < b_end - 1) { 453 | *NEXT(h, buf) = *a; 454 | *a = *b++; 455 | } else { 456 | swap(a, b); 457 | } 458 | } 459 | } else { // h > t 460 | if(!cmp(a->first, t->first)) { 461 | *NEXT(h, buf) = *a; 462 | *a = *NEXT(t, buf); 463 | } 464 | } 465 | a++; 466 | } 467 | if(a == a_end) { 468 | while(b < b_end) { 469 | if(h != t && !cmp(b->first, t->first)) { 470 | *a = *NEXT(t, buf); 471 | } else { 472 | *a = *b++; 473 | } 474 | a++; 475 | } 476 | } else { 477 | while(a < a_end) { 478 | if(h != t && !cmp(a->first, t->first)) { 479 | *NEXT(h, buf) = *a; 480 | *a = *NEXT(t, buf); 481 | } 482 | a++; 483 | } 484 | } 485 | while(t != h) { 486 | *a++ = *NEXT(t, buf); 487 | } 488 | } 489 | 490 | TEST(merge_with_buffer_fast) { 491 | pair_t arr[] = 492 | {{2, 2}, {4, 4}, {5, 5}, 493 | {6, 6}, {7, 7}, {8, 8}, 494 | {0, 0}, {1, 1}, {3, 3}}; 495 | pair_t buf[3]; 496 | memset(buf, -1, sizeof(buf)); 497 | merge_with_buffer_fast(arr, arr+6, 9, key_cmp, buf); 498 | if(!is_ordered(arr, LENGTH(arr), key_cmp)) return -1; 499 | return 0; 500 | } 501 | 502 | void bubble_sort(pair_t *arr, size_t n, cmp_t cmp) { 503 | COUNTUP(i, n-1) { 504 | COUNTUP(j, n-i-1) { 505 | if(cmp(arr[j+1].first, arr[j].first)) { 506 | swap(&arr[j], &arr[j+1]); 507 | } 508 | } 509 | } 510 | } 511 | 512 | bool is_ordered(pair_t *arr, size_t n, cmp_t cmp) { 513 | COUNTUP(i, n-1) { 514 | if(cmp(arr[i+1].first, arr[i].first)) { 515 | return false; 516 | } 517 | } 518 | return true; 519 | } 520 | 521 | #define BLOCK(i) min(n, bs * (i)) 522 | #define BLOCK_LAST(i) (BLOCK((i) + 1) - 1) 523 | #define CHECK_BLOCKS(name) \ 524 | do { \ 525 | COUNTUP(i, n_blocks) { \ 526 | pair_t *bl = &a[BLOCK(i)]; \ 527 | if(abs(bl - buffer) < bs) continue; \ 528 | on_assert_error(is_ordered(bl, bs, cmp)) { \ 529 | printf("OUT OF ORDER @ " #name " BLOCK(%d): ", i); \ 530 | print_pairs(bl, bs); \ 531 | } \ 532 | } \ 533 | } while(0) 534 | 535 | // Huang, Bing-Chao & Langston, Michael. (1988). Practical In-Place Merging. 536 | // Communications of the ACM. 31. 348-352. 10.1145/42392.42403. 537 | // http://www.akira.ruc.dk/~keld/teaching/algoritmedesign_f04/Artikler/04/Huang88.pdf 538 | // NOTE: slower than merge 539 | void merge_huang88(pair_t *arr, pair_t *b, size_t n, cmp_t cmp) { 540 | size_t 541 | bs = isqrt(n), 542 | offset = (b - arr) % bs, 543 | n_blocks = (n - offset) / bs, 544 | tail = n - (bs * n_blocks) - offset; 545 | pair_t 546 | *end = arr + n, 547 | *a = arr + offset; // first full block 548 | 549 | if(b - arr < (ptrdiff_t)(bs + offset)) { 550 | merge_left_short(arr, b, n, cmp); 551 | return; 552 | } 553 | 554 | if(end - b < (ptrdiff_t)bs) { 555 | merge_right_short(arr, b, n, cmp); 556 | return; 557 | } 558 | 559 | // find top bs elements 560 | pair_t *top_a = b, *top_b = end; 561 | LOOP(bs) { 562 | if(cmp(top_a[-1].first, top_b[-1].first)) { 563 | top_b--; 564 | } else { 565 | top_a--; 566 | } 567 | } 568 | 569 | // handle last block 570 | // swap top_b into last block of a (buffer) 571 | pair_t *buffer = b - bs; 572 | swap_block(buffer, top_b, end - top_b); 573 | 574 | // merge the last few elements 575 | merge_with_buffer(end - bs - tail, top_b, bs + tail, cmp, buffer); 576 | 577 | // handle first block 578 | if(offset) { 579 | pair_t *p = buffer + (bs - offset); 580 | swap_block(arr, p, offset); 581 | merge_with_buffer(p, b, bs + offset, cmp, arr); 582 | swap_block(arr, p, offset); 583 | } 584 | 585 | // move buffer to the first block 586 | swap_block(&a[BLOCK(0)], buffer, bs); 587 | buffer = &a[BLOCK(0)]; 588 | 589 | // selection sort on sqrt(n) blocks of size sqrt(n) 590 | // comparing the last item in the block 591 | RANGEUP(i, 1, n_blocks) { 592 | // find minimum in remaining blocks 593 | size_t min = i; 594 | uintptr_t min_key = a[BLOCK_LAST(min)].first; 595 | RANGEUP(j, i + 1, n_blocks) { 596 | uintptr_t key = a[BLOCK_LAST(j)].first; 597 | if(cmp(key, min_key)) { 598 | min = j; 599 | min_key = key; 600 | } 601 | } 602 | if(min > i) swap_block(&a[BLOCK(i)], &a[BLOCK(min)], bs); 603 | } 604 | 605 | pair_t *p = &a[BLOCK(1)]; 606 | RANGEUP(i, 2, n_blocks + !!tail) { 607 | pair_t *b = &a[BLOCK(i)]; 608 | size_t size = min((ptrdiff_t)bs, end - b); 609 | if(cmp(b->first, b[-1].first)) { 610 | merge_into_buffer(&buffer, &p, b, (b + size) - buffer, cmp); 611 | } 612 | } 613 | 614 | bubble_sort(buffer, bs, cmp); 615 | rotate(buffer, p, end - buffer); 616 | assert_error(is_ordered(arr, n, cmp)); 617 | } 618 | 619 | void test_pairs(pair_t *arr, size_t n, size_t a, unsigned int s) { 620 | const uint32_t k = (1ul << 31) - 1; 621 | COUNTUP(i, n) arr[i] = (pair_t) {i, i}; 622 | COUNTUP(i, n - 1) { // shuffle 623 | s = (s * k) % (n - i); 624 | swap(&arr[i], &arr[s]); 625 | } 626 | quicksort(arr, a); 627 | quicksort(arr + a, n - a); 628 | } 629 | 630 | TEST(merge_huang88) { 631 | pair_t arr[16]; 632 | const int b = 7; 633 | test_pairs(arr, LENGTH(arr), b, 0); 634 | merge_huang88(arr, arr + b, LENGTH(arr), key_cmp); 635 | if(!is_ordered(arr, LENGTH(arr), key_cmp)) return -1; 636 | return 0; 637 | } 638 | 639 | TEST(merge_huang88b) { 640 | pair_t arr[] = {{16, 16}, 641 | {17, 17}, {18, 18}, {19, 19}, {20, 20}, {21, 21}, 642 | {22, 22}, {23, 23}, {24, 24}, {25, 25}, {26, 26}, 643 | {27, 27}, {28, 28}, {29, 29}, {30, 30}, {31, 31}, 644 | {0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, 645 | {5, 5}, {6, 6}, {7, 7}, {8, 8}, {9, 9}, 646 | {10, 10}, {11, 11}, {12, 12}, {13, 13}, {14, 14}, 647 | {15, 15}}; 648 | const int b = 16; 649 | merge_huang88(arr, arr + b, LENGTH(arr), key_cmp); 650 | if(!is_ordered(arr, LENGTH(arr), key_cmp)) return -1; 651 | return 0; 652 | } 653 | 654 | #define MERGE_TEST(arr) \ 655 | do { \ 656 | merge((arr), (arr) + (LENGTH(arr) >> 1), LENGTH(arr), key_cmp); \ 657 | printf(#arr ": "); \ 658 | print_pairs((arr), LENGTH(arr)); \ 659 | } while(0) 660 | 661 | 662 | TEST(merge) { 663 | pair_t arr1[] = {{0, 0}, {2, 1}, {4, 2}, {6, 3}, {1, 4}, {3, 5}, {5, 6}, {7, 7}}; 664 | MERGE_TEST(arr1); 665 | 666 | pair_t arr2[] = {{0, 0}, {1, 1}, {6, 2}, {7, 3}, {2, 4}, {3, 5}, {4, 6}, {5, 7}}; 667 | MERGE_TEST(arr2); 668 | 669 | pair_t arr3[] = {{4, 0}, {5, 1}, {6, 2}, {7, 3}, {0, 4}, {1, 5}, {2, 6}, {3, 7}}; 670 | MERGE_TEST(arr3); 671 | 672 | pair_t arr4[] = {{1, 0}, {2, 1}, {4, 2}, {5, 3}, {0, 4}, {3, 5}, {6, 6}, {7, 7}}; 673 | MERGE_TEST(arr4); 674 | 675 | pair_t arr5[] = {{2, 0}, {3, 1}, {5, 2}, {8, 3}, {0, 4}, {1, 5}, {4, 6}, {7, 7}}; 676 | MERGE_TEST(arr5); 677 | 678 | pair_t arr6[] = {{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}, {7, 0}, 679 | {0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1}}; 680 | MERGE_TEST(arr6); 681 | 682 | pair_t arr7[] = {{0, 0}, {2, 0}, {4, 0}, {6, 0}, {8, 0}, {10, 0}, {12, 0}, {14, 0}, 683 | {1, 0}, {3, 0}, {5, 0}, {7, 0}, {9, 0}, {11, 0}, {13, 0}, {15, 0}}; 684 | MERGE_TEST(arr7); 685 | 686 | pair_t arr8[] = {{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {6, 0}, {9, 0}, {12, 0}, 687 | {5, 1}, {7, 1}, {8, 1}, {10, 1}, {11, 1}, {13, 1}, {14, 1}, {15, 1}}; 688 | MERGE_TEST(arr8); 689 | 690 | return 0; 691 | } 692 | 693 | /** Return number of entries that can be stored in a map. */ 694 | size_t map_size(map_t map) { 695 | return map[0].first; 696 | } 697 | 698 | /** Return number of entries currently stored in a map. */ 699 | size_t *map_cnt(map_t map) { 700 | return &map[0].second; 701 | } 702 | 703 | /** Clear all entries from the map. */ 704 | void map_clear(map_t map) { 705 | *map_cnt(map) = 0; 706 | } 707 | 708 | pair_t *map_elems(map_t map) { 709 | return &map[1]; 710 | } 711 | 712 | // called after inserting an element 713 | static 714 | void map_sort(map_t map, uintptr_t bit, cmp_t cmp) { 715 | uintptr_t cnt = *map_cnt(map); 716 | pair_t *elems = map_elems(map); 717 | pair_t *buf = &elems[cnt]; 718 | size_t buf_size = map_size(map) - cnt; 719 | if(!cnt || cnt & 1) return; 720 | uintptr_t x = (cnt - 1) & ~(bit - 1); 721 | while(x & bit) { 722 | x &= ~bit; 723 | if(bit <= buf_size) { 724 | merge_with_buffer_fast(&elems[x], &elems[x+bit], bit << 1, cmp, buf); 725 | } else { 726 | merge(&elems[x], &elems[x+bit], bit << 1, cmp); 727 | } 728 | bit <<= 1; 729 | } 730 | } 731 | 732 | static 733 | void _map_sort_full(map_t map, cmp_t cmp) { 734 | uintptr_t 735 | cnt = *map_cnt(map), 736 | x = cnt & ~(1 << __builtin_ctz(cnt)); 737 | pair_t *elems = map_elems(map); 738 | pair_t *buf = &elems[cnt]; 739 | size_t buf_size = map_size(map) - cnt; 740 | while(x) { 741 | uintptr_t y = x & ~(1 << __builtin_ctz(x)); 742 | if(min(x - y, cnt - x) <= buf_size) { 743 | merge_with_buffer_fast(&elems[y], &elems[x], cnt - y, cmp, buf); 744 | } else { 745 | merge(&elems[y], &elems[x], cnt - y, cmp); 746 | } 747 | x = y; 748 | } 749 | } 750 | 751 | /** Fully sort map. 752 | * Takes O(log n) time. 753 | * Lookup will be O(log n) afterwards. 754 | */ 755 | void map_sort_full(map_t map) { 756 | _map_sort_full(map, key_cmp); 757 | } 758 | 759 | /** Fully sort a map with string keys. */ 760 | void string_map_sort_full(map_t map) { 761 | _map_sort_full(map, string_cmp); 762 | } 763 | 764 | TEST(map_sort_full) { 765 | MAP(a, 21); 766 | *map_cnt(a) = 21; 767 | COUNTUP(i, 16) { 768 | pair_t *p = &a[i + 1]; 769 | p->first = i; 770 | p->second = 0; 771 | } 772 | COUNTUP(i, 4) { 773 | pair_t *p = &a[i + 17]; 774 | p->first = 2 * i; 775 | p->second = 1; 776 | } 777 | pair_t *p = &a[21]; 778 | p->first = 0; 779 | p->second = 2; 780 | map_sort_full(a); 781 | print_map(a); 782 | return 0; 783 | } 784 | 785 | static 786 | bool _map_insert(map_t map, pair_t x, cmp_t cmp) { 787 | uintptr_t *size = &map[0].first; 788 | uintptr_t *cnt = &map[0].second; 789 | if(*cnt >= *size) return false; 790 | map[++*cnt] = x; 791 | map_sort(map, 1, cmp); 792 | return true; 793 | } 794 | 795 | /** Insert an entry into a map. 796 | * O(log n) average time, up to O(n). 797 | * @return true if inserted 798 | * @snippet map.c map 799 | */ 800 | bool map_insert(map_t map, pair_t x) { 801 | return _map_insert(map, x, key_cmp); 802 | } 803 | 804 | /** Insert an entry into a string map. */ 805 | bool string_map_insert(map_t map, pair_t x) { 806 | return _map_insert(map, x, string_cmp); 807 | } 808 | 809 | static 810 | bool _map_merge(map_t map, pair_t *x, size_t n, cmp_t cmp) { 811 | if(n == 0) return true; 812 | uintptr_t *size = &map[0].first; 813 | uintptr_t *cnt = &map[0].second; 814 | pair_t *elems = map_elems(map); 815 | assert_error(check_map(map, cmp)); 816 | assert_error(check_order(x, n, cmp)); 817 | if(*cnt + n > *size) return false; 818 | if(*cnt == 0) { 819 | memcpy(elems, x, n * sizeof(pair_t)); 820 | *cnt = n; 821 | return true; 822 | } 823 | uintptr_t bit = 1; 824 | while(n) { 825 | uintptr_t m = 1 << __builtin_ctz(*cnt); 826 | if(m > n) { 827 | memcpy(&elems[*cnt], x, n * sizeof(pair_t)); 828 | *cnt += n; 829 | break; 830 | } 831 | memcpy(&elems[*cnt], x, m * sizeof(pair_t)); 832 | x += m; 833 | n -= m; 834 | *cnt += m; 835 | map_sort(map, bit, cmp); 836 | bit = m; // avoid redundant sorting 837 | } 838 | assert_error(check_map(map, cmp)); 839 | return true; 840 | } 841 | 842 | /** Merge `n` sorted entries at `x` into the map. */ 843 | bool map_merge(map_t map, pair_t *x, size_t n) { 844 | return _map_merge(map, x, n, key_cmp); 845 | } 846 | 847 | /** Merge `n` sorted string entries at `x` into the map. */ 848 | bool string_map_merge(map_t map, pair_t *x, size_t n) { 849 | return _map_merge(map, x, n, string_cmp); 850 | } 851 | 852 | bool _map_union(map_t a, map_t b, cmp_t cmp) { 853 | uintptr_t 854 | na = *map_cnt(a), 855 | nb = *map_cnt(b), x = nb; 856 | if(nb == 0) return true; 857 | if(na + nb > map_size(a)) return false; 858 | 859 | pair_t *bs = map_elems(b); 860 | uintptr_t bit = (uintptr_t)1 << int_log2l(x + 1); 861 | while(x) { 862 | if(x & bit) { 863 | _map_merge(a, bs, bit, cmp); 864 | bs += bit; 865 | x &= ~bit; 866 | } 867 | bit >>= 1; 868 | } 869 | assert_error(check_map(a, cmp)); 870 | return true; 871 | } 872 | 873 | /** Merge both maps into `a`. */ 874 | bool map_union(map_t a, map_t b) { 875 | return _map_union(a, b, key_cmp); 876 | } 877 | 878 | /** Merge both string maps into `a`. */ 879 | bool string_map_union(map_t a, map_t b) { 880 | return _map_union(a, b, string_cmp); 881 | } 882 | 883 | TEST(map_union) { 884 | COUNTUP(n, 9) { 885 | MAP(a, 16); 886 | MAP(b, 8); 887 | COUNTUP(i, 16-n) { 888 | pair_t 889 | pa = {i*2, 0}, 890 | pb = {i*2 + 1, 1}; 891 | map_insert(a, pa); 892 | if(i < n) map_insert(b, pb); 893 | } 894 | map_union(a, b); 895 | print_map(a); 896 | } 897 | return 0; 898 | } 899 | 900 | static 901 | bool _map_replace_insert(map_t map, pair_t x, cmp_t cmp) { 902 | pair_t *p = map_find(map, x.first); 903 | if(p) { 904 | p->second = x.second; 905 | return true; 906 | } else { 907 | return _map_insert(map, x, cmp); 908 | } 909 | } 910 | 911 | TEST(map_merge) { 912 | pair_t map[17] = { 913 | {LENGTH(map) - 1, 5}, 914 | {0,0}, {4,0}, {12, 0}, {16, 0}, 915 | {8, 1}}; 916 | pair_t x[11]; 917 | FOREACH(i, x) { 918 | x[i].first = i * 2 + 1; 919 | x[i].second = 10 + 10 * i; 920 | } 921 | map_merge(map, x, LENGTH(x)); 922 | print_map(map); 923 | pair_t *r = map_find(map, 16); 924 | if(r) { 925 | printf("r = {%d, %d}\n", (int)r->first, (int)r->second); 926 | } else { 927 | printf("r = NULL\n"); 928 | } 929 | return 0; 930 | } 931 | 932 | /** Insert into a map, replacing any entry with the same key. */ 933 | bool map_replace_insert(map_t map, pair_t x) { 934 | return _map_replace_insert(map, x, key_cmp); 935 | } 936 | 937 | /** Insert into a string map, replacing any entry with the same key. */ 938 | bool string_map_replace_insert(map_t map, pair_t x) { 939 | return _map_replace_insert(map, x, string_cmp); 940 | } 941 | 942 | /** Look up an entry in a map. 943 | * O((log n)^2) time 944 | * O(log n) time if the map is fully sorted. 945 | * @see map_sort_full 946 | * @return a pointer to the entry if found, otherwise NULL. 947 | */ 948 | pair_t *map_find(map_t map, uintptr_t key) { 949 | uintptr_t x = *map_cnt(map); 950 | pair_t *elems = map_elems(map); 951 | pair_t *result = NULL; 952 | uintptr_t bit = 1ll << int_log2(x); 953 | uintptr_t a = 0; 954 | size_t est = bit >> 1; 955 | while(x) { 956 | if(x & bit) { 957 | x &= ~bit; 958 | if((result = find_last(&elems[a], bit, key, &est))) break; 959 | a += bit; 960 | } 961 | est >>= 1; 962 | bit >>= 1; 963 | } 964 | return result; 965 | } 966 | 967 | pair_t *map_find_recent(map_t map, uintptr_t key) { 968 | uintptr_t x = *map_cnt(map); 969 | pair_t *elems = map_elems(map); 970 | pair_t *result = NULL; 971 | uintptr_t bit = 1; 972 | size_t est = 1; 973 | while(x) { 974 | if(x & bit) { 975 | x &= ~bit; 976 | if((result = find_last(&elems[x], bit, key, &est))) break; 977 | } 978 | est = (est << 1) + 1; 979 | bit <<= 1; 980 | } 981 | return result; 982 | } 983 | 984 | pair_t *map_find_sorted(map_t map, uintptr_t key) { 985 | return find_last(map_elems(map), *map_cnt(map), key, NULL); 986 | } 987 | 988 | /** Return the value for a key in a map. 989 | * Raises an error if the key in not found. 990 | */ 991 | uintptr_t map_get(map_t map, uintptr_t key) { 992 | pair_t *x = map_find(map, key); 993 | assert_error(x); 994 | return x->second; 995 | } 996 | 997 | /** Look up an entry in a string map. 998 | * O((log n)^2) time 999 | * O(log n) time if the map is fully sorted. 1000 | * @see map_sort_full 1001 | * @return pointer to the entry if found, otherwise NULL 1002 | */ 1003 | pair_t *string_map_find(map_t map, const char *key) { 1004 | uintptr_t x = *map_cnt(map); 1005 | pair_t *elems = map_elems(map); 1006 | pair_t *result = NULL; 1007 | uintptr_t bit = 1; 1008 | while(x) { 1009 | if(x & bit) { 1010 | x &= ~bit; 1011 | if((result = find_last_string(&elems[x], bit, key))) break; 1012 | } 1013 | bit <<= 1; 1014 | } 1015 | return result; 1016 | } 1017 | 1018 | /** Look up an entry in a string segment map. 1019 | * O((log n)^2) time 1020 | * O(log n) time if the map is fully sorted. 1021 | * @see map_sort_full 1022 | * @return pointer to the entry if found, otherwise NULL 1023 | */ 1024 | pair_t *seg_map_find(map_t map, seg_t key) { 1025 | uintptr_t x = *map_cnt(map); 1026 | pair_t *elems = map_elems(map); 1027 | pair_t *result = NULL; 1028 | uintptr_t bit = 1; 1029 | while(x) { 1030 | if(x & bit) { 1031 | x &= ~bit; 1032 | if((result = find_last_seg(&elems[x], bit, key))) break; 1033 | } 1034 | bit <<= 1; 1035 | } 1036 | return result; 1037 | } 1038 | 1039 | /** Look up a value in a map. 1040 | * O(n) time 1041 | * @return pointer to the value if found, otherwise NULL 1042 | */ 1043 | pair_t *map_find_value(map_t map, uintptr_t value) { 1044 | uintptr_t x = *map_cnt(map); 1045 | pair_t *elems = map_elems(map); 1046 | pair_t *result = NULL; 1047 | COUNTUP(i, x) { 1048 | if(elems[i].second == value) { 1049 | result = &elems[i]; 1050 | break; 1051 | } 1052 | } 1053 | return result; 1054 | } 1055 | 1056 | #if INTERFACE 1057 | /** Print a map. */ 1058 | #define print_map(map) _print_map(#map, map) 1059 | #endif 1060 | 1061 | void _print_map(char *name, map_t map) { 1062 | size_t cnt = *map_cnt(map); 1063 | printf("%s[%" PRIuPTR "] = ", name ? name : "map", cnt); 1064 | print_pairs(map_elems(map), cnt); 1065 | } 1066 | 1067 | #if INTERFACE 1068 | /** Print a string map. */ 1069 | #define print_string_map(map) _print_string_map(#map, map) 1070 | #endif 1071 | 1072 | void _print_string_map(char *name, map_t map) { 1073 | size_t cnt = *map_cnt(map); 1074 | printf("%s[%" PRIuPTR "] = ", name ? name : "map", cnt); 1075 | print_string_pairs(map_elems(map), cnt); 1076 | } 1077 | 1078 | #define find_test(map, key) _find_test((map), #map, (key)) 1079 | 1080 | pair_t *_find_test(map_t map, char *name, uintptr_t key) { 1081 | pair_t *p = map_find(map, key); 1082 | printf("map_find(%s, %" PRIuPTR ") = ", name, key); 1083 | if(p) { 1084 | printf("{%" PRIuPTR ", %" PRIuPTR "}\n", p->first, p->second); 1085 | } else { 1086 | printf("NULL\n"); 1087 | } 1088 | return p; 1089 | } 1090 | 1091 | TEST(map) { 1092 | /** [map] */ 1093 | int ret = 0; 1094 | MAP(map, 32); 1095 | int elems[] = {2, 5, 8, 3, 1, 0, 4, 7, 6, 9, 10, 15, 13, 11, 12}; 1096 | FOREACH(i, elems) { 1097 | pair_t p = {elems[i], i}; 1098 | map_insert(map, p); 1099 | } 1100 | print_map(map); 1101 | /** [map] */ 1102 | 1103 | FOREACH(i, elems) { 1104 | pair_t *p = find_test(map, elems[i]); 1105 | if(p) { 1106 | if(p->second != i) { 1107 | printf("MISMATCH\n"); 1108 | ret = -1; 1109 | } 1110 | } else { 1111 | printf("MISSING\n"); 1112 | ret = -1; 1113 | } 1114 | } 1115 | 1116 | if(!check_map(map, key_cmp)) ret = -2; 1117 | return ret; 1118 | } 1119 | 1120 | static bool expect(map_t map, uintptr_t key, uintptr_t x) { 1121 | pair_t *p = map_find(map, key); 1122 | if(!p) { 1123 | printf("expect(%" PRIuPTR ", %" PRIuPTR "): missing\n", key, x); 1124 | return false; 1125 | } else if(p->second != x) { 1126 | printf("expect(%" PRIuPTR ", %" PRIuPTR "): %" PRIuPTR "\n", key, x, p->second); 1127 | return false; 1128 | } 1129 | return true; 1130 | } 1131 | 1132 | TEST(map_stack_behavior) { 1133 | #define M 2 1134 | #define N 8 1135 | MAP(map, (M * N)); 1136 | COUNTUP(i, M) { 1137 | COUNTUP(j, N) { 1138 | pair_t x = {j, i}; 1139 | map_insert(map, x); 1140 | } 1141 | COUNTUP(j, N) { 1142 | if(!expect(map, j, i)) { 1143 | print_map(map); 1144 | return -1; 1145 | } 1146 | } 1147 | } 1148 | return 0; 1149 | #undef M 1150 | #undef N 1151 | } 1152 | 1153 | TEST(rotate) { 1154 | pair_t arr[7]; 1155 | FOREACH(i, arr) { 1156 | arr[i].first = i; 1157 | arr[i].second = i; 1158 | }; 1159 | pair_t tmp[LENGTH(arr)]; 1160 | 1161 | FOREACH(i, arr) { 1162 | memcpy(tmp, arr, sizeof(arr)); 1163 | rotate(tmp, tmp+i, LENGTH(tmp)); 1164 | print_pairs(tmp, LENGTH(tmp)); 1165 | } 1166 | return 0; 1167 | } 1168 | 1169 | /* 1170 | // TEST(rotate_speed) { 1171 | size_t cnt = 1 << 28; 1172 | pair_t *arr = calloc(cnt, sizeof(pair_t)); 1173 | rotate(arr, arr + (1 << 27) - 1, cnt); 1174 | free(arr); 1175 | return 0; 1176 | } 1177 | */ 1178 | 1179 | TEST(string_map) { 1180 | char *strings[] = { 1181 | "one", 1182 | "two", 1183 | "three", 1184 | "abc", 1185 | "cow", 1186 | "bat", 1187 | "cheese", 1188 | "quilt" 1189 | }; 1190 | MAP(map, LENGTH(strings)); 1191 | FOREACH(i, strings) { 1192 | pair_t x = {(uintptr_t)strings[i], i}; 1193 | string_map_insert(map, x); 1194 | } 1195 | print_string_map(map); 1196 | 1197 | pair_t *p = string_map_find(map, "cow"); 1198 | if(!p) return -1; 1199 | printf("%s => %" PRIuPTR "\n", (char *)p->first, p->second); 1200 | if(p->second != 4) return -1; 1201 | return 0; 1202 | } 1203 | 1204 | bool check_order(pair_t *elems, size_t size, cmp_t cmp) { 1205 | if(size <= 1) return true; 1206 | uintptr_t prev = elems[0].first; 1207 | RANGEUP(i, 1, size) { 1208 | uintptr_t x = elems[i].first; 1209 | if(cmp(x, prev)) { 1210 | printf("elems[%d] < prev\n", (int)i); 1211 | return false; 1212 | } 1213 | prev = x; 1214 | } 1215 | return true; 1216 | } 1217 | 1218 | bool check_map(map_t map, cmp_t cmp) { 1219 | uintptr_t x = *map_cnt(map); 1220 | pair_t *elems = map_elems(map); 1221 | uintptr_t bit = 1; 1222 | while(x) { 1223 | if(x & bit) { 1224 | x &= ~bit; 1225 | if(!check_order(&elems[x], bit, cmp)) return false; 1226 | } 1227 | bit <<= 1; 1228 | } 1229 | return true; 1230 | } 1231 | 1232 | map_t alloc_map(uintptr_t size) { 1233 | map_t map = (map_t)malloc(sizeof(pair_t) * (size + 1)); 1234 | map[0] = (pair_t) {size, 0}; 1235 | return map; 1236 | } 1237 | --------------------------------------------------------------------------------