Ver código fonte

Use Arena Allocator for tests

rexim 3 anos atrás
pai
commit
0b3a34596b
2 arquivos alterados com 241 adições e 61 exclusões
  1. 82 61
      test.c
  2. 159 0
      thirdparty/arena.h

+ 82 - 61
test.c

@@ -4,15 +4,6 @@
 #include <string.h>
 #include <errno.h>
 
-#define STB_IMAGE_WRITE_IMPLEMENTATION
-#include "./stb_image_write.h"
-
-#define STB_IMAGE_IMPLEMENTATION
-#include "./stb_image.h"
-
-#define OLIVEC_IMPLEMENTATION
-#include "olive.c"
-
 #define return_defer(value) do { result = (value); goto defer; } while (0)
 #define UNUSED(x) (void)(x)
 #define UNIMPLEMENTED(message) \
@@ -26,6 +17,39 @@
         exit(1); \
     } while (0)
 
+#define ARENA_IMPLEMENTATION
+#include "./arena.h"
+
+static Arena default_arena = {0};
+static Arena *context_arena = &default_arena;
+
+static void *context_alloc(size_t size)
+{
+    assert(context_arena);
+    return arena_alloc(context_arena, size);
+}
+
+static void *context_realloc(void *oldp, size_t oldsz, size_t newsz)
+{
+    if (newsz <= oldsz) return oldp;
+    return memcpy(context_alloc(newsz), oldp, oldsz);
+}
+
+#define STBI_MALLOC context_alloc
+#define STBI_FREE UNUSED
+#define STBI_REALLOC_SIZED context_realloc
+#define STB_IMAGE_IMPLEMENTATION
+#include "./stb_image.h"
+
+#define STBIW_MALLOC STBI_MALLOC
+#define STBIW_FREE STBI_FREE
+#define STBIW_REALLOC_SIZED STBI_REALLOC_SIZED
+#define STB_IMAGE_WRITE_IMPLEMENTATION
+#include "./stb_image_write.h"
+
+#define OLIVEC_IMPLEMENTATION
+#include "olive.c"
+
 // TODO: custom size for different tests
 #define WIDTH 128
 #define HEIGHT 128
