Mercurial > repo
view interps/cfunge/cfunge-src/src/funge-space/funge-space.c @ 12518:2d8fe55c6e65 draft default tip
<int-e> learn The password of the month is release incident pilot.
author | HackEso <hackeso@esolangs.org> |
---|---|
date | Sun, 03 Nov 2024 00:31:02 +0000 |
parents | 859f9b4339e6 |
children |
line wrap: on
line source
/* -*- mode: C; coding: utf-8; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- * * cfunge - A standard-conforming Befunge93/98/109 interpreter in C. * Copyright (C) 2008-2009 Arvid Norlander <anmaster AT tele2 DOT se> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at the proxy's option) any later version. Arvid Norlander is a * proxy who can decide which future versions of the GNU General Public * License can be used. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * How it works: * * We use a static array for the commonly used funge space near (0,0). * * The array is slightly offset to include a bit of the negative funge space * too. * * Outside this array we use a hash library. */ #include "../global.h" #include "funge-space.h" #include "../diagnostic.h" #include "../../lib/libghthash/ght_hash_table.h" #include "../../lib/libghthash/cfunge_mempool.h" #include <assert.h> #include <errno.h> #include <stdio.h> /* fclose, fileno, fopen, fputs, fwrite, ... */ #include <stdlib.h> #include <string.h> /* strerror */ #include <unistd.h> /* _POSIX_MAPPED_FILES, close, fstat */ #include <sys/types.h> /* fstat, open */ #include <sys/stat.h> /* fstat, open */ #include <fcntl.h> /* open, posix_fallocate */ #if !defined(_POSIX_MAPPED_FILES) || (_POSIX_MAPPED_FILES < 1) # error "cfunge needs a working mmap(), which this system claims it lacks." #endif #include <sys/mman.h> /* mmap, munmap, posix_madvise */ /// Initial size for hash table (main) #define FUNGESPACE_INITIAL_SIZE 0x40000 /// Initial size for hash table (column count) #define FUNGECOUNT_COL_INITIAL_SIZE 0x20000 /// Initial size for hash table (row count) #define FUNGECOUNT_ROW_INITIAL_SIZE 0x20000 typedef struct fungeSpace { /// These two form a rectangle for the program size funge_vector topLeftCorner; funge_vector bottomRightCorner; /// And this is the main hash table. ght_fspace_hash_table_t * restrict entries; #ifdef CFUN_EXACT_BOUNDS /// Hash tables for cell count in columns. ght_fspacecount_hash_table_t * restrict col_count; /// Hash tables for cell count in rows. ght_fspacecount_hash_table_t * restrict row_count; /// Are the bounds stored currently exact already? bool boundsexact; #endif /// Used during loading to handle 0,0 not being least point. bool boundsvalid; } fungeSpace; /// Funge-space storage. static fungeSpace fspace = { .topLeftCorner = {0, 0}, .bottomRightCorner = {0, 0}, .entries = NULL, #ifdef CFUN_EXACT_BOUNDS .col_count = NULL, .row_count = NULL, .boundsexact = true, #endif .boundsvalid = false }; #define FUNGESPACE_STATIC_OFFSET_X 64 #define FUNGESPACE_STATIC_OFFSET_Y 64 // Note that this must be true to not break code below: // (FUNGESPACE_STATIC_X * FUNGESPACE_STATIC_Y * sizeof(funge_cell)) % 128 == 0 // Further cfun_static_space must be aligned on 16 byte boundary. #define FUNGESPACE_STATIC_X 512 #define FUNGESPACE_STATIC_Y 1024 #define FUNGESPACE_RANGE_CHECK(rx, ry) \ (((rx) < FUNGESPACE_STATIC_X) && ((ry) < FUNGESPACE_STATIC_Y)) #define STATIC_COORD(rx, ry) ((rx)+(ry)*FUNGESPACE_STATIC_X) /** * Static array for core Funge Space. * * We need to give it an asm name here, or the non-PIC inline asm below won't * work properly in some cases. */ static funge_cell cfun_static_space[FUNGESPACE_STATIC_X * FUNGESPACE_STATIC_Y] #ifdef CFUNGE_COMP_GCC_COMPAT __asm__("cfun_static_space") #endif FUNGE_ATTR_ALIGNED(16); #ifdef CFUN_EXACT_BOUNDS /// Non-Space counts for each column. static funge_unsigned_cell cfun_static_use_count_col[FUNGESPACE_STATIC_X]; /// Non-Space counts for each row. static funge_unsigned_cell cfun_static_use_count_row[FUNGESPACE_STATIC_Y]; /** If difference is larger than this we switch to a different bounds minimising * algorithm */ # define SIMPLEBOUNDS_MAX 0x10000 /** * Decide if difference is too large or not, m_dim is either x or y. * Used in wrapping code to decide if we should minimise the bounds. * Check fspace.boundsexact first! */ # define BOUNDS_TOO_LARGE(m_dim) \ ((fspace.bottomRightCorner.m_dim - fspace.topLeftCorner.m_dim) > SIMPLEBOUNDS_MAX) #endif /* * Logic to select SSE asm, intrinsics or pure C versions. * * FSPACE_CREATE_SSE - SSE code. * FSPACE_CREATE_SSE_ASM - Inline SSE asm for x86_64. * FSPACE_CREATE_SSE_INT - SSE intrinsics. * FSPACE_ICC_INTRINSICS - ICC SSE intrinsics. * FSPACE_GCC_INTRINSICS - GCC SSE intrinsics. */ #undef FSPACE_CREATE_SSE #undef FSPACE_CREATE_SSE_ASM #undef FSPACE_CREATE_SSE_INT #undef FSPACE_ICC_INTRINSICS #undef FSPACE_GCC_INTRINSICS #if defined(CFUNGE_COMP_GCC_COMPAT) && defined(CFUNGE_ARCH_X86) && defined(__SSE__) # define FSPACE_CREATE_SSE 1 #endif #if defined(FSPACE_CREATE_SSE) && defined(__SSE2__) && defined(CFUNGE_ARCH_X86_64) # define FSPACE_CREATE_SSE_ASM 1 #endif #if defined(FSPACE_CREATE_SSE) && !defined(FSPACE_CREATE_SSE_ASM) # define FSPACE_CREATE_SSE_INT # ifdef CFUNGE_COMP_ICC # define FSPACE_ICC_INTRINSICS # else # define FSPACE_GCC_INTRINSICS # endif #endif // Handle ICC/GCC differences. #ifdef FSPACE_ICC_INTRINSICS # include <xmmintrin.h> #endif #ifdef FSPACE_CREATE_SSE typedef int32_t v4si __attribute__((vector_size(16))); # ifdef FSPACE_GCC_INTRINSICS typedef float v4sf __attribute__((vector_size(16))); # endif # ifdef USE32 # define FUNGESPACE_DATASIZE_STR "4" static const v4si fspace_vector_init = {0x20, 0x20, 0x20, 0x20}; # elif defined(USE64) # define FUNGESPACE_DATASIZE_STR "8" static const v4si fspace_vector_init = {0x20, 0x0, 0x20, 0x0}; # else # error "Unknown funge space data type size." # endif #endif /********************************* * Setup and teardown code here. * *********************************/ FUNGE_ATTR_FAST bool fungespace_create(void) { // Mark the static space area as pointer-free when using Boehm-GC. // FIXME: Not sure the arguments are correct.. cf_mark_static_noptr(&cfun_static_space, &cfun_static_space[FUNGESPACE_STATIC_X * FUNGESPACE_STATIC_Y]); cf_mark_static_noptr(&cfun_static_use_count_col, &cfun_static_use_count_col[FUNGESPACE_STATIC_X]); cf_mark_static_noptr(&cfun_static_use_count_row, &cfun_static_use_count_row[FUNGESPACE_STATIC_Y]); // Fill static array with spaces. // When possible use movntps, which reduces cache pollution (because it acts // as if the memory was write combining). // // GCC's __builtin_ia32_movntps refuses to load thing in the fastest way, so // provide an inline asm version too. Also __builtin_ia32_movntps generates // terrible code for -O0. Worse than plain C variant with no vectorisation // at all. // // Further using %[space] in the movntps resulted in the invalid asm: // cfun_static_space(%rip)(%rax) // I still had to list it as output though. // // For the pure-ISO C variant, something like -ftree-vectorize (GCC) or // -xP (icc, generate for SSE/SSE2/SSE3) can be used to at least speed up // the execution a bit. Though you will still get cache pollution. // // For the PIC variant GCC's i constraint generate a $ in front of the number // which doesn't work here. So we do it manually with macros that expand to // the correct sizes. // // I'm not sure if the sfence is needed. The AMD and Intel docs are a bit // unclear about this. And it doesn't seem to be needed in practise. However // I'd rather go for the safe alternative. #ifdef FSPACE_CREATE_SSE_ASM // ICC can't deal with embedded "cfun_static_space". # if defined(__pic__) || defined(__PIC__) || defined(CFUNGE_COMP_ICC) __asm__ volatile ("\ leaq %[space],%%rax\n\ leaq "FUNGESPACE_DATASIZE_STR "*"FUNGE_CPP_STRINGIFY(FUNGESPACE_STATIC_X) "*"FUNGE_CPP_STRINGIFY(FUNGESPACE_STATIC_Y)"+%[space],%%rdx\n\ movdqa %[mask],%%xmm0\n\ .p2align 4,,7\n\ .Lcf_fungespace_create_init_loop:\n\ movntps %%xmm0,(%%rax)\n\ addq $16,%%rax\n\ cmpq %%rdx,%%rax\n\ jne .Lcf_fungespace_create_init_loop\n\ sfence" : [space] "=o"(cfun_static_space) : [mask] "m"(fspace_vector_init) : "rax", "rdx", "xmm0"); # else __asm__ volatile ("\ xor %%eax,%%eax\n\ movdqa %[mask],%%xmm0\n\ .p2align 4,,7\n\ .Lcf_fungespace_create_init_loop:\n\ movntps %%xmm0,cfun_static_space(%%rax)\n\ movntps %%xmm0,0x10+cfun_static_space(%%rax)\n\ movntps %%xmm0,0x20+cfun_static_space(%%rax)\n\ movntps %%xmm0,0x30+cfun_static_space(%%rax)\n\ movntps %%xmm0,0x40+cfun_static_space(%%rax)\n\ movntps %%xmm0,0x50+cfun_static_space(%%rax)\n\ movntps %%xmm0,0x60+cfun_static_space(%%rax)\n\ movntps %%xmm0,0x70+cfun_static_space(%%rax)\n\ add $128,%%rax\n\ cmp %[size],%%rax\n\ jne .Lcf_fungespace_create_init_loop\n\ sfence" : [space] "=o"(cfun_static_space) : [mask] "m"(fspace_vector_init) , [size] "i"(sizeof(cfun_static_space)) : "rax", "xmm0"); # endif #elif defined(FSPACE_CREATE_SSE_INT) // Handle ICC # ifdef FSPACE_ICC_INTRINSICS for (size_t i = 0; i < (sizeof(cfun_static_space) / 16); i++) // Cast to void to shut up warning about strict-aliasing rules. _mm_stream_ps(((float*)(void*)&cfun_static_space) + i*4, *((const __m128*)(const void*)&fspace_vector_init)); _mm_sfence(); // Handle other GCC compatible compilers. # elif defined(FSPACE_GCC_INTRINSICS) for (size_t i = 0; i < (sizeof(cfun_static_space) / 16); i++) // Cast to void to shut up warning about strict-aliasing rules. __builtin_ia32_movntps(((float*)(void*)&cfun_static_space) + i*4, *((const v4sf*)(const void*)&fspace_vector_init)); __builtin_ia32_sfence(); # else # error "Unknown intrinsics selected. Should not happen." # endif #else for (size_t i = 0; i < sizeof(cfun_static_space) / sizeof(funge_cell); i++) cfun_static_space[i] = ' '; #endif fspace.entries = ght_fspace_create(FUNGESPACE_INITIAL_SIZE); if (FUNGE_UNLIKELY(!fspace.entries)) return false; ght_fspace_set_rehash(fspace.entries, true); #ifdef CFUN_EXACT_BOUNDS fspace.col_count = ght_fspacecount_create(FUNGECOUNT_COL_INITIAL_SIZE); fspace.row_count = ght_fspacecount_create(FUNGECOUNT_ROW_INITIAL_SIZE); if (FUNGE_UNLIKELY(!fspace.col_count || !fspace.row_count)) return false; ght_fspacecount_set_rehash(fspace.col_count, true); ght_fspacecount_set_rehash(fspace.row_count, true); // Set up mempool for hash library. if (FUNGE_UNLIKELY(!cf_mempool_fspacecount_setup())) return false; #endif return cf_mempool_fspace_setup(); } FUNGE_ATTR_FAST void fungespace_free(void) { if (fspace.entries) ght_fspace_finalize(fspace.entries); #ifdef CFUN_EXACT_BOUNDS if (fspace.col_count) ght_fspacecount_finalize(fspace.col_count); if (fspace.row_count) ght_fspacecount_finalize(fspace.row_count); cf_mempool_fspacecount_teardown(); #endif cf_mempool_fspace_teardown(); } /***************************************************************** * This section of the code handles tracking exact bounds for y. * *****************************************************************/ #ifdef CFUN_EXACT_BOUNDS FUNGE_ATTR_FAST static inline funge_unsigned_cell get_count_col(funge_cell x) { funge_unsigned_cell sx = (funge_unsigned_cell)x + FUNGESPACE_STATIC_OFFSET_X; if (sx < FUNGESPACE_STATIC_X) { return cfun_static_use_count_col[sx]; } else { funge_unsigned_cell *tmp = ght_fspacecount_get(fspace.col_count, &x); if (!tmp) return 0; return *tmp; } } FUNGE_ATTR_FAST static inline funge_unsigned_cell get_count_row(funge_cell y) { funge_unsigned_cell sy = (funge_unsigned_cell)y + FUNGESPACE_STATIC_OFFSET_Y; if (sy < FUNGESPACE_STATIC_Y) { return cfun_static_use_count_row[sy]; } else { funge_unsigned_cell *tmp = ght_fspacecount_get(fspace.row_count, &y); if (!tmp) return 0; return *tmp; } } FUNGE_ATTR_FAST FUNGE_ATTR_NONNULL static inline void largemodel_minimise(funge_cell * restrict max, funge_cell * restrict min, ght_fspacecount_hash_table_t* restrict hashtable, const funge_unsigned_cell* restrict sarray, const size_t sarray_len, const size_t sarray_off) { // Sparse scan over hash array. funge_cell min_h = 0; funge_cell max_h = 0; bool isfirst = true; ght_fspacecount_iterator_t iterator; const funge_cell *p_key; funge_unsigned_cell *p; for (p = ght_fspacecount_first(hashtable, &iterator, &p_key); p; p = ght_fspacecount_next(&iterator, &p_key)) { funge_cell value = *p_key; if (FUNGE_UNLIKELY(isfirst)) { max_h = min_h = value; isfirst = false; } else { if (max_h < value) max_h = value; if (min_h > value) min_h = value; } } // Now scan static array. for (size_t i = 0; i < sarray_len;i++) if (sarray[i] > 0) { funge_cell value = i - sarray_off; if (max_h < value) max_h = value; if (min_h > value) min_h = value; } *min = min_h; *max = max_h; } FUNGE_ATTR_FAST static inline void fungespace_minimize_bounds(void) { funge_cell minx, miny, maxx, maxy; if (fspace.boundsexact) return; minx = fspace.topLeftCorner.x; miny = fspace.topLeftCorner.y; maxx = fspace.bottomRightCorner.x; maxy = fspace.bottomRightCorner.y; /* Time for scanning. * * If difference is huge, (over 2^16 atm, see the define SIMPLEBOUNDS_MAX): * * First try to do a sparse scan by iterating over all entries in the hash array. * * Then scan static space and grow the previously calculated bounds if needed. * * If the difference is smaller: * * Scan inwards from each edge until we a non-zero count in a column. * * If we hit the other bound we stop, lets lock up in an infinite loop * in the wrapping code instead of here! */ if (FUNGE_UNLIKELY((maxx - minx) > SIMPLEBOUNDS_MAX)) { largemodel_minimise(&maxx, &minx, fspace.col_count, cfun_static_use_count_col, FUNGESPACE_STATIC_X, FUNGESPACE_STATIC_OFFSET_X); } else { for (; minx < maxx; minx++) { if (get_count_col(minx) != 0) break; } for (; maxx > minx; maxx--) { if (get_count_col(maxx) != 0) break; } } if (FUNGE_UNLIKELY((maxy - miny) > SIMPLEBOUNDS_MAX)) { largemodel_minimise(&maxy, &miny, fspace.row_count, cfun_static_use_count_row, FUNGESPACE_STATIC_Y, FUNGESPACE_STATIC_OFFSET_Y); } else { for (; miny < maxy; miny++) { if (get_count_row(miny) != 0) break; } for (; maxy > miny; maxy--) { if (get_count_row(maxy) != 0) break; } } fspace.topLeftCorner.x = minx; fspace.topLeftCorner.y = miny; fspace.bottomRightCorner.x = maxx; fspace.bottomRightCorner.y = maxy; fspace.boundsexact = true; } /** * Clear the boundsexact flag if needed. */ FUNGE_ATTR_FAST static inline void fungespace_check_pos(const funge_cell x, const funge_cell y) { if (x == fspace.bottomRightCorner.x) fspace.boundsexact = false; else if (y == fspace.bottomRightCorner.y) fspace.boundsexact = false; else if (x == fspace.topLeftCorner.x) fspace.boundsexact = false; else if (y == fspace.topLeftCorner.y) fspace.boundsexact = false; } #define FSPACE_COUNT_OP_OR_NEW(m_var, m_op, m_a, m_key, m_val) \ do { \ if (m_var) \ (*(m_var)) m_op; \ else if (ght_fspacecount_insert((m_a), m_val, &m_key) == -1) { \ ght_fspacecount_replace((m_a), m_val, &m_key); \ } \ } while(0) /** * Update column/row counts. */ FUNGE_ATTR_FAST static inline void fungespace_count(bool isset, const funge_vector * restrict position) { funge_cell x = position->x; funge_cell y = position->y; funge_unsigned_cell sx = (funge_unsigned_cell)x + FUNGESPACE_STATIC_OFFSET_X; funge_unsigned_cell sy = (funge_unsigned_cell)y + FUNGESPACE_STATIC_OFFSET_Y; if (sx < FUNGESPACE_STATIC_X) { if (isset) cfun_static_use_count_col[sx]++; else cfun_static_use_count_col[sx]--; } else { funge_unsigned_cell *prevcol = ght_fspacecount_get(fspace.col_count, &x); if (isset) FSPACE_COUNT_OP_OR_NEW(prevcol, ++, fspace.col_count, x, 1); else FSPACE_COUNT_OP_OR_NEW(prevcol, --, fspace.col_count, x, 0); } if (sy < FUNGESPACE_STATIC_Y) { if (isset) cfun_static_use_count_row[sy]++; else cfun_static_use_count_row[sy]--; } else { funge_unsigned_cell *prevrow = ght_fspacecount_get(fspace.row_count, &y); if (isset) FSPACE_COUNT_OP_OR_NEW(prevrow, ++, fspace.row_count, y, 1); else FSPACE_COUNT_OP_OR_NEW(prevrow, --, fspace.row_count, y, 0); } if (!isset) fungespace_check_pos(x, y); } #endif /******************************************************** * Code for checking Funge Space bounds in various ways * ********************************************************/ FUNGE_ATTR_FAST void fungespace_get_bounds_rect(fungeRect * restrict rect) { #ifdef CFUN_EXACT_BOUNDS fungespace_minimize_bounds(); #endif rect->x = fspace.topLeftCorner.x; rect->y = fspace.topLeftCorner.y; // +1 because it is inclusive. rect->w = fspace.bottomRightCorner.x - fspace.topLeftCorner.x; rect->h = fspace.bottomRightCorner.y - fspace.topLeftCorner.y; } /** * Check if position is in range. */ FUNGE_ATTR_FAST FUNGE_ATTR_NONNULL FUNGE_ATTR_PURE FUNGE_ATTR_WARN_UNUSED static inline bool fungespace_in_range(const funge_vector * restrict position) { if ((position->x > fspace.bottomRightCorner.x) || (position->x < fspace.topLeftCorner.x) || (position->y > fspace.bottomRightCorner.y) || (position->y < fspace.topLeftCorner.y)) return false; return true; } /************************ * Funge space get code * ************************/ FUNGE_ATTR_FAST funge_cell fungespace_get(const funge_vector * restrict position) { // Offsets for static. funge_unsigned_cell x = (funge_unsigned_cell)position->x + FUNGESPACE_STATIC_OFFSET_X; funge_unsigned_cell y = (funge_unsigned_cell)position->y + FUNGESPACE_STATIC_OFFSET_Y; if (FUNGESPACE_RANGE_CHECK(x, y)) { return cfun_static_space[STATIC_COORD(x,y)]; } else { funge_cell *tmp = (funge_cell*)ght_fspace_get(fspace.entries, position); if (!tmp) return (funge_cell)' '; else return *tmp; } } FUNGE_ATTR_FAST funge_cell fungespace_get_offset(const funge_vector * restrict position, const funge_vector * restrict offset) { funge_vector tmp; funge_cell *result; // Offsets for static. funge_unsigned_cell x, y; assert(position != NULL); assert(offset != NULL); tmp.x = position->x + offset->x; tmp.y = position->y + offset->y; x = (funge_unsigned_cell)tmp.x + FUNGESPACE_STATIC_OFFSET_X; y = (funge_unsigned_cell)tmp.y + FUNGESPACE_STATIC_OFFSET_Y; if (FUNGESPACE_RANGE_CHECK(x, y)) { return cfun_static_space[STATIC_COORD(x,y)]; } else { result = (funge_cell*)ght_fspace_get(fspace.entries, &tmp); if (!result) return (funge_cell)' '; else return *result; } } /************************ * Funge space set code * ************************/ FUNGE_ATTR_FAST static inline void fungespace_set_no_bounds_update(funge_cell value, const funge_vector * restrict position) { // Offsets for static. funge_unsigned_cell x = (funge_unsigned_cell)position->x + FUNGESPACE_STATIC_OFFSET_X; funge_unsigned_cell y = (funge_unsigned_cell)position->y + FUNGESPACE_STATIC_OFFSET_Y; if (FUNGESPACE_RANGE_CHECK(x, y)) { #ifdef CFUN_EXACT_BOUNDS funge_cell prev = cfun_static_space[STATIC_COORD(x,y)]; #endif cfun_static_space[STATIC_COORD(x,y)] = value; #ifdef CFUN_EXACT_BOUNDS if (value != prev) { if ((prev == ' ') || (value == ' ')) fungespace_count((value != ' '), position); } #endif } else { #ifdef CFUN_EXACT_BOUNDS funge_cell* prev = ght_fspace_get(fspace.entries, position); if (!prev) { if (value == ' ') return; if (FUNGE_UNLIKELY(ght_fspace_insert(fspace.entries, value, position) == -1)) { DIAG_FATAL_LOC("Internal error: insert in hash table failed when value known not to exist."); } fungespace_count(true, position); } else { if (value == ' ') { ght_fspace_remove(fspace.entries, position); fungespace_count(false, position); } else { *prev = value; } } #else if (value == ' ') { ght_fspace_remove(fspace.entries, position); } else { // Reuse cell if it exists funge_cell *tmp; if ((tmp = (funge_cell*)ght_fspace_get(fspace.entries, position)) != NULL) { *tmp = value; } else { if (FUNGE_UNLIKELY(ght_fspace_insert(fspace.entries, value, position) == -1)) { DIAG_FATAL_LOC("Internal error: insert in hash table failed when value known not to exist."); } } } #endif } } FUNGE_ATTR_FAST void fungespace_set(funge_cell value, const funge_vector * restrict position) { assert(position != NULL); if (value != ' ') { // It is faster to not use else if here, because this way the code // translates into conditional moves (on x86 at least). if (fspace.bottomRightCorner.y < position->y) fspace.bottomRightCorner.y = position->y; if (fspace.topLeftCorner.y > position->y) fspace.topLeftCorner.y = position->y; if (fspace.bottomRightCorner.x < position->x) fspace.bottomRightCorner.x = position->x; if (fspace.topLeftCorner.x > position->x) fspace.topLeftCorner.x = position->x; } fungespace_set_no_bounds_update(value, position); } /** * Special variant of fungespace_set() to deal with initial load. * Needed to handle the initial bounding box properly. * Must NOT be called with a space for value. */ FUNGE_ATTR_FAST static inline void fungespace_set_initial(funge_cell value, const funge_vector * restrict position) { if (FUNGE_LIKELY(fspace.boundsvalid)) { // It is faster to not use else if here, because this way the code // translates into conditional moves (on x86 at least). if (fspace.bottomRightCorner.y < position->y) fspace.bottomRightCorner.y = position->y; if (fspace.topLeftCorner.y > position->y) fspace.topLeftCorner.y = position->y; if (fspace.bottomRightCorner.x < position->x) fspace.bottomRightCorner.x = position->x; if (fspace.topLeftCorner.x > position->x) fspace.topLeftCorner.x = position->x; } else { fspace.topLeftCorner.y = fspace.bottomRightCorner.y = position->y; fspace.topLeftCorner.x = fspace.bottomRightCorner.x = position->x; fspace.boundsvalid = true; } fungespace_set_no_bounds_update(value, position); } FUNGE_ATTR_FAST void fungespace_set_offset(funge_cell value, const funge_vector * restrict position, const funge_vector * restrict offset) { assert(position != NULL); assert(offset != NULL); fungespace_set(value, vector_create_ref(position->x + offset->x, position->y + offset->y)); } /***************** * Wrapping code * *****************/ /// Duplicated from vector.c for speed reasons (inlining). FUNGE_ATTR_PURE FUNGE_ATTR_FAST FUNGE_ATTR_WARN_UNUSED static inline bool fspace_vector_is_cardinal(const funge_vector * restrict v) { // Due to unsigned this can't overflow in the addition below. funge_unsigned_cell x = (funge_unsigned_cell)ABS(v->x); funge_unsigned_cell y = (funge_unsigned_cell)ABS(v->y); if ((x + y) != 1) return false; if (x && y) return false; return true; } FUNGE_ATTR_FAST void fungespace_wrap(funge_vector * restrict position, const funge_vector * restrict delta) { #ifdef CFUN_EXACT_BOUNDS if (FUNGE_UNLIKELY(!fspace.boundsexact && (BOUNDS_TOO_LARGE(x) || BOUNDS_TOO_LARGE(y)))) fungespace_minimize_bounds(); #endif if (!fungespace_in_range(position)) { // Quick and dirty if cardinal. if (FUNGE_LIKELY(fspace_vector_is_cardinal(delta))) { // FIXME, HACK: Why are the +1/-1 needed? if (position->x < fspace.topLeftCorner.x) position->x = fspace.bottomRightCorner.x + 1; else if (position->x > fspace.bottomRightCorner.x) position->x = fspace.topLeftCorner.x - 1; if (position->y < fspace.topLeftCorner.y) position->y = fspace.bottomRightCorner.y + 1; else if (position->y > fspace.bottomRightCorner.y) position->y = fspace.topLeftCorner.y - 1; } else { do { position->x -= delta->x; position->y -= delta->y; } while (fungespace_in_range(position)); position->x += delta->x; position->y += delta->y; } } } /****************** * Load/save code * ******************/ // Returns fd, addr and length. /** * mmap() a file. * @param filename Filename to mmap(). * @param maddr Pointer to a char* where the mapping's address will be placed. * @param length Pointer to a size_t where the size of the mapping will be placed. * @return * Returns the file descriptor, or -1 in case of error, or -2 in case of * empty file. */ FUNGE_ATTR_FAST FUNGE_ATTR_WARN_UNUSED static inline int do_mmap(const char * restrict filename, unsigned char **maddr, size_t * restrict length) { char *addr = NULL; struct stat sb; int fd; size_t len; fd = open(filename, O_RDONLY); if (FUNGE_UNLIKELY(fd == -1)) return -1; if (FUNGE_UNLIKELY(fstat(fd, &sb) == -1)) { diag_error_format("fstat() on file \"%s\" failed: %s", filename, strerror(errno)); goto error; } len = (size_t)sb.st_size; *length = len; // An empty file isn't an error, but we can't mmap it. if (len == 0) { close(fd); return -2; } // mmap() it. addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0); if (FUNGE_UNLIKELY(addr == MAP_FAILED)) { diag_error_format("mmap() on file \"%s\" failed: %s", filename, strerror(errno)); goto error; } #if defined(_POSIX_ADVISORY_INFO) && (_POSIX_ADVISORY_INFO > 0) posix_madvise(addr, len, POSIX_MADV_SEQUENTIAL); #endif *maddr = (unsigned char*)addr; return fd; error: if (addr != NULL) { munmap(addr, len); } if (fd != -1) { close(fd); } return -1; } /** * Clean up a mapping created with do_mmap(). * @param fd is the file descriptor to close. * @param addr is the address to the mmap()ed area. * @param length is the length of the mmap()ed area. */ FUNGE_ATTR_FAST static inline void do_mmap_cleanup(int fd, unsigned char *addr, size_t length) { if (FUNGE_LIKELY(addr != NULL)) { munmap((char*)addr, length); } if (FUNGE_LIKELY(fd != -1)) { close(fd); } } /// Macro for handling newlines. #define FUNGE_INITIAL_NEWLINE \ pos.x = 0; \ pos.y++; /** * Load a string into Funge-Space at 0,0. Used for initial loading. * Can handle null-bytes in the string without problems. * @param program is the string to load. * @param length is the length of the string. */ FUNGE_ATTR_FAST FUNGE_ATTR_NONNULL #ifndef FUNGE_EXTERNAL_LIBRARY static inline #endif void fungespace_load_string(const unsigned char * restrict program, size_t length) { bool lastwascr = false; // Coord in Funge-Space. funge_vector pos = {0, 0}; for (size_t i = 0; i < length; i++) { switch (program[i]) { case ' ': if (lastwascr) { lastwascr = false; FUNGE_INITIAL_NEWLINE } pos.x++; break; case '\r': if (lastwascr) { FUNGE_INITIAL_NEWLINE } lastwascr = true; break; case '\n': lastwascr = false; FUNGE_INITIAL_NEWLINE break; // Ignore form feed. Treat it as newline is treated in Unefunge. case '\f': break; default: if (lastwascr) { lastwascr = false; FUNGE_INITIAL_NEWLINE } fungespace_set_initial((funge_cell)program[i], &pos); pos.x++; break; } } } FUNGE_ATTR_FAST bool fungespace_load(const char * restrict filename) { unsigned char *addr; int fd; size_t length; assert(filename != NULL); fd = do_mmap(filename, &addr, &length); if (FUNGE_UNLIKELY(fd == -1)) return false; // Empty file? else if (FUNGE_UNLIKELY(fd == -2)) { diag_warn("File is empty, program will be infinite loop."); return true; } fungespace_load_string(addr, length); // Cleanup do_mmap_cleanup(fd, addr, length); return true; } /// Macro for handling newlines. #define FUNGE_OFFSET_NEWLINE \ if (pos.x > size->x) \ size->x = pos.x; \ pos.x = 0; \ pos.y++; FUNGE_ATTR_FAST bool fungespace_load_at_offset(const char * restrict filename, const funge_vector * restrict offset, funge_vector * restrict size, bool binary) { unsigned char *addr; int fd; size_t length; funge_vector pos = {0, 0}; assert(filename != NULL); assert(offset != NULL); assert(size != NULL); fd = do_mmap(filename, &addr, &length); if (FUNGE_UNLIKELY(fd == -1)) return false; // Set size here, we have to initialise it for both empty-file and normal // load. size->x = 0; size->y = 0; // Empty file? if (FUNGE_UNLIKELY(fd == -2)) return true; if (binary) { pos.x = offset->x; pos.y = offset->y; for (size_t i = 0; i < length; i++) { if (addr[i] != ' ') fungespace_set((funge_cell)addr[i], &pos); pos.x++; } } else { bool lastwascr = false; for (size_t i = 0; i < length; i++) { switch (addr[i]) { // Ignore form feed. Treat it as newline is treated in Unefunge. case '\f': break; case '\r': if (lastwascr) { // Blergh two \r after each other. FUNGE_OFFSET_NEWLINE } lastwascr = true; break; case '\n': FUNGE_OFFSET_NEWLINE lastwascr = false; break; default: if (lastwascr) { lastwascr = false; FUNGE_OFFSET_NEWLINE } if (addr[i] != ' ') fungespace_set_offset((funge_cell)addr[i], &pos, offset); pos.x++; break; } } if (lastwascr) pos.y++; } if (pos.x > size->x) size->x = pos.x; if (pos.y > size->y) size->y = pos.y; do_mmap_cleanup(fd, addr, length); return true; } FUNGE_ATTR_FAST bool fungespace_save_to_file(const char * restrict filename, const funge_vector * restrict offset, const funge_vector * restrict size, bool textfile) { FILE * file; funge_cell maxy = offset->y + size->y; funge_cell maxx = offset->x + size->x; assert(filename != NULL); assert(offset != NULL); assert(size != NULL); file = fopen(filename, "wb"); if (!file) return false; if (!textfile) { // Microoptimising! Remove this if it bothers you. // However it also makes it possible to error out early. #if defined(_POSIX_ADVISORY_INFO) && (_POSIX_ADVISORY_INFO > 0) if (posix_fallocate(fileno(file), 0, (off_t)(size->y * size->x)) != 0) { goto error; } #endif cf_flockfile(file); for (funge_cell y = offset->y; y < maxy; y++) { for (funge_cell x = offset->x; x < maxx; x++) { funge_cell value = fungespace_get(vector_create_ref(x, y)); cf_putc_unlocked((int)value, file); } cf_putc_unlocked('\n', file); } cf_funlockfile(file); // Text mode... } else { size_t index = 0; // Extra size->y for adding a lot of \n... unsigned char * restrict towrite = cf_malloc_noptr((size_t)(size->x * size->y + size->y) * sizeof(unsigned char)); if (!towrite) { goto error; } // Construct each line. for (funge_cell y = offset->y; y < maxy; y++) { ssize_t lastspace = (ssize_t)size->x; funge_cell * restrict string = cf_malloc_noptr((size_t)size->x * sizeof(funge_cell)); if (!string) { cf_free(towrite); goto error; } for (funge_cell x = offset->x; x < maxx; x++) { string[x-offset->x] = fungespace_get(vector_create_ref(x, y)); } do { lastspace--; } while ((lastspace >= 0) && (string[lastspace] == ' ')); if (lastspace > 0) { for (ssize_t i = 0; i <= lastspace; i++) { towrite[index+i] = (unsigned char)string[i]; } index += lastspace + 1; } cf_free(string); towrite[index] = (funge_cell)'\n'; index++; } // Remove trailing newlines. { ssize_t lastnewline = (ssize_t)index; do { lastnewline--; } while ((lastnewline >= 0) && (towrite[lastnewline] == '\n')); if (lastnewline > 0) { size_t retval = fwrite(towrite, sizeof(unsigned char), (size_t)(lastnewline + 1), file); if (retval != (size_t)lastnewline + 1) { cf_free(towrite); goto error; } } } cf_free(towrite); } fclose(file); return true; error: if (file) fclose(file); return false; } /************* * Debugging * *************/ #ifndef NDEBUG // For use with call in gdb void fungespace_dump(void) FUNGE_ATTR_UNUSED FUNGE_ATTR_COLD; void fungespace_dumparea(funge_cell minx, funge_cell miny, funge_cell maxx, funge_cell maxy) FUNGE_ATTR_UNUSED FUNGE_ATTR_COLD; void fungespace_dump_sparse(void) FUNGE_ATTR_UNUSED FUNGE_ATTR_COLD; void fungespace_dump(void) { if (!fspace.entries) return; fputs("Positive fungespace follows:\n", stderr); for (funge_cell y = 0; y <= fspace.bottomRightCorner.y; y++) { for (funge_cell x = 0; x <= fspace.bottomRightCorner.x; x++) fprintf(stderr, "%c", (char)fungespace_get(vector_create_ref(x, y))); fprintf(stderr, "\n"); } fputs("\n", stderr); } void fungespace_dumparea(funge_cell minx, funge_cell miny, funge_cell maxx, funge_cell maxy) { if (!fspace.entries) return; fprintf(stderr, "Fungespace (%"FUNGECELLPRI",%"FUNGECELLPRI ") to (%"FUNGECELLPRI",%"FUNGECELLPRI") follows:\n", minx, miny, maxx, maxy); for (funge_cell y = miny; y <= maxy; y++) { for (funge_cell x = minx; x <= maxx; x++) fprintf(stderr, "%c", (char)fungespace_get(vector_create_ref(x, y))); fprintf(stderr, "\n"); } fputs("\n", stderr); } void fungespace_dump_sparse(void) { if (!fspace.entries) return; fputs("Sparse Fungespace follows:\n", stderr); fputs("(static\n", stderr); for (funge_cell x = -FUNGESPACE_STATIC_OFFSET_X; x < FUNGESPACE_STATIC_X - FUNGESPACE_STATIC_OFFSET_X; x++) for (funge_cell y = -FUNGESPACE_STATIC_OFFSET_Y; y < FUNGESPACE_STATIC_Y - FUNGESPACE_STATIC_OFFSET_Y; y++) { funge_cell value = fungespace_get(vector_create_ref(x, y)); if (value != ' ') fprintf(stderr, " ((%"FUNGECELLPRI" %"FUNGECELLPRI") %"FUNGECELLPRI" \"%c\")\n", x, y, value, (char)value); } fputs(")\n", stderr); fputs("(hash\n", stderr); // Sparse scan over hash array. { ght_fspace_iterator_t iterator; const funge_vector *p_key; funge_cell *p; for (p = ght_fspace_first(fspace.entries, &iterator, &p_key); p; p = ght_fspace_next(&iterator, &p_key)) { fprintf(stderr, " ((%"FUNGECELLPRI" %"FUNGECELLPRI") %"FUNGECELLPRI" \"%c\")\n", p_key->x, p_key->y, *p, (char)*p); } } fputs(")\n", stderr); } #endif