@@ -82,67 +106,60 @@ typedef enum {
 
 Replay_Result replay_test_case(const char *program_path, const char *expected_file_path, const char *actual_file_path, const char *diff_file_path)
 {
-    Replay_Result result = REPLAY_PASSED;
-    uint32_t *expected_pixels = NULL;
-
-    {
-        int expected_width, expected_height;
-        expected_pixels = (uint32_t*) stbi_load(expected_file_path, &expected_width, &expected_height, NULL, 4);
-        if (expected_pixels == NULL) {
-            fprintf(stderr, "%s: ERROR: could not read the file: %s\n", expected_file_path, strerror(errno));
-            if (errno == ENOENT) {
-                fprintf(stderr, "%s: HINT: Consider running `$ %s record` to create it\n", expected_file_path, program_path);
-            }
-            return_defer(REPLAY_ERRORED);
+    int expected_width, expected_height;
+    uint32_t * expected_pixels = (uint32_t*) stbi_load(expected_file_path, &expected_width, &expected_height, NULL, 4);
+    if (expected_pixels == NULL) {
+        fprintf(stderr, "%s: ERROR: could not read the file: %s\n", expected_file_path, strerror(errno));
+        if (errno == ENOENT) {
+            fprintf(stderr, "%s: HINT: Consider running `$ %s record` to create it\n", expected_file_path, program_path);
         }
+        return(REPLAY_ERRORED);
+    }
 
-        // TODO: it would be cool if "unexpected image size" error would generate the image diff as well
-        // The size of the image diff should be max(expected_width, actual_width) by max(expected_height, actual_height) with the paddings on the right and bottom edges filled with ERROR_COLOR
-        if (expected_width != WIDTH || expected_height != HEIGHT) {
-            fprintf(stderr, "%s: TEST FAILURE: unexpected image size. Expected %dx%d, but got %dx%d\n",
-                    expected_file_path, expected_width, expected_height, WIDTH, HEIGHT);
-            return_defer(REPLAY_FAILED);
-        }
+    // TODO: it would be cool if "unexpected image size" error would generate the image diff as well
+    // The size of the image diff should be max(expected_width, actual_width) by max(expected_height, actual_height) with the paddings on the right and bottom edges filled with ERROR_COLOR
+    if (expected_width != WIDTH || expected_height != HEIGHT) {
+        fprintf(stderr, "%s: TEST FAILURE: unexpected image size. Expected %dx%d, but got %dx%d\n",
+        expected_file_path, expected_width, expected_height, WIDTH, HEIGHT);
+        return(REPLAY_FAILED);
+    }
 
-        bool failed = false;
-        for (size_t y = 0; y < HEIGHT; ++y) {
-            for (size_t x = 0; x < WIDTH; ++x) {
-                uint32_t expected_pixel = expected_pixels[y*WIDTH + x];
-                uint32_t actual_pixel = actual_pixels[y*WIDTH + x];
-                if (expected_pixel != actual_pixel) {
-                    diff_pixels[y*WIDTH + x] = ERROR_COLOR;
-                    failed = true;
-                } else {
-                    diff_pixels[y*WIDTH + x] = expected_pixel;
-                }
+    bool failed = false;
+    for (size_t y = 0; y < HEIGHT; ++y) {
+        for (size_t x = 0; x < WIDTH; ++x) {
+            uint32_t expected_pixel = expected_pixels[y*WIDTH + x];
+            uint32_t actual_pixel = actual_pixels[y*WIDTH + x];
+            if (expected_pixel != actual_pixel) {
+                diff_pixels[y*WIDTH + x] = ERROR_COLOR;
+                failed = true;
+            } else {
+                diff_pixels[y*WIDTH + x] = expected_pixel;
             }
         }
+    }
 
-        if (failed) {
-            fprintf(stderr, "%s: TEST FAILURE: unexpected pixels in generated image\n", expected_file_path);
-
-            if (!stbi_write_png(actual_file_path, WIDTH, HEIGHT, 4, actual_pixels, sizeof(uint32_t)*WIDTH)) {
-                fprintf(stderr, "ERROR: could not generate image with actual pixels %s: %s\n", actual_file_path, strerror(errno));
-                return_defer(REPLAY_ERRORED);
-            }
+    if (failed) {
+        fprintf(stderr, "%s: TEST FAILURE: unexpected pixels in generated image\n", expected_file_path);
 
-            if (!stbi_write_png(diff_file_path, WIDTH, HEIGHT, 4, diff_pixels, sizeof(uint32_t)*WIDTH)) {
-                fprintf(stderr, "ERROR: could not generate diff image %s: %s\n", diff_file_path, strerror(errno));
-                return_defer(REPLAY_ERRORED);
-            }
+        if (!stbi_write_png(actual_file_path, WIDTH, HEIGHT, 4, actual_pixels, sizeof(uint32_t)*WIDTH)) {
+            fprintf(stderr, "ERROR: could not generate image with actual pixels %s: %s\n", actual_file_path, strerror(errno));
+            return(REPLAY_ERRORED);
+        }
 
-            fprintf(stderr, "%s: HINT: See actual image %s\n", expected_file_path, actual_file_path);
-            fprintf(stderr, "%s: HINT: See diff image %s\n", expected_file_path, diff_file_path);
-            fprintf(stderr, "%s: HINT: If this behaviour is intentional confirm that by updating the image with `$ %s record`\n", expected_file_path, program_path);
-            return_defer(REPLAY_FAILED);
+        if (!stbi_write_png(diff_file_path, WIDTH, HEIGHT, 4, diff_pixels, sizeof(uint32_t)*WIDTH)) {
+            fprintf(stderr, "ERROR: could not generate diff image %s: %s\n", diff_file_path, strerror(errno));
+            return(REPLAY_ERRORED);
         }
 
-        printf("%s OK\n", expected_file_path);
+        fprintf(stderr, "%s: HINT: See actual image %s\n", expected_file_path, actual_file_path);
+        fprintf(stderr, "%s: HINT: See diff image %s\n", expected_file_path, diff_file_path);
+        fprintf(stderr, "%s: HINT: If this behaviour is intentional confirm that by updating the image with `$ %s record`\n", expected_file_path, program_path);
+        return(REPLAY_FAILED);
     }
 
-defer:
-    if (expected_pixels) stbi_image_free(expected_pixels);
-    return result;
+    printf("%s OK\n", expected_file_path);
+
+    return(REPLAY_PASSED);
 }
 
 typedef struct {
@@ -238,6 +255,8 @@ Test_Case test_cases[] = {
 
 int main(int argc, char **argv)
 {
+    int result = 0;
+
     assert(argc >= 1);
     const char *program_path = argv[0];
     bool record = argc >= 2 && strcmp(argv[1], "record") == 0;
@@ -245,10 +264,12 @@ int main(int argc, char **argv)
     for (size_t i = 0; i < TEST_CASES_COUNT; ++i) {
         test_cases[i].generate_actual_pixels();
         if (record) {
-            if (!record_test_case(test_cases[i].expected_file_path)) return 1;
+            if (!record_test_case(test_cases[i].expected_file_path)) return_defer(1);
         } else {
-            if (replay_test_case(program_path, test_cases[i].expected_file_path, test_cases[i].actual_file_path, test_cases[i].diff_file_path) == REPLAY_ERRORED) return 1;
+            if (replay_test_case(program_path, test_cases[i].expected_file_path, test_cases[i].actual_file_path, test_cases[i].diff_file_path) == REPLAY_ERRORED) return_defer(1);
         }
     }
-    return 0;
+defer:
+    arena_free(&default_arena);
+    return result;
 }

+ 159 - 0
thirdparty/arena.h

@@ -0,0 +1,159 @@
+// Copyright 2022 Alexey Kutepov <[email protected]>
+
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef ARENA_H_
+#define ARENA_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifndef ARENA_ASSERT
+#include <assert.h>
+#define ARENA_ASSERT assert
+#endif
+
+#define ARENA_BACKEND_LIBC_MALLOC 0
+#define ARENA_BACKEND_LINUX_MMAP 1
+#define ARENA_BACKEND_WIN32_VIRTUALALLOC 2
+#define ARENA_BACKEND_WASM_HEAPBASE 3
+
+#ifndef ARENA_BACKEND
+#define ARENA_BACKEND ARENA_BACKEND_LIBC_MALLOC
+#endif // ARENA_BACKEND
+
+typedef struct Region Region;
+
+struct Region {
+    Region *next;
+    size_t count;
+    size_t capacity;
+    uintptr_t data[];
+};
+
+typedef struct {
+    Region *begin, *end;
+} Arena;
+
+#define REGION_DEFAULT_CAPACITY (8*1024)
+
+Region *new_region(size_t capacity);
+void free_region(Region *r);
+
+// TODO: snapshot/rewind capability for the arena
+// - Snapshot should be combination of a->end and a->end->count. 
+// - Rewinding should be restoring a->end and a->end->count from the snapshot and
+// setting count-s of all the Region-s after the remembered a->end to 0.
+void *arena_alloc(Arena *a, size_t size_bytes);
+void arena_reset(Arena *a);
+void arena_free(Arena *a);
+
+#endif // ARENA_H_
+
+#ifdef ARENA_IMPLEMENTATION
+
+#if ARENA_BACKEND == ARENA_BACKEND_LIBC_MALLOC
+#include <stdlib.h>
+
+// TODO: instead of accepting specific capacity new_region() should accept the size of the object we want to fit into the region
+// It should be up to new_region() to decide the actual capacity to allocate
+Region *new_region(size_t capacity)
+{
+    size_t size_bytes = sizeof(Region) + sizeof(uintptr_t)*capacity;
+    // TODO: it would be nice if we could guarantee that the regions are allocated by ARENA_BACKEND_LIBC_MALLOC are page aligned
+    Region *r = malloc(size_bytes);
+    ARENA_ASSERT(r);
+    r->next = NULL;
+    r->count = 0;
+    r->capacity = capacity;
+    return r;
+}
+
+void free_region(Region *r)
+{
+    free(r);
+}
+#elif ARENA_BACKEND == ARENA_BACKEND_LINUX_MMAP
+#  error "TODO: Linux mmap backend is not implemented yet"
+#elif ARENA_BACKEND == ARENA_BACKEND_WIN32_VIRTUALALLOC
+#  error "TODO: Win32 VirtualAlloc backend is not implemented yet"
+#elif ARENA_BACKEND == ARENA_BACKEND_WASM_HEAPBASE
+#  error "TODO: WASM __heap_base backend is not implemented yet"
+#else
+#  error "Unknown Arena backend"
+#endif
+
+// TODO: add debug statistic collection mode for arena
+// Should collect things like:
+// - How many times new_region was called
+// - How many times existing region was skipped
+// - How many times allocation exceeded REGION_DEFAULT_CAPACITY
+
+void *arena_alloc(Arena *a, size_t size_bytes)
+{
+    size_t size = (size_bytes + sizeof(uintptr_t) - 1)/sizeof(uintptr_t);
+
+    if (a->end == NULL) {
+        ARENA_ASSERT(a->begin == NULL);
+        size_t capacity = REGION_DEFAULT_CAPACITY;
+        if (capacity < size) capacity = size;
+        a->end = new_region(capacity);
+        a->begin = a->end;
+    }
+
+    while (a->end->count + size > a->end->capacity && a->end->next != NULL) {
+        a->end = a->end->next;
+    }
+
+    if (a->end->count + size > a->end->capacity) {
+        ARENA_ASSERT(a->end->next == NULL);
+        size_t capacity = REGION_DEFAULT_CAPACITY;
+        if (capacity < size) capacity = size;
+        a->end->next = new_region(capacity);
+        a->end = a->end->next;
+    }
+
+    void *result = &a->end->data[a->end->count];
+    a->end->count += size;
+    return result;
+}
+
+void arena_reset(Arena *a)
+{
+    for (Region *r = a->begin; r != NULL; r = r->next) {
+        r->count = 0;
+    }
+
+    a->end = a->begin;
+}
+
+void arena_free(Arena *a)
+{
+    Region *r = a->begin;
+    while (r) {
+        Region *r0 = r;
+        r = r->next;
+        free_region(r0);
+    }
+    a->begin = NULL;
+    a->end = NULL;
+}
+
+#endif // ARENA_IMPLEMENTATION