From 5c605d9b72577887d25677094574d5bbb54cf5ea Mon Sep 17 00:00:00 2001 From: Andrew Byers Date: Tue, 13 Aug 2024 10:43:46 -0500 Subject: [PATCH] Refactor and fix problem on Windows --- src/alloc.c | 524 ++++++++++++++++++---------------------- src/alloc.h | 30 +-- src/api.c | 39 +-- src/ast.c | 2 +- src/auxlib.c | 2 +- src/call.c | 2 +- src/code.c | 2 +- src/codegen.c | 128 +++++----- src/compile.c | 2 +- src/config.h | 8 + src/debug.c | 3 + src/env.h | 3 +- src/hir.h | 2 +- src/lib.c | 325 +++++++------------------ src/map.c | 5 +- src/map.h | 76 ++---- src/mem.c | 22 +- src/parse.c | 16 +- src/parse.h | 1 + src/paw.c | 15 +- src/paw.h | 8 +- src/resolve.c | 71 +++--- src/resolve.h | 1 + src/rt.c | 6 +- src/util.h | 6 +- src/value.c | 38 ++- src/value.h | 2 +- test/CMakeLists.txt | 1 + test/scripts/class.paw | 423 -------------------------------- test/scripts/map.paw | 27 ++- test/scripts/matmul.paw | 46 ++++ test/scripts/nqueen.paw | 45 ++++ test/scripts/string.paw | 152 ++++++------ test/test.c | 11 +- test/test.h | 4 +- test/test_alloc.c | 28 ++- test/test_error.c | 14 +- test/test_impl.c | 71 ++---- test/test_oom.c | 46 +++- test/test_rt.c | 2 +- test/test_so.c | 2 +- 41 files changed, 833 insertions(+), 1378 deletions(-) delete mode 100644 test/scripts/class.paw create mode 100644 test/scripts/matmul.paw create mode 100644 test/scripts/nqueen.paw diff --git a/src/alloc.c b/src/alloc.c index f1aebc3..d55186b 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -1,39 +1,17 @@ // Copyright (c) 2024, The paw Authors. All rights reserved. // This source code is licensed under the MIT License, which can be found in // LICENSE.md. See AUTHORS.md for a list of contributor names. -// -// NOTE: The block allocator code is from SQLite (mem3.c) #include "alloc.h" #include "env.h" #include "mem.h" -#define MIN_RUNTIME_SIZE (32 * 1024) -#define MIN_HEAP_SIZE (aligned(sizeof(struct BlockAllocator)) + \ - aligned(sizeof(struct FastBins)) + \ - MIN_RUNTIME_SIZE) +#define HEAP_META_SIZE (PAW_ROUND_UP(sizeof(struct Heap)) + \ + PAW_ROUND_UP(sizeof(struct Allocator))) #define OS_MALLOC(P, size) ((P)->alloc((P)->ud, NULL, 0, size)) #define OS_FREE(P, ptr, size) ((P)->alloc((P)->ud, ptr, size, 0)) -#define BUMP(p, n) ERASE_TYPE(CAST_UPTR(p) + (n)) -#define ERASE_TYPE(p) CAST(p, void *) - -static inline paw_Bool is_pow2(size_t v) -{ - return v > 0 && !(v & (v >> 1)); -} - -static inline paw_Bool is_aligned(void *p, size_t align) -{ - return !(CAST_UPTR(p) & (align - 1)); -} - -static inline size_t aligned(size_t v) -{ - return v + (-v & (PAW_ALIGN - 1)); -} - #if defined(__has_feature) # if __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__) # define __SANITIZE_ADDRESS__ @@ -51,61 +29,53 @@ void __asan_unpoison_memory_region(const volatile void *ptr, size_t size); # define UNPOISON_MEMORY_REGION(p, z) #endif -#define BIN_FACTOR 8 -#define MIN_BIN_SIZE (sizeof(Value) * 2) -#define MAX_BIN_SIZE (MIN_BIN_SIZE * FAST_BIN_COUNT) #define FLAGS_PER_BYTE 8 - -#define BIN_ID(size) CHECK_EXP((size) > 0 && aligned(size), ((size) - 1) / MIN_BIN_SIZE) #define FLAG_BASE(H, uptr) CHECK_EXP(Z_IN_BOUNDS(H, uptr), ((uptr) - (H)->bounds[0]) / sizeof(void *)) #define FLAG_ID(base) ((base) / FLAGS_PER_BYTE) #define FLAG_BIT(base) ((base) % FLAGS_PER_BYTE) -struct BinInfo { - struct BinInfo *prev; - char buffer[]; -}; - -struct BlockId { +struct ChunkId { uint32_t v; }; -struct Block { +struct Chunk { union { struct { uint32_t prev_size; uint32_t size4x; } hdr; struct { - struct BlockId prev; - struct BlockId next; + struct ChunkId prev; + struct ChunkId next; } list; }; }; -#define B_HDR(L, i) (&(L)->blocks[(i) - 1].hdr) -#define B_LIST(L, i) (&(L)->blocks[i].list) +#define CHUNK_HDR(L, i) (&(L)->chunks[(i) - 1].hdr) +#define CHUNK_LIST(L, i) (&(L)->chunks[i].list) -#define BLOCK_NLISTS 61 -#define KEY_BLOCK_SHIFT 3 -#define BLOCK_SIZE sizeof(struct Block) -#define BAD_BLOCK 0 +#define MAX_SMALL 10 +#define NUM_HASH 61 +#define KEY_CHUNK_SHIFT 3 +#define CHUNK_SIZE sizeof(struct Chunk) +#define BAD_CHUNK 0 -// Allocator for larger regions of memory -// Based off of mem3.c from SQLite -struct BlockAllocator { - struct BlockId free[BLOCK_NLISTS]; - struct Block *blocks; - size_t nblocks; +// Allocator based off of mem3.c from SQLite +struct Allocator { + struct ChunkId small[MAX_SMALL - 1]; + struct ChunkId hash[NUM_HASH]; + struct Chunk *chunks; + size_t nchunks; paw_Alloc alloc; paw_Env *P; - struct BlockId key; + struct ChunkId key; uint32_t key_size; uint32_t key_min; }; +_Static_assert(PAW_HEAP_MIN >= HEAP_META_SIZE, "PAW_HEAP_MIN is too small"); uint8_t pawZ_get_flag(struct Heap *H, uintptr_t uptr) { @@ -130,105 +100,110 @@ void pawZ_clear_flag(struct Heap *H, uintptr_t uptr) *pflag = *pflag & ~(1 << FLAG_BIT(id)); } -static void *block_malloc(struct BlockAllocator *, size_t); -static void block_free(struct BlockAllocator *, void *, size_t); - -static void init_block_allocator(struct BlockAllocator *a, void *heap, size_t heap_size) +static void init_chunk_allocator(struct Allocator *a, void *heap, size_t heap_size) { - paw_assert(is_aligned(heap, PAW_ALIGN)); - memset(a->free, 0, sizeof(a->free)); + paw_assert(PAW_IS_ALIGNED(heap)); + memset(a, 0, sizeof(*a)); - assert(BLOCK_SIZE == 8); - a->blocks = heap; - a->nblocks = heap_size / BLOCK_SIZE - 2; + assert(CHUNK_SIZE == 8); + a->nchunks = heap_size / CHUNK_SIZE - 2; + a->chunks = heap; - a->key_size = a->nblocks; + a->key_size = a->nchunks; a->key_min = a->key_size; a->key.v = 1; - B_HDR(a, 1)->size4x = (a->key_size << 2) + 2; - B_HDR(a, 1 + a->nblocks)->prev_size = a->nblocks; - B_HDR(a, 1 + a->nblocks)->size4x = 1; + CHUNK_HDR(a, 1)->size4x = (a->key_size << 2) + 2; + CHUNK_HDR(a, 1 + a->nchunks)->prev_size = a->nchunks; + CHUNK_HDR(a, 1 + a->nchunks)->size4x = 1; } -static struct Block *block_at(struct BlockAllocator *a, struct BlockId id) +static struct Chunk *chunk_at(struct Allocator *a, struct ChunkId id) { - paw_assert(id.v < a->nblocks); - return &a->blocks[id.v]; + paw_assert(id.v < a->nchunks); + return &a->chunks[id.v]; } -static void unlink_from_list(struct BlockAllocator *a, struct BlockId id, struct BlockId *proot) +static void unlink_from_list(struct Allocator *a, struct ChunkId id, struct ChunkId *proot) { - struct Block *b = block_at(a, id); - const struct BlockId prev = b->list.prev; - const struct BlockId next = b->list.next; - if (prev.v != BAD_BLOCK) { - struct Block *prev_block = block_at(a, prev); - prev_block->list.next = next; + struct Chunk *b = chunk_at(a, id); + const struct ChunkId prev = b->list.prev; + const struct ChunkId next = b->list.next; + if (prev.v != BAD_CHUNK) { + struct Chunk *prev_chunk = chunk_at(a, prev); + prev_chunk->list.next = next; } else { *proot = next; } - if (next.v != BAD_BLOCK) { - struct Block *next_block = block_at(a, next); - next_block->list.prev = prev; + if (next.v != BAD_CHUNK) { + struct Chunk *next_chunk = chunk_at(a, next); + next_chunk->list.prev = prev; } b->list.prev.v = 0; b->list.next.v = 0; } -static void unlink_block(struct BlockAllocator *a, struct BlockId id) +static void unlink_chunk(struct Allocator *a, struct ChunkId id) { - paw_assert((B_HDR(a, id.v)->size4x & 1) == 0); + paw_assert((CHUNK_HDR(a, id.v)->size4x & 1) == 0); paw_assert(id.v >= 1); - const uint32_t size = B_HDR(a, id.v)->size4x / 4; - paw_assert(size == B_HDR(a, id.v + size)->prev_size); + const uint32_t size = CHUNK_HDR(a, id.v)->size4x / 4; + paw_assert(size == CHUNK_HDR(a, id.v + size)->prev_size); paw_assert(size >= 2); - const uint32_t hash = size % BLOCK_NLISTS; - unlink_from_list(a, id, &a->free[hash]); + if (size <= MAX_SMALL) { + unlink_from_list(a, id, &a->small[size - 2]); + } else { + const uint32_t hash = size % NUM_HASH; + unlink_from_list(a, id, &a->hash[hash]); + } } -static void link_into_list(struct BlockAllocator *a, struct BlockId id, struct BlockId *proot) +static void link_into_list(struct Allocator *a, struct ChunkId id, struct ChunkId *proot) { - B_LIST(a, id.v)->next = *proot; - B_LIST(a, id.v)->prev.v = 0; - if (proot->v != BAD_BLOCK) { - B_LIST(a, proot->v)->prev = id; + CHUNK_LIST(a, id.v)->next = *proot; + CHUNK_LIST(a, id.v)->prev.v = 0; + if (proot->v != BAD_CHUNK) { + CHUNK_LIST(a, proot->v)->prev = id; } *proot = id; } -static void link_block(struct BlockAllocator *a, struct BlockId id) +static void link_chunk(struct Allocator *a, struct ChunkId id) { paw_assert(id.v >= 1); - paw_assert((B_HDR(a, id.v)->size4x & 1) == 0); - const uint32_t size = B_HDR(a, id.v)->size4x / 4; - paw_assert(size == B_HDR(a, id.v + size)->prev_size); + paw_assert((CHUNK_HDR(a, id.v)->size4x & 1) == 0); + const uint32_t size = CHUNK_HDR(a, id.v)->size4x / 4; + paw_assert(size == CHUNK_HDR(a, id.v + size)->prev_size); paw_assert(size >= 2); - const uint32_t hash = size % BLOCK_NLISTS; - link_into_list(a, id, &a->free[hash]); + if (size <= MAX_SMALL) { + link_into_list(a, id, &a->small[size - 2]); + } else { + const uint32_t hash = size % NUM_HASH; + link_into_list(a, id, &a->hash[hash]); + } } -static void fix_block_list(struct BlockAllocator *a, struct BlockId *proot) +static void fix_chunk_list(struct Allocator *a, struct ChunkId *proot) { - struct BlockId next; + struct ChunkId next; - for (struct BlockId i = *proot; i.v != BAD_BLOCK; i = next){ - next = B_LIST(a, i.v)->next; - uint32_t size = B_HDR(a, i.v)->size4x; + for (struct ChunkId i = *proot; i.v != BAD_CHUNK; i = next){ + next = CHUNK_LIST(a, i.v)->next; + uint32_t size = CHUNK_HDR(a, i.v)->size4x; paw_assert((size & 1) == 0); if ((size & 2) == 0) { unlink_from_list(a, i, proot); - paw_assert(i.v > B_HDR(a, i.v)->prev_size); - struct BlockId prev = {i.v - B_HDR(a, i.v)->prev_size}; + paw_assert(i.v > CHUNK_HDR(a, i.v)->prev_size); + struct ChunkId prev = {i.v - CHUNK_HDR(a, i.v)->prev_size}; if (prev.v == next.v) { - next = B_LIST(a, prev.v)->next; + next = CHUNK_LIST(a, prev.v)->next; } - unlink_block(a, prev); + unlink_chunk(a, prev); size = i.v + size / 4 - prev.v; - const uint32_t x = B_HDR(a, prev.v)->size4x & 2; - B_HDR(a, prev.v)->size4x = size * 4 | x; - B_HDR(a, prev.v + size)->prev_size = size; - link_block(a, prev); + const uint32_t x = CHUNK_HDR(a, prev.v)->size4x & 2; + CHUNK_HDR(a, prev.v)->size4x = size * 4 | x; + CHUNK_HDR(a, prev.v + size)->prev_size = size; + link_chunk(a, prev); i = prev; } else { size /= 4; @@ -240,210 +215,201 @@ static void fix_block_list(struct BlockAllocator *a, struct BlockId *proot) } } -static void *checkout_block(struct BlockAllocator *a, struct BlockId i, uint32_t nblocks) +static void *checkout_chunk(struct Allocator *a, struct ChunkId i, uint32_t nchunks) { paw_assert(i.v >= 1); - paw_assert(B_HDR(a, i.v)->size4x / 4 == nblocks); - paw_assert(B_HDR(a, i.v + nblocks)->prev_size == nblocks); - const uint32_t x = B_HDR(a, i.v)->size4x; - B_HDR(a, i.v)->size4x = nblocks * 4 | 1 | (x & 2); - B_HDR(a, i.v + nblocks)->prev_size = nblocks; - B_HDR(a, i.v + nblocks)->size4x |= 2; - return &a->blocks[i.v]; + paw_assert(CHUNK_HDR(a, i.v)->size4x / 4 == nchunks); + paw_assert(CHUNK_HDR(a, i.v + nchunks)->prev_size == nchunks); + const uint32_t x = CHUNK_HDR(a, i.v)->size4x; + CHUNK_HDR(a, i.v)->size4x = nchunks * 4 | 1 | (x & 2); + CHUNK_HDR(a, i.v + nchunks)->prev_size = nchunks; + CHUNK_HDR(a, i.v + nchunks)->size4x |= 2; + return &a->chunks[i.v]; } -static void *key_block_alloc(struct BlockAllocator *a, uint32_t nblocks) +static void *key_chunk_alloc(struct Allocator *a, uint32_t nchunks) { - assert(a->key_size >= nblocks); - if (nblocks >= a->key_size - 1) { - void *p = checkout_block(a, a->key, a->key_size); + assert(a->key_size >= nchunks); + if (nchunks >= a->key_size - 1) { + void *p = checkout_chunk(a, a->key, a->key_size); a->key.v = 0; a->key_size = 0; a->key_min = 0; return p; } else { - struct BlockId newi = {a->key.v + a->key_size - nblocks}; + struct ChunkId newi = {a->key.v + a->key_size - nchunks}; assert(newi.v > a->key.v+1); - B_HDR(a, a->key.v + a->key_size)->prev_size = nblocks; - B_HDR(a, a->key.v + a->key_size)->size4x |= 2; - B_HDR(a, newi.v)->size4x = nblocks*4 + 1; - a->key_size -= nblocks; - B_HDR(a, newi.v)->prev_size = a->key_size; - const uint32_t x = B_HDR(a, a->key.v)->size4x & 2; - B_HDR(a, a->key.v)->size4x = a->key_size * 4 | x; + CHUNK_HDR(a, a->key.v + a->key_size)->prev_size = nchunks; + CHUNK_HDR(a, a->key.v + a->key_size)->size4x |= 2; + CHUNK_HDR(a, newi.v)->size4x = nchunks * 4 + 1; + a->key_size -= nchunks; + CHUNK_HDR(a, newi.v)->prev_size = a->key_size; + const uint32_t x = CHUNK_HDR(a, a->key.v)->size4x & 2; + CHUNK_HDR(a, a->key.v)->size4x = a->key_size * 4 | x; if (a->key_size < a->key_min) { a->key_min = a->key_size; } - return &a->blocks[newi.v]; + return &a->chunks[newi.v]; } } -#define COMPUTE_NBLOCKS(nbytes) CHECK_EXP(BLOCK_SIZE == 8 && (nbytes) > 12, \ - ((nbytes) + 11) / BLOCK_SIZE) +// The maximum number of bytes that can fit in the smallest possible +// allocation is 12, since we have to write the chunk size at the +// end (in the 'prev_size' field of the following chunk's header). +_Static_assert(CHUNK_SIZE == 8, "failed allocator precondition"); +#define COMPUTE_NUM_CHUNKS(nbytes) \ + ((nbytes) > 12 ? ((nbytes) + 11) / 8 : 2) +#define TOO_MANY_CHUNKS(nbytes) \ + ((nbytes) > SIZE_MAX - 11 || \ + ((nbytes) + 11) / CHUNK_SIZE > UINT32_MAX) // Modified from SQLite (mem3.c) -static void *block_malloc(struct BlockAllocator *a, size_t nbytes) +static void *unsafe_malloc(struct Heap *H, size_t nbytes) { - uint32_t nblocks = COMPUTE_NBLOCKS(nbytes); - paw_assert(nblocks >= 2); - + struct Allocator *a = H->a; + nbytes = PAW_ROUND_UP(nbytes); + if (TOO_MANY_CHUNKS(nbytes)) return NULL; + const uint32_t nchunks = COMPUTE_NUM_CHUNKS(nbytes); + paw_assert(nchunks >= 2); + // search for an exact fit - const uint32_t hash = nblocks % BLOCK_NLISTS; - for (struct BlockId id = a->free[hash]; - id.v != BAD_BLOCK; - id = B_LIST(a, id.v)->next) { - if (B_HDR(a, id.v)->size4x / 4 == nblocks) { - unlink_from_list(a, id, &a->free[hash]); - return checkout_block(a, id, nblocks); + if (nchunks <= MAX_SMALL) { + struct ChunkId id = a->small[nchunks - 2]; + if (id.v != BAD_CHUNK) { + unlink_from_list(a, id, &a->small[nchunks - 2]); + return checkout_chunk(a, id, nchunks); + } + } else { + const uint32_t hash = nchunks % NUM_HASH; + for (struct ChunkId id = a->hash[hash]; + id.v != BAD_CHUNK; + id = CHUNK_LIST(a, id.v)->next) { + if (CHUNK_HDR(a, id.v)->size4x / 4 == nchunks) { + unlink_from_list(a, id, &a->hash[hash]); + return checkout_chunk(a, id, nchunks); + } } } - if (a->key_size >= nblocks) { - return key_block_alloc(a, nblocks); + if (a->key_size >= nchunks) { + return key_chunk_alloc(a, nchunks); } - for (uint32_t to_free = nblocks * 16; - to_free < a->nblocks * 16; + for (uint32_t to_free = nchunks * 16; + to_free < a->nchunks * 16; to_free *= 2) { - if (a->key.v != BAD_BLOCK) { - link_block(a, a->key); - a->key.v = BAD_BLOCK; + if (a->key.v != BAD_CHUNK) { + link_chunk(a, a->key); + a->key.v = BAD_CHUNK; a->key_size = 0; } - for (uint32_t i = 0; i < BLOCK_NLISTS; ++i) { - fix_block_list(a, &a->free[i]); + for (uint32_t i = 0; i < NUM_HASH; ++i) { + fix_chunk_list(a, &a->hash[i]); + } + for (uint32_t i = 0; i < MAX_SMALL - 1; ++i) { + fix_chunk_list(a, &a->small[i]); } if (a->key_size != 0) { - unlink_block(a, a->key); - if (a->key_size >= nblocks) { - return key_block_alloc(a, nblocks); + unlink_chunk(a, a->key); + if (a->key_size >= nchunks) { + return key_chunk_alloc(a, nchunks); } } } - return NULL; } -// Modified from SQLite (mem3.c) -static void block_free(struct BlockAllocator *a, void *ptr, size_t nbytes) +static void *z_malloc(struct Heap *H, size_t nbytes) { - uint32_t nblocks = COMPUTE_NBLOCKS(nbytes); - paw_assert(nblocks >= 2); - struct Block *b = ptr; - paw_assert(b > a->blocks && b < &a->blocks[a->nblocks]); - const struct BlockId i = {b - a->blocks}; - paw_assert((B_HDR(a, i.v)->size4x & 1) == 1); - paw_assert(nblocks == B_HDR(a, i.v)->size4x / 4); - paw_assert(i.v + nblocks <= a->nblocks + 1); - B_HDR(a, i.v)->size4x &= ~1; - B_HDR(a, i.v + nblocks)->prev_size = nblocks; - B_HDR(a, i.v + nblocks)->size4x &= ~2; - link_block(a, i); - - if (a->key.v != BAD_BLOCK) { - while ((B_HDR(a, a->key.v)->size4x & 2) == 0) { - nblocks = B_HDR(a, a->key.v)->prev_size; - a->key.v -= nblocks; - a->key_size += nblocks; - unlink_block(a, a->key); - const uint32_t x = B_HDR(a, a->key.v)->size4x & 2; - B_HDR(a, a->key.v)->size4x = a->key_size * 4 | x; - a->blocks[a->key.v + a->key_size-1].hdr.prev_size = a->key_size; - } - const uint32_t x = B_HDR(a, a->key.v)->size4x & 2; - while ((B_HDR(a, a->key.v + a->key_size)->size4x & 1) == 0) { - unlink_block(a, (struct BlockId){a->key.v + a->key_size}); - a->key_size += B_HDR(a, a->key.v + a->key_size)->size4x / 4; - B_HDR(a, a->key.v)->size4x = a->key_size * 4 | x; - B_HDR(a, a->key.v + a->key_size)->prev_size = a->key_size; - } - } + // TODO: lock/unlock mutex + return unsafe_malloc(H, nbytes); } -static size_t block_size(void *ptr) +size_t pawZ_sizeof(void *ptr) { paw_assert(ptr != NULL); - struct Block *b = ptr; + struct Chunk *b = ptr; paw_assert((b[-1].hdr.size4x & 1) != 0); return (b[-1].hdr.size4x & ~3) * 2 - 4; } -static void *alloc_uninit(struct FastBins *a, size_t id) +// Modified from SQLite (mem3.c) +static void unsafe_free(struct Heap *H, void *ptr) { - void *ptr = a->uninit; - const size_t want = MIN_BIN_SIZE * (id + 1); - if (want > a->uninit_size) return NULL; - UNPOISON_MEMORY_REGION(a->uninit, want); - a->uninit = BUMP(a->uninit, want); - a->uninit_size -= want; - return ptr; + struct Allocator *a = H->a; + struct Chunk *b = ptr; + paw_assert(b > a->chunks && b < &a->chunks[a->nchunks]); + const struct ChunkId i = {b - a->chunks}; + uint32_t nchunks = CHUNK_HDR(a, i.v)->size4x / 4; + paw_assert((CHUNK_HDR(a, i.v)->size4x & 1) == 1); + paw_assert(nchunks == CHUNK_HDR(a, i.v)->size4x / 4); + paw_assert(i.v + nchunks <= a->nchunks + 1); + CHUNK_HDR(a, i.v)->size4x &= ~1; + CHUNK_HDR(a, i.v + nchunks)->prev_size = nchunks; + CHUNK_HDR(a, i.v + nchunks)->size4x &= ~2; + link_chunk(a, i); + + if (a->key.v != BAD_CHUNK) { + while ((CHUNK_HDR(a, a->key.v)->size4x & 2) == 0) { + nchunks = CHUNK_HDR(a, a->key.v)->prev_size; + a->key.v -= nchunks; + a->key_size += nchunks; + unlink_chunk(a, a->key); + const uint32_t x = CHUNK_HDR(a, a->key.v)->size4x & 2; + CHUNK_HDR(a, a->key.v)->size4x = a->key_size * 4 | x; + a->chunks[a->key.v + a->key_size-1].hdr.prev_size = a->key_size; + } + const uint32_t x = CHUNK_HDR(a, a->key.v)->size4x & 2; + while ((CHUNK_HDR(a, a->key.v + a->key_size)->size4x & 1) == 0) { + unlink_chunk(a, (struct ChunkId){a->key.v + a->key_size}); + a->key_size += CHUNK_HDR(a, a->key.v + a->key_size)->size4x / 4; + CHUNK_HDR(a, a->key.v)->size4x = a->key_size * 4 | x; + CHUNK_HDR(a, a->key.v + a->key_size)->prev_size = a->key_size; + } + } } -static void *fast_malloc(struct FastBins *a, size_t size) +static void z_free(struct Heap *H, void *ptr) { - paw_assert(0 < size && size <= MAX_BIN_SIZE); - const int id = BIN_ID(size); - struct BinInfo **pbin = &a->info[id]; - if (*pbin != NULL) { - struct BinInfo *bin = *pbin; - *pbin = bin->prev; - return ERASE_TYPE(bin); - } - return alloc_uninit(a, id); + // TODO: lock/unlock mutex + unsafe_free(H, ptr); } -static void fast_free(struct FastBins *a, void *ptr, size_t size) +static size_t compute_flag_count(size_t h) { - if (ptr == NULL) return; - struct BinInfo *bin = CAST(ptr, struct BinInfo *); - const int id = BIN_ID(size); - *bin = (struct BinInfo){ - .prev = a->info[id], - }; - a->info[id] = bin; + const size_t F = FLAGS_PER_BYTE; + const size_t m = HEAP_META_SIZE; + const size_t p = sizeof(void *); + return F * (h - m) / (F * p + 1); } -// TODO: Put everything in the same allocation -int pawZ_init(paw_Env *P, size_t heap_size) +int pawZ_init(paw_Env *P, void *heap, size_t heap_size, paw_Bool is_owned) { - if (heap_size < MIN_HEAP_SIZE) return PAW_EVALUE; - const size_t nflags = heap_size / sizeof(void *); - const size_t zflags = nflags / FLAGS_PER_BYTE; - paw_assert(zflags * FLAGS_PER_BYTE == nflags); + paw_assert(heap != NULL); + const size_t nf = compute_flag_count(heap_size); + const size_t zf = nf / FLAGS_PER_BYTE; - struct Heap *H = OS_MALLOC(P, sizeof(struct Heap) + zflags); - if (H == NULL) return PAW_EMEMORY; +#define SKIP_CHUNK(z) (heap = BUMP_PTR(heap, PAW_ROUND_UP(z)), \ + heap_size -= PAW_ROUND_UP(z)) + struct Heap *H = heap; + P->H = H; *H = (struct Heap){ - .heap_size = heap_size, - .nflags = nflags, + .is_owned = is_owned, + .nflags = nf, .P = P, }; - memset(H->flags, 0, zflags); - P->H = H; + memset(H->flags, 0, zf); + SKIP_CHUNK(sizeof(struct Heap)); + SKIP_CHUNK(zf); - void *heap = OS_MALLOC(P, heap_size); - if (heap == NULL) goto no_memory; - H->heap = heap; - -#define SKIP_CHUNK(z) (heap = BUMP(heap, aligned(z)), \ - heap_size -= aligned(z)) - H->a_block = heap; - SKIP_CHUNK(sizeof(struct BlockAllocator)); - init_block_allocator(H->a_block, heap, heap_size); + H->a = heap; + SKIP_CHUNK(sizeof(struct Allocator)); + init_chunk_allocator(H->a, heap, heap_size); #undef SKIP_CHUNK - - const size_t arena_size = heap_size / BIN_FACTOR; - void *arena = block_malloc(H->a_block, arena_size); - if (arena == NULL) goto no_memory; - POISON_MEMORY_REGION(arena, arena_size); - H->bins = (struct FastBins){ - .uninit_size = arena_size, - .arena_size = arena_size, - .uninit = arena, - .arena = arena, - }; - H->bounds[0] = CAST_UPTR(H->heap); - H->bounds[1] = H->bounds[0] + H->heap_size; + // flags must cover the entire usable heap space + H->bounds[0] = CAST_UPTR(heap); + paw_assert(nf * sizeof(void *) <= heap_size); + H->bounds[1] = H->bounds[0] + nf * sizeof(void *); return PAW_OK; no_memory: @@ -474,58 +440,42 @@ static void detect_leaks(paw_Env *P) void pawZ_uninit(paw_Env *P) { - struct Heap *H = P->H; - if (H != NULL) { - if (H->bins.arena != NULL) { - UNPOISON_MEMORY_REGION(H->bins.uninit, H->bins.uninit_size); - block_free(P->H->a_block, H->bins.arena, H->bins.arena_size); - } - if (H->heap != NULL) OS_FREE(P, H->heap, H->heap_size); - OS_FREE(P, H, sizeof(*H) + H->nflags / FLAGS_PER_BYTE); - P->H = NULL; + if (P->H->is_owned) { + P->alloc(P->ud, P, P->heap_size, 0); } } #define CHECK_UNUSED(H, ptr) paw_assert(!pawZ_get_flag(H, CAST_UPTR(ptr))) -static void z_free(struct Heap *H, void *ptr, size_t size) +static void *z_realloc(struct Heap *H, void *old_ptr, size_t new_size) { - size = aligned(size); - if (size <= MAX_BIN_SIZE) { - fast_free(&H->bins, ptr, size); - } else { - block_free(H->a_block, ptr, size); + if (old_ptr == NULL) { + return z_malloc(H, new_size); } -} - -static void *z_malloc(struct Heap *H, size_t size) -{ - size = aligned(size); - void *ptr = size <= MAX_BIN_SIZE - ? fast_malloc(&H->bins, size) - : block_malloc(H->a_block, size); - return ptr; -} - -static void *z_realloc(struct Heap *H, void *old_ptr, size_t old_size, size_t new_size) -{ - void *new_ptr = z_malloc(H, new_size); - if (new_ptr != NULL && old_size > 0) { - const size_t copy_size = PAW_MIN(old_size, new_size); - memcpy(new_ptr, old_ptr, copy_size); + if (new_size == 0) { + z_free(H, old_ptr); + return 0; + } + const size_t old_size = pawZ_sizeof(old_ptr); + if (new_size <= old_size && new_size >= old_size - 128) { + return old_ptr; + } + void *new_ptr = unsafe_malloc(H, new_size); + if (new_ptr != NULL) { + memcpy(new_ptr, old_ptr, PAW_MIN(old_size, new_size)); + unsafe_free(H, old_ptr); } - z_free(H, old_ptr, old_size); return new_ptr; } -void *pawZ_alloc(paw_Env *P, void *ptr, size_t old_size, size_t new_size) +void *pawZ_alloc(paw_Env *P, void *ptr, size_t size) { struct Heap *H = P->H; - if (new_size == 0) { - z_free(H, ptr, old_size); + if (size == 0) { + z_free(H, ptr); return NULL; } - if (old_size == 0) return z_malloc(H, new_size); - return z_realloc(H, ptr, old_size, new_size); + if (ptr == NULL) return z_malloc(H, size); + return z_realloc(H, ptr, size); } diff --git a/src/alloc.h b/src/alloc.h index 2d2899d..a119a6b 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -4,11 +4,8 @@ // // alloc: Low-level memory allocation routines // -// Uses 2 different heuristics depending on allocation size. For small allocations, -// we use a slotted allocator that never attempts to merge freelist entries. For -// large allocations we use a slightly-modified version of the memsys3 allocator from -// SQLite (mem3.c). The large object allocator uses only the hash-based partitioning -// scheme, since it expects only large objects. +// Uses code modified from SQLite's memsys3 allocator, which can be found in +// 'src/mem3.c' in the SQLite repository. // // TODO: Handle expanding the heap: change paw_Alloc to be more like sbrk, since // that is all we really need from the user (an initial 'heap' pointer, and a callback @@ -23,35 +20,24 @@ struct GcFlag { uint8_t value; }; -#define FAST_BIN_COUNT 64 - -struct FastBins { - struct BinInfo *info[FAST_BIN_COUNT]; - size_t uninit_size; - size_t arena_size; - void *uninit; - void *arena; -}; - - struct Heap { - struct FastBins bins; - struct BlockAllocator *a_block; + struct Allocator *a; uintptr_t bounds[2]; - size_t heap_size; - void *heap; paw_Env *P; + paw_Bool is_owned; + size_t nflags; uint8_t flags[]; }; #define Z_IN_BOUNDS(H, u) ((H)->bounds[0] <= (u) && (u) < (H)->bounds[1]) -int pawZ_init(paw_Env *P, size_t heap_size); +int pawZ_init(paw_Env *P, void *heap, size_t heap_size, paw_Bool is_owned); void pawZ_uninit(paw_Env *P); -void *pawZ_alloc(paw_Env *P, void *ptr, size_t old_size, size_t new_size); +size_t pawZ_sizeof(void *ptr); +void *pawZ_alloc(paw_Env *P, void *ptr, size_t size); void pawZ_set_flag(struct Heap *H, uintptr_t ptr); void pawZ_clear_flag(struct Heap *H, uintptr_t ptr); diff --git a/src/api.c b/src/api.c index 3738d2c..c6a685c 100644 --- a/src/api.c +++ b/src/api.c @@ -20,8 +20,6 @@ #include #include -#include - static void *default_alloc(void *ud, void *ptr, size_t old_size, size_t new_size) { paw_unused(ud); @@ -65,21 +63,34 @@ static void open_aux(paw_Env *P, void *arg) CHECK_GC(P); } -paw_Env *paw_open(paw_Alloc alloc, void *ud) +#define OR_DEFAULT(a, b) ((a) ? (a) : (b)) + +paw_Env *paw_open(const struct paw_Options *o) { - alloc = alloc ? alloc : default_alloc; - paw_Env *P = alloc(ud, NULL, 0, sizeof *P); - if (P == NULL) return NULL; + size_t heap_size = OR_DEFAULT(o->heap_size, PAW_HEAP_DEFAULT); + heap_size &= ~(PAW_ALIGN - 1); // round down + if (heap_size < PAW_HEAP_MIN) return NULL; + + void *ud = OR_DEFAULT(o->ud, NULL); + paw_Alloc alloc = OR_DEFAULT(o->alloc, default_alloc); + void *heap = OR_DEFAULT(o->heap, alloc(ud, NULL, 0, heap_size)); + const paw_Bool owns_heap = o->heap == NULL; + if (heap == NULL) return NULL; + paw_assert(PAW_IS_ALIGNED(heap)); + const size_t zh = heap_size; + void *ph = heap; + + paw_Env *P = heap; *P = (paw_Env){ - .alloc = alloc, + .heap_size = heap_size, + .alloc = alloc, .ud = ud, }; + heap = BUMP_PTR(heap, PAW_ROUND_UP(sizeof(*P))); + heap_size -= PAW_ROUND_UP(sizeof(*P)); - // TODO: let user pass in options. These options should work for programs that - // don't use a huge amount of memory. - const size_t heap_size = 8388608 * 2; - if (pawZ_init(P, heap_size)) { - alloc(ud, P, sizeof(*P), 0); + if (pawZ_init(P, heap, heap_size, owns_heap)) { + if (owns_heap) alloc(ud, ph, zh, 0); return NULL; } if (pawC_try(P, open_aux, NULL)) { @@ -96,8 +107,6 @@ void paw_close(paw_Env *P) pawE_uninit(P); pawS_uninit(P); pawZ_uninit(P); - - P->alloc(P->ud, P, sizeof *P, 0); } int paw_find_public(paw_Env *P) @@ -319,7 +328,7 @@ int paw_call(paw_Env *P, int argc) int paw_get_count(paw_Env *P) { - return P->top.p - P->cf->base.p; + return CAST(P->top.p - P->cf->base.p, int); } static int upvalue_index(int nup, int index) diff --git a/src/ast.c b/src/ast.c index ce7fc13..0b08fe0 100644 --- a/src/ast.c +++ b/src/ast.c @@ -39,7 +39,7 @@ void pawAst_free(struct Ast *ast) struct T *pawAst_new_##name(struct Ast *ast, enum T##Kind kind) \ { \ struct T *r = pawK_pool_alloc(ENV(ast), &(ast)->pool, sizeof(struct T)); \ - r->hdr.line = (ast)->lex->line; \ + r->hdr.line = (ast)->lex->last_line; \ r->hdr.kind = kind; \ return r; \ } diff --git a/src/auxlib.c b/src/auxlib.c index d3d2e1f..331e68c 100644 --- a/src/auxlib.c +++ b/src/auxlib.c @@ -91,7 +91,7 @@ void pawL_add_value(paw_Env *P, Buffer *buf, paw_Type type) // Table and stringify algorithm modified from micropython static const uint8_t kLogBase2[] = { 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, // + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, }; size_t pawL_integer_format_size(size_t nbits, int base) diff --git a/src/call.c b/src/call.c index 456ba06..7e2181a 100644 --- a/src/call.c +++ b/src/call.c @@ -105,7 +105,7 @@ void pawC_stack_grow(paw_Env *P, int n) { paw_assert(n > 0); paw_assert(P->bound.p >= P->stack.p); - const int alloc = CAST_SIZE(P->bound.p - P->stack.p); + const int alloc = CAST(P->bound.p - P->stack.p, int); pawC_stack_realloc(P, NEXT_ALLOC(alloc, n)); } diff --git a/src/code.c b/src/code.c index 94f454a..c2c8071 100644 --- a/src/code.c +++ b/src/code.c @@ -15,7 +15,7 @@ static void add_line(struct FuncState *fs) } pawM_grow(P, p->lines, fs->nlines, p->nlines); p->lines[fs->nlines++] = (struct LineInfo){ - .line = -1, // TODO: Get line from somewhere... + .line = fs->line, .pc = fs->pc, }; } diff --git a/src/codegen.c b/src/codegen.c index 4928141..2183e47 100644 --- a/src/codegen.c +++ b/src/codegen.c @@ -10,7 +10,7 @@ #include "parse.h" #include "type.h" -#define SYNTAX_ERROR(G, ...) pawE_error(ENV(G), PAW_ESYNTAX, -1, __VA_ARGS__) +#define SYNTAX_ERROR(G, ...) pawE_error(ENV(G), PAW_ESYNTAX, (G)->fs->line, __VA_ARGS__) #define GET_DECL(G, id) pawHir_get_decl((G)->hir, id) #define TYPE2CODE(G, type) (pawP_type2code((G)->C, type)) @@ -503,7 +503,7 @@ static struct HirVarInfo find_var(struct Generator *G, const String *name) if (!resolve_local(fs, name, &info) && // not local !resolve_upvalue(fs, name, &info) && // not local to caller !resolve_global(G, name, &info)) { // not found - pawE_error(ENV(G), PAW_ENAME, -1, "undefined variable '%s'", name->text); + pawE_error(ENV(G), PAW_ENAME, fs->line, "undefined variable '%s'", name->text); } return info; } @@ -625,6 +625,8 @@ static void code_basic_lit(struct HirVisitor *V, struct HirLiteralExpr *e) { struct Generator *G = V->ud; struct FuncState *fs = G->fs; + fs->line = e->line; + if (e->basic.t == PAW_TUNIT) { pawK_code_0(fs, OP_PUSHUNIT); } else if (e->basic.t != PAW_TBOOL) { @@ -639,21 +641,25 @@ static void code_basic_lit(struct HirVisitor *V, struct HirLiteralExpr *e) static void code_tuple_lit(struct HirVisitor *V, struct HirLiteralExpr *e) { + struct Generator *G = V->ud; + struct FuncState *fs = G->fs; + fs->line = e->line; + struct HirTupleLit *lit = &e->tuple; V->VisitExprList(V, lit->elems); - struct Generator *G = V->ud; - struct FuncState *fs = G->fs; pawK_code_U(fs, OP_NEWTUPLE, lit->elems->count); } static void code_container_lit(struct HirVisitor *V, struct HirLiteralExpr *e) { + struct Generator *G = V->ud; + struct FuncState *fs = G->fs; + fs->line = e->line; + struct HirContainerLit *lit = &e->cont; V->VisitExprList(V, lit->items); - struct Generator *G = V->ud; - struct FuncState *fs = G->fs; const Op op = lit->code == PAW_TVECTOR ? OP_NEWVECTOR : OP_NEWMAP; pawK_code_U(fs, op, lit->items->count); } @@ -664,6 +670,7 @@ static void code_composite_lit(struct HirVisitor *V, struct HirLiteralExpr *e) struct HirCompositeLit *lit = &e->comp; struct Generator *G = V->ud; struct FuncState *fs = G->fs; + fs->line = e->line; const DefId did = e->type->adt.did; struct HirAdtDecl *d = &GET_DECL(G, did)->adt; @@ -693,11 +700,8 @@ static void code_literal_expr(struct HirVisitor *V, struct HirLiteralExpr *e) } } -static void code_and(struct HirVisitor *V, struct HirLogicalExpr *e) +static void code_and(struct HirVisitor *V, struct FuncState *fs, struct HirLogicalExpr *e) { - struct Generator *G = V->ud; - struct FuncState *fs = G->fs; - V->VisitExpr(V, e->lhs); const int jump = code_jump(fs, OP_JUMPFALSE); pawK_code_U(fs, OP_POP, 1); @@ -705,11 +709,8 @@ static void code_and(struct HirVisitor *V, struct HirLogicalExpr *e) patch_here(fs, jump); } -static void code_or(struct HirVisitor *V, struct HirLogicalExpr *e) +static void code_or(struct HirVisitor *V, struct FuncState *fs, struct HirLogicalExpr *e) { - struct Generator *G = V->ud; - struct FuncState *fs = G->fs; - V->VisitExpr(V, e->lhs); const int else_jump = code_jump(fs, OP_JUMPFALSE); const int then_jump = code_jump(fs, OP_JUMP); @@ -721,10 +722,14 @@ static void code_or(struct HirVisitor *V, struct HirLogicalExpr *e) static void code_logical_expr(struct HirVisitor *V, struct HirLogicalExpr *e) { + struct Generator *G = V->ud; + struct FuncState *fs = G->fs; + fs->line = e->line; + if (e->is_and) { - code_and(V, e); + code_and(V, fs, e); } else { - code_or(V, e); + code_or(V, fs, e); } } @@ -732,6 +737,7 @@ static void code_chain_expr(struct HirVisitor *V, struct HirChainExpr *e) { struct Generator *G = V->ud; struct FuncState *fs = G->fs; + fs->line = e->line; V->VisitExpr(V, e->target); const int jump = code_jump(fs, OP_JUMPNULL); @@ -743,6 +749,7 @@ static void code_unop_expr(struct HirVisitor *V, struct HirUnOpExpr *e) { struct Generator *G = V->ud; struct FuncState *fs = G->fs; + fs->line = e->line; V->VisitExpr(V, e->target); CODE_OP(fs, OP_UNOP, e->op, HIR_TYPEOF(e->target)); @@ -752,6 +759,8 @@ static void code_binop_expr(struct HirVisitor *V, struct HirBinOpExpr *e) { struct Generator *G = V->ud; struct FuncState *fs = G->fs; + fs->line = e->line; + V->VisitExpr(V, e->lhs); V->VisitExpr(V, e->rhs); CODE_OP(fs, OP_BINOP, e->op, HIR_TYPEOF(e->rhs)); @@ -766,6 +775,7 @@ static void code_expr_stmt(struct HirVisitor *V, struct HirExprStmt *s) { struct Generator *G = V->ud; struct FuncState *fs = G->fs; + fs->line = s->line; if (s->rhs != NULL) { code_setter(V, s->lhs, s->rhs); @@ -835,6 +845,8 @@ static void code_func(struct HirVisitor *V, struct HirFuncDecl *d, struct HirVar static void code_field_decl(struct HirVisitor *V, struct HirFieldDecl *d) { struct Generator *G = V->ud; + G->fs->line = d->line; + new_local(G->fs, d->name, d->type); } @@ -842,23 +854,20 @@ static void code_var_decl(struct HirVisitor *V, struct HirVarDecl *d) { struct Generator *G = V->ud; struct FuncState *fs = G->fs; + fs->line = d->line; + declare_local(fs, d->name, d->type); V->VisitExpr(V, d->init); define_local(fs); } -static void code_adt_decl(struct HirVisitor *V, struct HirAdtDecl *d) -{ - // NOOP - paw_unused(V); - paw_unused(d); -} - static void code_block_stmt(struct HirVisitor *V, struct HirBlock *b) { struct BlockState bs; struct Generator *G = V->ud; struct FuncState *fs = G->fs; + fs->line = b->line; + enter_block(fs, &bs, PAW_FALSE); V->VisitStmtList(V, b->stmts); leave_block(fs); @@ -868,6 +877,8 @@ static void code_return_stmt(struct HirVisitor *V, struct HirReturnStmt *s) { struct Generator *G = V->ud; struct FuncState *fs = G->fs; + fs->line = s->line; + V->VisitExpr(V, s->expr); pawK_code_0(fs, OP_RETURN); } @@ -941,6 +952,8 @@ static void code_variant_constructor(struct HirVisitor *V, struct HirType *type, static void code_path_expr(struct HirVisitor *V, struct HirPathExpr *e) { struct Generator *G = V->ud; + G->fs->line = e->line; + if (is_variant_constructor(G, e->type)) { code_variant_constructor(V, e->type, NULL); } else { @@ -954,6 +967,7 @@ static void code_call_expr(struct HirVisitor *V, struct HirCallExpr *e) paw_assert(HirIsFuncType(e->func)); struct Generator *G = V->ud; struct FuncState *fs = G->fs; + fs->line = e->line; if (is_variant_constructor(G, e->func)) { code_variant_constructor(V, e->func, e->args); @@ -974,28 +988,12 @@ static void code_conversion_expr(struct HirVisitor *V, struct HirConversionExpr const Op op = e->to == PAW_TBOOL ? OP_CASTBOOL : e->to == PAW_TINT ? OP_CASTINT : OP_CASTFLOAT; + G->fs->line = e->line; V->VisitExpr(V, e->arg); pawK_code_U(G->fs, op, from->adt.base); } -// Stamp out monomorphizations of a function template -static void monomorphize_func(struct HirVisitor *V, struct HirFuncDecl *d) -{ - if (d->monos == NULL) { - return; - } - struct Generator *G = V->ud; - for (int i = 0; i < d->monos->count; ++i) { - struct HirDecl *decl = d->monos->data[i]; - struct HirFuncDecl *inst = HirGetFuncDecl(decl); - struct HirFuncDef *fdef = HirGetFuncDef(inst->type); - inst->name = mangle_name(G, inst->name, fdef->types); - const struct HirVarInfo info = new_global(G, inst->name, inst->type, d->is_pub); - code_func(V, inst, info); - } -} - static void register_func(struct Generator *G, struct HirFuncDecl *d) { const struct HirVarInfo info = new_global(G, d->name, d->type, d->is_pub); @@ -1046,21 +1044,11 @@ static void code_items(struct HirVisitor *V) } } -static void code_func_decl(struct HirVisitor *V, struct HirFuncDecl *d) -{ - struct Generator *G = V->ud; - if (d->generics == NULL) { - const struct HirVarInfo info = new_global(G, d->name, d->type, d->is_pub); - code_func(V, d, info); - } else { - monomorphize_func(V, d); - } -} - static void code_if_stmt(struct HirVisitor *V, struct HirIfStmt *s) { struct Generator *G = V->ud; struct FuncState *fs = G->fs; + fs->line = s->line; V->VisitExpr(V, s->cond); const int else_jump = code_jump(fs, OP_JUMPFALSEPOP); @@ -1085,13 +1073,15 @@ static void close_until_loop(struct FuncState *fs) } bs = outer; } - pawE_error(ENV(fs->G), PAW_ESYNTAX, -1, "label outside loop"); + pawE_error(ENV(fs->G), PAW_ESYNTAX, fs->line, "label outside loop"); } static void code_label_stmt(struct HirVisitor *V, struct HirLabelStmt *s) { struct Generator *G = V->ud; struct FuncState *fs = G->fs; + fs->line = s->line; + close_until_loop(fs); // fix the stack add_label(fs, s->label); // emit a jump, to be patched later } @@ -1118,14 +1108,15 @@ static void code_dowhile_stmt(struct HirVisitor *V, struct HirWhileStmt *s) static void code_while_stmt(struct HirVisitor *V, struct HirWhileStmt *s) { - if (s->is_dowhile) { - code_dowhile_stmt(V, s); - return; - } struct Generator *G = V->ud; struct FuncState *fs = G->fs; struct BlockState bs; + fs->line = s->line; + if (s->is_dowhile) { + code_dowhile_stmt(V, s); + return; + } enter_block(fs, &bs, PAW_TRUE); const int loop = fs->pc; V->VisitExpr(V, s->cond); @@ -1203,6 +1194,8 @@ static void code_for_stmt(struct HirVisitor *V, struct HirForStmt *s) struct BlockState bs; struct Generator *G = V->ud; struct FuncState *fs = G->fs; + fs->line = s->line; + enter_block(fs, &bs, PAW_TRUE); if (s->is_fornum) { code_fornum_stmt(V, s); @@ -1215,27 +1208,33 @@ static void code_for_stmt(struct HirVisitor *V, struct HirForStmt *s) static void code_index_expr(struct HirVisitor *V, struct HirIndex *e) { struct Generator *G = V->ud; + struct FuncState *fs = G->fs; struct HirType *target = HIR_TYPEOF(e->target); const paw_Type t = basic_code(G, target); + fs->line = e->line; + V->VisitExpr(V, e->target); if (e->is_slice) { code_slice_indices(V, e->first, e->second, target); - pawK_code_U(G->fs, OP_GETSLICE, t); + pawK_code_U(fs, OP_GETSLICE, t); } else { V->VisitExpr(V, e->first); - pawK_code_U(G->fs, OP_GETITEM, t); + pawK_code_U(fs, OP_GETITEM, t); } } static void code_selector_expr(struct HirVisitor *V, struct HirSelector *e) { struct Generator *G = V->ud; + struct FuncState *fs = G->fs; + fs->line = e->line; + V->VisitExpr(V, e->target); if (e->is_index) { - pawK_code_U(G->fs, OP_GETTUPLE, e->index); + pawK_code_U(fs, OP_GETTUPLE, e->index); } else { const struct HirVarInfo info = resolve_attr(G, HIR_TYPEOF(e->target), e->name); - pawK_code_U(G->fs, OP_GETATTR, info.index); + pawK_code_U(fs, OP_GETATTR, info.index); } } @@ -1246,7 +1245,7 @@ static void add_builtin_func(struct Generator *G, const char *name) const int g = pawE_new_global(P, str); GlobalVar *gv = pawE_get_global(P, g); const Value key = {.o = CAST_OBJECT(str)}; - const Value *pv = pawH_get_(P->builtin, key); + const Value *pv = pawH_get(P->builtin, key); gv->value = *pv; } @@ -1273,13 +1272,16 @@ static void setup_pass(struct HirVisitor *V, struct Generator *G) V->VisitLabelStmt = code_label_stmt; V->VisitReturnStmt = code_return_stmt; V->VisitVarDecl = code_var_decl; - V->VisitFuncDecl = code_func_decl; - V->VisitAdtDecl = code_adt_decl; V->VisitFieldDecl = code_field_decl; add_builtin_func(G, "assert"); add_builtin_func(G, "print"); + add_builtin_func(G, "_string_split"); + add_builtin_func(G, "_string_join"); + add_builtin_func(G, "_string_find"); + add_builtin_func(G, "_string_starts_with"); + add_builtin_func(G, "_string_ends_with"); add_builtin_func(G, "_vector_push"); add_builtin_func(G, "_vector_pop"); add_builtin_func(G, "_vector_insert"); diff --git a/src/compile.c b/src/compile.c index 8441226..1069270 100644 --- a/src/compile.c +++ b/src/compile.c @@ -29,7 +29,7 @@ paw_Type pawP_type2code(struct Compiler *C, struct HirType *type) String *pawP_scan_nstring(paw_Env *P, Map *st, const char *s, size_t n) { const Value *pv = pawC_pushns(P, s, n); - Value *value = pawH_action(P, st, *pv, MAP_ACTION_CREATE); + Value *value = pawH_create(P, st, *pv); *value = *pv; // anchor in map pawC_pop(P); CHECK_GC(P); diff --git a/src/config.h b/src/config.h index abf6599..b536c88 100644 --- a/src/config.h +++ b/src/config.h @@ -26,6 +26,14 @@ # define PAW_STACK_MAX 1000000 #endif +#ifndef PAW_HEAP_DEFAULT +# define PAW_HEAP_DEFAULT ((size_t)16777216) +#endif + +#ifndef PAW_HEAP_MIN +# define PAW_HEAP_MIN ((size_t)16384) +#endif + #if defined(__APPLE__) # define PAW_OS_MACOS # define PAW_OS_POSIX diff --git a/src/debug.c b/src/debug.c index 74afa66..de5bd0c 100644 --- a/src/debug.c +++ b/src/debug.c @@ -1,3 +1,6 @@ +// Copyright (c) 2024, The paw Authors. All rights reserved. +// This source code is licensed under the MIT License, which can be found in +// LICENSE.md. See AUTHORS.md for a list of contributor names. #include "debug.h" #include "auxlib.h" #include "call.h" diff --git a/src/env.h b/src/env.h index fa779be..a6dd04d 100644 --- a/src/env.h +++ b/src/env.h @@ -71,7 +71,7 @@ struct MethodList { Value data[]; }; -typedef struct paw_Env { paw_Bool done; // TODO: remove +typedef struct paw_Env { StringTable strings; CallFrame main; @@ -103,6 +103,7 @@ typedef struct paw_Env { paw_Bool done; // TODO: remove int alloc; } gv; + size_t heap_size; struct Heap *H; paw_Alloc alloc; void *ud; diff --git a/src/hir.h b/src/hir.h index 9b48fec..9bfcf6e 100644 --- a/src/hir.h +++ b/src/hir.h @@ -33,7 +33,7 @@ struct Resolver; X(CallExpr, call) \ X(Index, index) \ X(Selector, select) \ - X(StructField, sitem) \ + X(StructField, sitem) \ X(MapElem, mitem) #define HIR_TYPE_LIST(X) \ diff --git a/src/lib.c b/src/lib.c index 79ec217..bc084d4 100644 --- a/src/lib.c +++ b/src/lib.c @@ -45,89 +45,6 @@ int pawL_check_varargc(paw_Env *P, int min, int max) return narg; } -// static void try_aux(paw_Env *P, void *arg) -//{ -// const int argc = *CAST(arg, int *); -// const Value f = CF_BASE(1); -// pawC_call(P, v_object(f), argc - 1); -// } -// -// static int base_try(paw_Env *P) -//{ -// int argc = pawL_check_varargc(P, 1, UINT8_MAX); -// const int status = pawC_try(P, try_aux, &argc); -// paw_push_int(P, status); -// return 1; -// } -// -// static int base_require(paw_Env *P) -//{ -// pawL_check_argc(P, 1); -// const char *name = pawL_check_string(P, 1); -// pawL_require_lib(P, name); -// return 1; -// } -// -// #de f in e make_to_bool(suffix, T) \ -// static int base_to_bool_ ## suffix(paw_Env *P) \ -// { \ -// pawL_check_argc(P, 1); \ -// pawR_to_bool(P, T); \ -// return 1; \ -// } -// make_to_bool(s, PAW_TSTRING) -// make_to_bool(i, PAW_TINT) -// make_to_bool(f, PAW_TFLOAT) -// -// #de f in e make_to_int(suffix, T) \ -// static int base_to_int_ ## suffix(paw_Env *P) \ -// { \ -// pawL_check_argc(P, 1); \ -// pawR_to_int(P, T); \ -// return 1; \ -// } -// make_to_int(s, PAW_TSTRING) -// make_to_int(i, PAW_TINT) -// make_to_int(f, PAW_TFLOAT) -// -// #de f in e make_to_float(suffix, T) \ -// static int base_to_float_ ## suffix(paw_Env *P) \ -// { \ -// pawL_check_argc(P, 1); \ -// pawR_to_float(P, T); \ -// return 1; \ -// } -// make_to_float(s, PAW_TSTRING) -// make_to_float(i, PAW_TINT) -// make_to_float(f, PAW_TFLOAT) -// -// static int base_chr(paw_Env *P) -//{ -// pawL_check_argc(P, 1); -// const paw_Int ord = pawL_check_int(P, 1); -// if (0x00 <= ord && ord <= 0xFF) { -// const uint8_t chr[] = {ord}; -// paw_push_nstring(P, (const char *)chr, 1); -// } else { -// // TODO: Encode UTF-8 codepoint -// pawR_error(P, PAW_EOVERFLOW, "FIXME: Support UTF-8!"); -// } -// return 1; -// } -// -// static int base_ord(paw_Env *P) -//{ -// pawL_check_argc(P, 1); -// const char *str = pawL_check_string(P, 1); -// const size_t len = paw_length(P, 1); -// if (!len || len > 4) { -// pawR_error(P, PAW_EVALUE, "invalid UTF-8"); -// } -// // TODO: Decode UTF-8 codepoint -// paw_push_int(P, str[0]); -// return 1; -// } - static int base_assert(paw_Env *P) { if (v_false(CF_BASE(1))) { @@ -144,61 +61,6 @@ static int base_print(paw_Env *P) return 0; } -// struct ReaderState { -// const char *data; -// size_t size; -// }; -// -// static const char *reader(paw_Env *P, void *ud, size_t *size) -//{ -// paw_unused(P); -// struct ReaderState *ld = ud; -// -// // Return the whole string. -// *size = ld->size; -// ld->size = 0; -// return ld->data; -// } -// -// static int base_load(paw_Env *P) -//{ -// // load() takes a single parameter: a string containing source code. -// pawL_check_argc(P, 1); -// pawL_check_type(P, 1, PAW_TSTRING); -// -// struct ReaderState state = { -// .data = paw_string(P, 1), -// .size = paw_length(P, 1), -// }; -// // Load the code and leave it in a closure object on the stack. -// const int status = paw_load(P, reader, "", &state); -// if (status != PAW_OK) { -// // Rethrow the error. The error message is already on the stack. -// pawC_throw(P, status); -// } -// return 1; -// } -// -// static int base_getattr(paw_Env *P) -//{ -// const int argc = pawL_check_varargc(P, 2, 3); -// const paw_Bool fallback = argc == 3; -// if (fallback) { -// paw_rotate(P, -3, 1); -// } -// if (pawR_getattr_raw(P, fallback)) { -// pawR_attr_error(P, P->top.p[-1]); -// } -// return 1; -// } -// -// static int base_setattr(paw_Env *P) -//{ -// pawL_check_argc(P, 3); -// pawR_setattr(P); -// return 0; -// } - static int vector_insert(paw_Env *P) { Vector *vec = v_vector(CF_BASE(1)); @@ -229,8 +91,7 @@ static int vector_pop(paw_Env *P) static paw_Int clamped_index(paw_Env *P, int loc, paw_Int n) { const paw_Int i = v_int(CF_BASE(loc)); - return i < 0 ? 0 : i >= n ? n - 1 - : i; + return i < 0 ? 0 : i >= n ? n - 1 : i; } // TODO: It would be nice to let pop() take an optional parameter indicating the @@ -259,25 +120,14 @@ static int vector_clone(paw_Env *P) return 1; } -// static String *check_string(paw_Env *P, int i) -//{ -// const Value v = CF_BASE(i); -// if (v_type(v) != VSTRING) { -// pawR_error(P, PAW_ETYPE, "expected string"); -// } -// return v_string(v); -// } - -static const char *find_substr(const char *str, size_t nstr, const char *sub, - size_t nsub) +static const char *find_substr(const char *str, size_t nstr, const char *sub, size_t nsub) { - if (nsub == 0) { - return str; - } + if (nsub == 0) return str; const char *ptr = str; const char *end = str + nstr; while ((ptr = strchr(ptr, sub[0]))) { - if (nsub <= CAST_SIZE(end - ptr) && 0 == memcmp(ptr, sub, nsub)) { + if (nsub <= CAST_SIZE(end - ptr) && + 0 == memcmp(ptr, sub, nsub)) { return ptr; } str = ptr + nsub; @@ -287,11 +137,12 @@ static const char *find_substr(const char *str, size_t nstr, const char *sub, static int string_find(paw_Env *P) { - pawL_check_argc(P, 1); - const String *s = v_string(CF_BASE(0)); - const String *find = v_string(CF_BASE(1)); - const char *result = - find_substr(s->text, s->length, find->text, find->length); + pawL_check_argc(P, 2); + const String *s = v_string(CF_BASE(1)); + const String *find = v_string(CF_BASE(2)); + const char *result = find_substr( + s->text, s->length, + find->text, find->length); if (result) { // index of substring v_set_int(P->top.p - 1, result - s->text); } else { // not found @@ -300,77 +151,76 @@ static int string_find(paw_Env *P) return 1; } -//// TODO: Remove '/*TODO*/+1', these should be methods, which take the -//// context as the implicit first parameter -// static int string_split(paw_Env *P) -//{ -// pawL_check_argc(P, 1); -// const String *sep = v_string(CF_BASE(1)); -// String *s = v_string(CF_BASE(0)); -// if (sep->length == 0) { -// pawR_error(P, PAW_EVALUE, "empty separator"); -// } -// -// paw_Int npart = 0; -// const char *part; -// size_t nstr = s->length; -// const char *pstr = s->text; -// while ((part = find_substr(pstr, nstr, sep->text, sep->length))) { -// const size_t n = CAST_SIZE(part - pstr); -// pawC_pushns(P, pstr, n); -// part += sep->length; // skip separator -// pstr = part; -// nstr -= n; -// ++npart; -// } -// const char *end = s->text + s->length; // add the rest -// pawC_pushns(P, pstr, CAST_SIZE(end - pstr)); -// ++npart; -// -// pawR_literal_array(P, npart); -// return 1; -// } -// -// static int string_join(paw_Env *P) -//{ -// pawL_check_argc(P, 1); -// const Value seq = CF_BASE(1); -// String *s = v_string(CF_BASE(0)); -// -// Buffer buf; -// pawL_init_buffer(P, &buf); -// paw_Int itr = PAW_ITER_INIT; -// Vector *a = v_vector(seq); -// while (pawA_iter(a, &itr)) { -// const Value v = a->begin[itr]; -// // Add a chunk, followed by the separator if necessary. -// const String *chunk = v_string(v); -// pawL_add_nstring(P, &buf, chunk->text, chunk->length); -// if (CAST_SIZE(itr + 1) < pawA_length(a)) { -// pawL_add_nstring(P, &buf, s->text, s->length); -// } -// } -// pawL_push_result(P, &buf); -// return 1; -// } +// TODO: These should be methods, which take the context as the implicit first parameter +static int string_split(paw_Env *P) +{ + pawL_check_argc(P, 2); + const String *sep = v_string(CF_BASE(2)); + String *s = v_string(CF_BASE(1)); + if (sep->length == 0) { + pawR_error(P, PAW_EVALUE, "empty separator"); + } + + paw_Int npart = 0; + const char *part; + size_t nstr = s->length; + const char *pstr = s->text; + while ((part = find_substr(pstr, nstr, sep->text, sep->length))) { + const size_t n = CAST_SIZE(part - pstr); + pawC_pushns(P, pstr, n); + part += sep->length; // skip separator + pstr = part; + nstr -= n; + ++npart; + } + const char *end = s->text + s->length; // add the rest + pawC_pushns(P, pstr, CAST_SIZE(end - pstr)); + ++npart; + + pawR_literal_vector(P, npart); + return 1; + } + +static int string_join(paw_Env *P) +{ + pawL_check_argc(P, 2); + String *s = v_string(CF_BASE(1)); + const Value seq = CF_BASE(2); + + Buffer buf; + pawL_init_buffer(P, &buf); + paw_Int itr = PAW_ITER_INIT; + Vector *a = v_vector(seq); + while (pawV_vec_iter(a, &itr)) { + const Value v = a->begin[itr]; + // Add a chunk, followed by the separator if necessary. + const String *chunk = v_string(v); + pawL_add_nstring(P, &buf, chunk->text, chunk->length); + if (CAST_SIZE(itr + 1) < pawV_vec_length(a)) { + pawL_add_nstring(P, &buf, s->text, s->length); + } + } + pawL_push_result(P, &buf); + return 1; + } static int string_starts_with(paw_Env *P) { - pawL_check_argc(P, 1); - String *s = v_string(CF_BASE(0)); - const String *prefix = v_string(CF_BASE(1)); + pawL_check_argc(P, 2); + String *s = v_string(CF_BASE(1)); + const String *prefix = v_string(CF_BASE(2)); const size_t prelen = prefix->length; - const paw_Bool b = - s->length >= prelen && 0 == memcmp(prefix->text, s->text, prelen); + const paw_Bool b = s->length >= prelen && + 0 == memcmp(prefix->text, s->text, prelen); v_set_bool(P->top.p - 1, b); return 1; } static int string_ends_with(paw_Env *P) { - pawL_check_argc(P, 1); - String *s = v_string(CF_BASE(0)); - const String *suffix = v_string(CF_BASE(1)); + pawL_check_argc(P, 2); + String *s = v_string(CF_BASE(1)); + const String *suffix = v_string(CF_BASE(2)); const size_t suflen = suffix->length; paw_Bool b = PAW_FALSE; if (s->length >= suflen) { @@ -381,20 +231,17 @@ static int string_ends_with(paw_Env *P) return 1; } -static int string_clone(paw_Env *P) +static int int_to_string(paw_Env *P) { - // Leave the string receiver on top of the stack. The copy of the string - // into this function's receiver slot serves as the clone. Strings are - // immutable: and have copy-on-write sematics. - paw_unused(P); + pawR_to_string(P, PAW_TINT, NULL); return 1; } static int map_get(paw_Env *P) { - const Value key = CF_BASE(1 /*TODO*/ + 1); - Map *m = v_map(CF_BASE(0 /*TODO*/ + 1)); - const Value *pv = pawH_get(P, m, key); + Map *m = v_map(CF_BASE(1)); + const Value key = CF_BASE(2); + const Value *pv = pawH_get(m, key); if (pv != NULL) { // replace default value P->top.p[-1] = *pv; @@ -404,14 +251,16 @@ static int map_get(paw_Env *P) static int map_erase(paw_Env *P) { - Map *m = v_map(CF_BASE(0 /*TODO*/ + 1)); - pawH_remove(P, m, CF_BASE(1 /*TODO*/ + 1)); + pawL_check_argc(P, 2); + Map *m = v_map(CF_BASE(1)); + pawH_erase(m, CF_BASE(2)); return 0; } static int map_clone(paw_Env *P) { - Map *m = v_map(CF_BASE(0 /*TODO*/ + 1)); + pawL_check_argc(P, 1); + Map *m = v_map(CF_BASE(1)); Value *pv = pawC_push0(P); pawH_clone(P, pv, m); return 1; @@ -450,6 +299,11 @@ void pawL_init(paw_Env *P) add_builtin_func(P, "print", base_print); // fn print(string) // TODO: Replace with real methods + add_builtin_func(P, "_string_starts_with", string_starts_with); + add_builtin_func(P, "_string_ends_with", string_ends_with); + add_builtin_func(P, "_string_find", string_find); + add_builtin_func(P, "_string_split", string_split); + add_builtin_func(P, "_string_join", string_join); add_builtin_func(P, "_vector_push", vector_push); add_builtin_func(P, "_vector_pop", vector_pop); add_builtin_func(P, "_vector_insert", vector_insert); @@ -468,10 +322,9 @@ static paw_Bool load_cached(paw_Env *P, const char *name) paw_push_string(P, name); const Value key = P->top.p[-1]; - Value *pvalue = pawH_get(P, P->libs, key); - if (!pvalue) { - return PAW_FALSE; - } + Value *pvalue = pawH_get(P->libs, key); + if (pvalue == NULL) return PAW_FALSE; + // replace library name pawC_pushv(P, *pvalue); paw_shift(P, 1); diff --git a/src/map.c b/src/map.c index 4739258..67204c3 100644 --- a/src/map.c +++ b/src/map.c @@ -57,6 +57,7 @@ static void rehash_map(Map old_m, Map *m) const size_t count = m->length; m->length = 0; for (size_t i = 0; m->length < count; ++i) { + paw_assert(i < old_m.capacity); if (mm[i].state == MAP_ITEM_OCCUPIED) { add_item(m, ks[i], vs[i]); } @@ -141,7 +142,7 @@ paw_Bool pawH_equals(paw_Env *P, Map *lhs, Map *rhs) } MapCursor mc = {lhs, 0}; while (mc.index < lhs->capacity) { - Value *v = pawH_action(P, rhs, *h_cursor_key(&mc), MAP_ACTION_NONE); + Value *v = pawH_get(rhs, *h_cursor_key(&mc)); if (v == NULL || !items_equal(*h_cursor_value(&mc), *v)) { return PAW_FALSE; } @@ -156,7 +157,7 @@ void pawH_extend(paw_Env *P, Map *dst, Map *src) while (mc.index < src->capacity) { if (h_get_state(&mc) != MAP_ITEM_OCCUPIED) { const Value key = *h_cursor_key(&mc); - Value *value = pawH_action(P, dst, key, MAP_ACTION_CREATE); + Value *value = pawH_create(P, dst, key); *value = *h_cursor_value(&mc); } ++mc.index; diff --git a/src/map.h b/src/map.h index 5cee0d4..7ed76b8 100644 --- a/src/map.h +++ b/src/map.h @@ -86,83 +86,37 @@ static inline size_t pawH_length(const Map *m) return m->length; } -static inline Value *pawH_get_(Map *m, Value key) +static inline Value *pawH_get(Map *m, Value key) { - if (m->length == 0) { - return NULL; - } + if (m->length == 0) return NULL; MapCursor mc = h_cursor_lookup(m, key); - if (h_is_occupied(&mc)) { - return h_cursor_value(&mc); - } - return NULL; -} - - -typedef enum MapAction { - MAP_ACTION_NONE, - MAP_ACTION_CREATE, - MAP_ACTION_REMOVE, -} MapAction; - -static inline Value *pawH_action(paw_Env *P, Map *m, Value key, - MapAction action) -{ - if (action == MAP_ACTION_CREATE) { - return pawH_create(P, m, key); - } else if (m->length == 0) { - return NULL; - } - MapCursor mc = h_cursor_lookup(m, key); - if (!h_is_occupied(&mc)) { - return NULL; - } - if (action == MAP_ACTION_REMOVE) { - h_set_state(&mc, MAP_ITEM_ERASED); - --m->length; - - // Return the address of the slot to indicate success. - return h_cursor_key(&mc); - } - paw_assert(action == MAP_ACTION_NONE); + if (!h_is_occupied(&mc)) return NULL; return h_cursor_value(&mc); } -static inline paw_Bool pawH_contains(paw_Env *P, Map *m, Value key) +static inline void pawH_erase(Map *m, Value key) { - return pawH_action(P, m, key, MAP_ACTION_NONE) != NULL; + if (m->length == 0) return; + MapCursor mc = h_cursor_lookup(m, key); + if (!h_is_occupied(&mc)) return; + h_set_state(&mc, MAP_ITEM_ERASED); + --m->length; } static inline void pawH_insert(paw_Env *P, Map *m, Value key, Value value) { - Value *slot = pawH_action(P, m, key, MAP_ACTION_CREATE); - if (!slot) { - pawH_key_error(P, key, PAW_TSTRING); // TODO: key type - } - *slot = value; -} - -static inline void pawH_remove(paw_Env *P, Map *m, Value key) -{ - if (!pawH_action(P, m, key, MAP_ACTION_REMOVE)) { - pawH_key_error(P, key, PAW_TSTRING); // TODO: key type - } -} - -static inline Value *pawH_get(paw_Env *P, Map *m, Value key) -{ - return pawH_action(P, m, key, MAP_ACTION_NONE); + *pawH_create(P, m, key) = value; } -static inline void pawH_set(paw_Env *P, Map *m, Value key, Value value) +static inline paw_Bool pawH_contains(Map *m, Value key) { - *pawH_get(P, m, key) = value; + return pawH_get(m, key) != NULL; } -static inline paw_Bool pawH_iter(const Map *m, paw_Int *itr) +static inline paw_Bool pawH_iter(const Map *m, paw_Int *pi) { - for (++*itr; *itr < paw_cast_int(m->capacity); ++*itr) { - const MapMeta *mm = pawH_meta(m, *itr); + for (++*pi; *pi < paw_cast_int(m->capacity); ++*pi) { + const MapMeta *mm = pawH_meta(m, *pi); if (mm->state == MAP_ITEM_OCCUPIED) { return PAW_TRUE; } diff --git a/src/mem.c b/src/mem.c index ee1201d..1b97376 100644 --- a/src/mem.c +++ b/src/mem.c @@ -4,14 +4,10 @@ #include "mem.h" #include "alloc.h" #include "gc.h" -#include -#include -#include -#include -static void *first_try(paw_Env *P, void *ptr, size_t size0, size_t size) +static void *first_try(paw_Env *P, void *ptr, size_t size) { -//#if PAW_STRESS > 0 +#if PAW_STRESS > 0 if (size > 0 && !P->gc_noem) { // Fail on the first attempt to get more memory, but only if the // runtime is allowed to perform emergency collections. Otherwise, @@ -20,14 +16,14 @@ static void *first_try(paw_Env *P, void *ptr, size_t size0, size_t size) // not allowed. return NULL; } -//#endif - return pawZ_alloc(P, ptr, size0, size); +#endif + return pawZ_alloc(P, ptr, size); } -static void *try_again(paw_Env *P, void *ptr, size_t size0, size_t size) +static void *try_again(paw_Env *P, void *ptr, size_t size) { pawG_collect(P); // emergency collection - return pawZ_alloc(P, ptr, size0, size); + return pawZ_alloc(P, ptr, size); } static void *m_alloc(paw_Env *P, void *ptr, size_t size0, size_t size) @@ -35,13 +31,13 @@ static void *m_alloc(paw_Env *P, void *ptr, size_t size0, size_t size) if (size == 0) { if (ptr == NULL) return NULL; P->gc_bytes -= size0; // 'free' never fails - return pawZ_alloc(P, ptr, size0, 0); + return pawZ_alloc(P, ptr, 0); } // (re)allocate memory - void *ptr2 = first_try(P, ptr, size0, size); + void *ptr2 = first_try(P, ptr, size); if (ptr2 == NULL && !P->gc_noem) { // run an emergency collection and try again - ptr2 = try_again(P, ptr, size0, size); + ptr2 = try_again(P, ptr, size); } if (ptr2 != NULL) { P->gc_bytes += size - size0; diff --git a/src/parse.c b/src/parse.c index 3065220..104464a 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1336,11 +1336,17 @@ static const char kPrelude[] = "pub fn assert(cond: bool) \n" // TODO: Replace with methods - "pub fn _vector_push(vec: [T], v: T) \n" - "pub fn _vector_pop(vec: [T]) -> T \n" - "pub fn _vector_insert(vec: [T], i: int, v: T)\n" - "pub fn _vector_erase(vec: [T], i: int) -> T \n" - "pub fn _vector_clone(vec: [T]) -> [T] \n"; + "pub fn _int_to_string(self: int, base: int) -> str\n" + "pub fn _string_split(self: str, sep: str) -> [str] \n" + "pub fn _string_join(self: str, seq: [str]) -> str \n" + "pub fn _string_find(self: str, target: str) -> int \n" + "pub fn _string_starts_with(self: str, prefix: str) -> bool\n" + "pub fn _string_ends_with(self: str, suffix: str) -> bool \n" + "pub fn _vector_push(self: [T], v: T) \n" + "pub fn _vector_pop(self: [T]) -> T \n" + "pub fn _vector_insert(self: [T], i: int, v: T)\n" + "pub fn _vector_erase(self: [T], i: int) -> T \n" + "pub fn _vector_clone(self: [T]) -> [T] \n"; struct PreludeReader { size_t size; diff --git a/src/parse.h b/src/parse.h index 8bb1a9f..f66aa03 100644 --- a/src/parse.h +++ b/src/parse.h @@ -71,6 +71,7 @@ struct FuncState { int nproto; // number of nested functions int nlines; // number of source lines int pc; // number of instructions + int line; enum FuncKind kind; // type of function }; diff --git a/src/paw.c b/src/paw.c index af7fa74..57238dc 100644 --- a/src/paw.c +++ b/src/paw.c @@ -15,6 +15,7 @@ // clang-format off #define PROGRAM_OPTIONS \ OPT_STR(e, src, "execute program passed as string") \ + OPT_INT(H, log_heap, "log of default heap size in bytes") \ OPT_OPT(h, "display usage information") \ OPT_OPT(q, "suppress output") @@ -151,15 +152,17 @@ static void show_help(void) static void handle_error(paw_Env *P, int status) { if (status != PAW_OK) { - error(status, "%s", paw_string(P, -1)); + error(status, "%s\n", paw_string(P, -1)); } } #define CHUNKNAME "(chunk)" -static paw_Env *load_source(void) +static paw_Env *load_source(size_t heap_size) { - paw_Env *P = paw_open(NULL, NULL); + paw_Env *P = paw_open(&(struct paw_Options){ + .heap_size = heap_size, + }); if (P == NULL) { error(PAW_EMEMORY, "not enough memory\n"); } @@ -186,7 +189,7 @@ static void setup_stack(paw_Env *P, int argc, const char **argv) paw_push_string(P, "main"); const int pid = paw_find_public(P); if (pid < 0) { - error(PAW_ERUNTIME, "unable to find entrypoint (public 'main' function)"); + error(PAW_ERUNTIME, "unable to find entrypoint (public 'main' function)\n"); } paw_push_public(P, pid); @@ -208,7 +211,9 @@ int main(int argc, const char **argv) show_help(); return 0; } - paw_Env *P = load_source(); + paw_Env *P = load_source(s_opt.H + ? 1 << s_opt.H + : 0 /* use default */); setup_stack(P, argc, argv); call_main(P, argc); diff --git a/src/paw.h b/src/paw.h index decc349..533234c 100644 --- a/src/paw.h +++ b/src/paw.h @@ -31,7 +31,13 @@ typedef void *(*paw_Alloc)(void *ud, void *ptr, size_t size0, size_t size); typedef const char *(*paw_Reader)(paw_Env *P, void *ud, size_t *size); typedef int (*paw_Function)(paw_Env *P); -paw_Env *paw_open(paw_Alloc alloc, void *ud); +struct paw_Options { + paw_Alloc alloc; + size_t heap_size; + void *heap; + void *ud; +}; +paw_Env *paw_open(const struct paw_Options *o); void paw_close(paw_Env *P); paw_Alloc paw_get_allocator(paw_Env *P); diff --git a/src/resolve.c b/src/resolve.c index e2d15a5..db7c2c0 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -2,9 +2,10 @@ // This source code is licensed under the MIT License, which can be found in // LICENSE.md. See AUTHORS.md for a list of contributor names. // -// check.c: Implementation of the type checker. This code transforms an AST -// from the parser into a graph by unifying types based on lexical scope. +// resolve.c: Implementation of the type checker. This code transforms an AST +// from the parser into a type-annotated HIR tree. +#include "resolve.h" #include "ast.h" #include "code.h" #include "compile.h" @@ -15,14 +16,13 @@ #include "map.h" #include "mem.h" #include "parse.h" -#include "resolve.h" #include "str.h" #include "type.h" #include "unify.h" -#define NAME_ERROR(R, line, ...) pawE_error(ENV(R), PAW_ENAME, line, __VA_ARGS__) -#define SYNTAX_ERROR(R, ...) pawE_error(ENV(R), PAW_ESYNTAX, -1, __VA_ARGS__) -#define TYPE_ERROR(R, ...) pawE_error(ENV(R), PAW_ETYPE, -1, __VA_ARGS__) +#define NAME_ERROR(R, ...) pawE_error(ENV(R), PAW_ENAME, (R)->line, __VA_ARGS__) +#define SYNTAX_ERROR(R, ...) pawE_error(ENV(R), PAW_ESYNTAX, (R)->line, __VA_ARGS__) +#define TYPE_ERROR(R, ...) pawE_error(ENV(R), PAW_ETYPE, (R)->line, __VA_ARGS__) #define CACHED_STR(R, i) pawE_cstr(ENV(R), CAST_SIZE(i)) #define TYPE2CODE(R, type) (pawP_type2code((R)->C, type)) @@ -315,8 +315,7 @@ static struct HirSymbol *new_global(struct Resolver *R, String *name, struct Hir for (int i = 0; i < st->symbols->count; ++i) { struct HirSymbol *symbol = st->symbols->data[i]; if (pawS_eq(symbol->name, name)) { - NAME_ERROR(R, decl->hdr.line, - "duplicate global '%s' (declared previously on line %d)", + NAME_ERROR(R, "duplicate global '%s' (declared previously on line %d)", name->text, symbol->decl->hdr.line); } } @@ -335,16 +334,6 @@ static struct HirSymbol *try_resolve_symbol(struct Resolver *R, const String *na const int index = pawHir_find_symbol(scope, name); if (index >= 0) { struct HirSymbol *symbol = scope->symbols->data[index]; - if (scope->fn_depth != R->func_depth && - !HirIsFuncDecl(symbol->decl) && - !symbol->is_type) { - // TODO: note that this is not currently possible: named functions (non-closures) can only - // appear at the toplevel, anything they reference outside themselves is global - // TODO: replace is_type with more specific flag, this will mess - // up function templates! - TYPE_ERROR(R, "attempt to reference non-local variable '%s' " - "(consider using a closure)", name->text); - } return scope->symbols->data[index]; } } @@ -360,7 +349,7 @@ static struct HirSymbol *resolve_symbol(struct Resolver *R, const String *name) { struct HirSymbol *symbol = try_resolve_symbol(R, name); if (symbol == NULL) { - NAME_ERROR(R, -1, "undefined symbol '%s'", name->text); + NAME_ERROR(R, "undefined symbol '%s'", name->text); } return symbol; } @@ -692,7 +681,6 @@ static void instantiate_func_aux(struct Resolver *R, struct HirFuncDecl *base, s r->fdef.types = types; inst->type = r; - // NOTE: '.scope' not used for functions struct HirTypeList *generics = HirGetFuncDef(base->type)->types; prep_func_instance(R, generics, types, inst); @@ -777,9 +765,7 @@ static struct HirDecl *instantiate_struct(struct Resolver *R, struct HirAdtDecl static struct HirDecl *instantiate_func(struct Resolver *R, struct HirFuncDecl *base, struct HirTypeList *types) { check_template_param(R, base->generics, types); - if (types == NULL) { - return HIR_CAST_DECL(base); - } + if (types == NULL) return HIR_CAST_DECL(base); normalize_type_list(R, types); struct HirDecl *inst = find_func_instance(R, base, types); if (inst == NULL) { @@ -792,9 +778,8 @@ static struct HirDecl *instantiate_func(struct Resolver *R, struct HirFuncDecl * struct HirDecl *pawP_instantiate(struct Resolver *R, struct HirDecl *base, struct HirTypeList *types) { - if (types == NULL) { - // not polymorphic - } else if (HIR_IS_POLY_ADT(base)) { + if (types == NULL) return base; + if (HIR_IS_POLY_ADT(base)) { return instantiate_struct(R, &base->adt, types); } else if (HIR_IS_POLY_FUNC(base)) { return instantiate_func(R, &base->func, types); @@ -968,7 +953,7 @@ static struct HirDecl *expect_attr(struct Resolver *R, const struct StructPack * { struct HirDecl *attr = resolve_attr(pack->fields, name); if (attr == NULL) { - NAME_ERROR(R, -1, "field '%s' does not exist in type '%s'", name->text, + NAME_ERROR(R, "field '%s' does not exist in type '%s'", name->text, pack->name->text); } return attr; @@ -1023,9 +1008,7 @@ static struct HirType *typeof_path(struct HirPath *path) static struct HirType *resolve_tuple_type(struct Resolver *R, struct AstTupleType *e) { - if (e->types->count == 0) { - return get_type(R, PAW_TUNIT); - } + if (e->types->count == 0) return get_type(R, PAW_TUNIT); struct HirType *r = new_type(R, NO_DECL, kHirTupleType, e->line); r->tuple.elems = resolve_type_list(R, e->types); return r; @@ -1150,10 +1133,8 @@ static void op_type_error(struct Resolver *R, const struct HirType *type, const static struct HirType *binop_vector(struct Resolver *R, BinaryOp op, struct HirType *type) { const struct HirType *elem_t = hir_vector_elem(type); - if (op == BINARY_ADD) { - // 2 vectors with the same element type can be added - return type; - } else if (!HIR_IS_BASIC_T(elem_t)) { + if (op == BINARY_ADD) return type; + if (!HIR_IS_BASIC_T(elem_t)) { op_type_error(R, elem_t, "element"); } return get_type(R, PAW_TBOOL); @@ -1604,11 +1585,11 @@ static struct HirType *resolve_composite_lit(struct Resolver *R, struct AstCompo struct HirExpr *hir_item = resolve_expr(R, ast_item); struct HirStructField *item = HirGetStructField(hir_item); v_set_object(&key, item->name); - if (pawH_contains(P, map, key)) { - NAME_ERROR(R, hir_item->hdr.line, "duplicate field '%s' in initializer for struct '%s'", + if (pawH_contains(map, key)) { + NAME_ERROR(R, "duplicate field '%s' in initializer for struct '%s'", item->name->text, pack.name->text); } - Value *value = pawH_action(P, map, key, MAP_ACTION_CREATE); + Value *value = pawH_create(P, map, key); v_set_int(value, i); pawHir_expr_list_push(R->hir, order, hir_item); } @@ -1616,9 +1597,9 @@ static struct HirType *resolve_composite_lit(struct Resolver *R, struct AstCompo struct HirDecl *decl = pack.fields->data[i]; struct HirFieldDecl *field = HirGetFieldDecl(decl); v_set_object(&key, field->name); - Value *value = pawH_get(P, map, key); + Value *value = pawH_get(map, key); if (value == NULL) { - NAME_ERROR(R, field->line, "missing initializer for field '%s' in struct '%s'", + NAME_ERROR(R, "missing initializer for field '%s' in struct '%s'", field->name->text, pack.name->text); } const paw_Int index = v_int(*value); @@ -1629,12 +1610,12 @@ static struct HirType *resolve_composite_lit(struct Resolver *R, struct AstCompo struct HirType *item_t = expr_type(R, item); item->sitem.index = i; unify(R, item_t, field_t); - pawH_remove(P, map, key); + pawH_erase(map, key); } paw_Int iter = PAW_ITER_INIT; while (pawH_iter(map, &iter)) { const Value *pkey = pawH_key(map, CAST_SIZE(iter)); - NAME_ERROR(R, -1, "unexpected field '%s' in initializer for struct '%s'", + NAME_ERROR(R, "unexpected field '%s' in initializer for struct '%s'", v_string(*pkey), pack.name->text); } paw_assert(pack.fields->count == e->items->count); @@ -1819,7 +1800,7 @@ static struct HirExpr *resolve_index(struct Resolver *R, struct AstIndex *e) expect = get_type(R, PAW_TINT); r->type = get_type(R, PAW_TSTRING); } else { - not_container: +not_container: TYPE_ERROR(R, "type cannot be indexed (not a container)"); } if (e->is_slice) { @@ -1869,7 +1850,7 @@ static struct HirExpr *resolve_selector(struct Resolver *R, struct AstSelector * TYPE_ERROR(R, "expected name of struct field (integer indices can " "only be used with tuples)"); } else if (pack.fields == NULL) { - NAME_ERROR(R, e->line, "unit structure '%s' has no fields", pack.name->text); + NAME_ERROR(R, "unit structure '%s' has no fields", pack.name->text); } struct HirDecl *attr = expect_attr(R, &pack, e->name); r->type = HIR_TYPEOF(attr); @@ -1907,6 +1888,7 @@ static struct HirDecl *ResolveGenericDecl(struct Resolver *R, struct AstGenericD static struct HirDecl *resolve_decl(struct Resolver *R, struct AstDecl *decl) { + R->line = decl->hdr.line; switch (AST_KINDOF(decl)) { #define DEFINE_CASE(a, b) \ case kAst##a: \ @@ -1918,6 +1900,7 @@ static struct HirDecl *resolve_decl(struct Resolver *R, struct AstDecl *decl) static struct HirStmt *resolve_stmt(struct Resolver *R, struct AstStmt *stmt) { + R->line = stmt->hdr.line; switch (AST_KINDOF(stmt)) { #define DEFINE_CASE(a, b) \ case kAst##a: \ @@ -1934,6 +1917,7 @@ static struct HirStmt *resolve_stmt(struct Resolver *R, struct AstStmt *stmt) static struct HirType *resolve_type(struct Resolver *R, struct AstExpr *expr) { struct HirType *r; + R->line = expr->hdr.line; switch (AST_KINDOF(expr)) { case kAstPathExpr: r = resolve_path_type(R, AstGetPathExpr(expr)); @@ -1953,6 +1937,7 @@ static struct HirType *resolve_type(struct Resolver *R, struct AstExpr *expr) static struct HirExpr *resolve_expr(struct Resolver *R, struct AstExpr *expr) { struct HirExpr *r; + R->line = expr->hdr.line; switch (AST_KINDOF(expr)) { case kAstLiteralExpr: r = resolve_literal_expr(R, AstGetLiteralExpr(expr)); diff --git a/src/resolve.h b/src/resolve.h index 173f8a9..a2095b7 100644 --- a/src/resolve.h +++ b/src/resolve.h @@ -21,6 +21,7 @@ struct Resolver { int nresults; int vector_gid; int map_gid; + int line; paw_Bool in_closure; // 1 if the enclosing function is a closure, else 0 }; diff --git a/src/rt.c b/src/rt.c index 3b3824e..c7d2f81 100644 --- a/src/rt.c +++ b/src/rt.c @@ -60,7 +60,7 @@ static void add_zeros(paw_Env *P, int n) static int current_line(const CallFrame *cf) { Proto *p = cf->fn->p; - const int pc = CAST(cf->pc - p->source, int); + const int pc = CAST(cf->pc - p->source - 1, int); int i = 0; for (; i < p->nlines - 1; ++i) { @@ -695,7 +695,7 @@ static void other_binop(paw_Env *P, BinaryOp binop, paw_Type t, Value x, v_set_bool(pv, pawV_vec_contains(P, v_vector(y), x)); } else { paw_assert(t == PAW_TMAP); - v_set_bool(pv, pawH_contains(P, v_map(y), x)); + v_set_bool(pv, pawH_contains(v_map(y), x)); } vm_pop(1); } else if (t == PAW_TSTRING) { @@ -803,7 +803,7 @@ static void getitem_vector(paw_Env *P, Vector *vector, paw_Int index) static int getitem_map(paw_Env *P, Map *map, Value key) { - const Value *pv = pawH_get(P, map, key); + const Value *pv = pawH_get(map, key); if (pv) { *vm_top(2) = *pv; vm_pop(1); diff --git a/src/util.h b/src/util.h index 6f8cdaa..6f89025 100644 --- a/src/util.h +++ b/src/util.h @@ -7,8 +7,8 @@ #include "paw.h" #include #include -#include #include +#include #define paw_assert assert @@ -19,11 +19,15 @@ #define PAW_MIN(x, y) ((x) < (y) ? (x) : (y)) #define PAW_MAX(x, y) ((x) > (y) ? (x) : (y)) #define PAW_CLAMP(v, x, y) PAW_MIN(PAW_MAX(v, x), y) +#define PAW_ROUND_UP(n) ((n) + (-(n) & (PAW_ALIGN - 1))) +#define PAW_IS_ALIGNED(p) (!(CAST_UPTR(p) & (PAW_ALIGN - 1))) #define CHECK_EXP(c, e) (paw_assert(c), e) #define CAST(x, t) ((t)(x)) #define CAST_SIZE(x) CAST(x, size_t) #define CAST_UPTR(x) CAST(x, uintptr_t) +#define ERASE_TYPE(p) CAST(p, void *) +#define BUMP_PTR(p, n) ERASE_TYPE(CAST_UPTR(p) + (n)) // Check for inclusion in one of the character classes #define ISDIGIT(c) (kCharClassTable[(uint8_t)(c)] & 1) diff --git a/src/value.c b/src/value.c index 9c28548..50fad20 100644 --- a/src/value.c +++ b/src/value.c @@ -24,9 +24,11 @@ static void int_to_string(paw_Env *P, paw_Int i) // Don't call llabs(INT64_MIN). The result is undefined on 2s complement // systems. - uint64_t u = i == INT64_MIN ? (1ULL << 63) : (uint64_t)llabs(i); + uint64_t u = i == INT64_MIN + ? UINT64_C(1) << 63 + : CAST(llabs(i), uint64_t); do { - *ptr-- = (char)(u % 10 + '0'); + *ptr-- = CAST(u % 10 + '0', char); u /= 10; } while (u); if (negative) { @@ -44,7 +46,7 @@ static void float_to_string(paw_Env *P, paw_Float f) pawC_pushns(P, temp, CAST_SIZE(n)); } -const char *pawV_to_string(paw_Env *P, Value v, paw_Type type, size_t *nout) +const char *pawV_to_string(paw_Env *P, Value v, paw_Type type, size_t *plength) { switch (type) { case PAW_TSTRING: @@ -64,7 +66,7 @@ const char *pawV_to_string(paw_Env *P, Value v, paw_Type type, size_t *nout) return NULL; } const String *s = v_string(P->top.p[-1]); - *nout = s->length; + if (plength != NULL) *plength = s->length; return s->text; } @@ -408,17 +410,13 @@ static int char2base(char c) } } -static int check_suffix(const char *p, const char *text) +static int check_suffix(const char *p, const char *base) { - if (*p && !ISSPACE(*p)) { - return -1; - } + if (*p != '\0' && !ISSPACE(*p)) return -1; // If one of the pawV_parse_* functions are called on a string like " ", // then all of the checks will pass, despite " " not being a valid number. // Make sure that doesn't happen. - if (p == text) { - return -1; - } + if (p == base) return -1; return 0; } @@ -458,30 +456,28 @@ int pawV_parse_uint64(paw_Env *P, const char *text) return PAW_OK; } -#define skip_digits(p) \ +#define SKIP_DIGITS(p) \ while (ISDIGIT(*(p))) { \ - ++(p); \ + ++(p); \ } int pawV_parse_float(paw_Env *P, const char *text) { // First, validate the number format. const char *p = text; - if (p[0] == '0' && p[1] && !is_fp(p[1])) { + if (p[0] == '0' && p[1] != '\0' && !is_fp(p[1])) { return PAW_ESYNTAX; } - skip_digits(p); + SKIP_DIGITS(p) if (*p == '.') { ++p; - skip_digits(p); + SKIP_DIGITS(p) } if (*p == 'e' || *p == 'E') { - ++p; - if (*p == '+' || *p == '-') { - ++p; - } - skip_digits(p); + p += 1 + (p[1] == '+' || p[1] == '-'); + if (!ISDIGIT(*p)) return PAW_ESYNTAX; + SKIP_DIGITS(p) } if (check_suffix(p, text)) { return PAW_ESYNTAX; diff --git a/src/value.h b/src/value.h index a5321cb..f78c8c5 100644 --- a/src/value.h +++ b/src/value.h @@ -331,7 +331,7 @@ typedef enum MapState { } MapState; typedef struct MapMeta { - MapState state : 2; + uint8_t state : 2; } MapMeta; typedef struct Map { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2f945b0..6c3b915 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -30,5 +30,6 @@ message("TODO: fix test_api and test_limits") #build_test(test_limits) test_script(bubble "500") +test_script(matmul "1500") #test_script(binary_trees "5") test_script(mandelbrot "-2.1,0.9,80,-1.3,1.3,24") diff --git a/test/scripts/class.paw b/test/scripts/class.paw deleted file mode 100644 index 35deee6..0000000 --- a/test/scripts/class.paw +++ /dev/null @@ -1,423 +0,0 @@ --- class.paw - -{ - class A {} - let a = A() - - -- Functions added after __init are not bound to the instance. They - -- can be called and assigned to variables, but they will not receive - -- the hidden 'self' parameter, nor will they have access to 'super'. - a.a = fn() {} - a.b = fn(a) {return a} - assert(a.a() == null) - assert(a.b(42) == 42) - - let aa = a.a - let ab = a.b - assert(aa() == null) - assert(ab(42) == 42) -} - -{ - fn test(code) { - fn test() { - load(code)() - } - assert(0 != try(test)) - } - -- Classes are closed after creation (after closing '}' is encountered - -- in class the definition). - test('class A {}; A.a = 123') - test('class A {}; A.a = fn() {}') - - -- Non-bound functions cannot access 'self' or 'super'. - test('class A {}; let a = A(); a.a = fn() {self.value = 123}; a.a()') - test('class A {}; let a = A(); a.a = fn() {super.value = 123}; a.a()') -} - -{ - fn test(ins) { - -- 'c' and 'd' do not exist. Attempting to access either is an error. - assert(ins.a == 1) - assert(ins.b == null) - - ins.method() - assert(ins.a == 1) - assert(ins.b == 2) - assert(ins.c == 3) - } - - class Class { - __init() { - self.a = 1 - self.b = null - } - - method() { - self.b = 2 - self.c = 3 - } - } - - test(Class()) - - let cls = Class - test(cls()) -} - -{ - let cls - let a = [0] - { - let b = [0] - { - let c = [0] - - class Capture { - __init() { - self.a = a - self.b = b - self.c = c - } - } - cls = Capture - c[0] = 3 - } - b[0] = 2 - } - a[0] = 1 - - let ins = cls() - assert(ins.a[0] == 1) - assert(ins.b[0] == 2) - assert(ins.c[0] == 3) -} - -{ - let get - let set - let ins - let num - - fn setup(cls, n) { - num = n - ins = cls() - ins.setter(n) - assert(ins.getter() == n) - - get = ins.getter - set = ins.setter - assert(get() == n) - } - - fn test() { - assert(get() == num) - set(123) - assert(get() == 123) - } - - { - { - class Class { - setter(x) { - self.x = x - } - getter() { - return self.x - } - } - -- NOTE: Default __init() returns an instance of the class - setup(Class, 42) - assert(ins.x == 42) - } - test() - } - - { - { - let x = 0 - class Class { - setter(y) { - x = y - } - getter() { - return x - } - } - setup(Class, 321) - assert(x == 321) - } - test() - } -} - -{ - class Class { - __init() { - self.cls = Class - self.num = 42 - } - test() { - self.ins = self.cls() - } - } - let ins = Class() - ins.test() - - assert(ins.num == 42) - assert(ins.ins.num == 42) -} - -{ - class Class { - count(n) { - assert(n >= 0) - return n == 0 ?? 0 :: 1 + self.count(n - 1) - } - } - let c = Class() - assert(c.count(10) == 10) - assert(c.count(20) == 20) - assert(c.count(30) == 30) -} - --- Test inheritance: -{ - class Parent {} - class Child: Parent {} - let c = Child() - - let n = 0 - class Parent {method() {n = n + 1}} - class Child: Parent {} - let c = Child() - let m = c.method - m() -- Call bound method - c.method() -- Invoke directly - assert(n == 2) - - let n = 0 - class Grandparent {method() {n = -1}} - class Parent {method() {n = n + 1}} - class Child: Parent {} - let c = Child() - let m = c.method - -- Calls Parent.method() - m() - c.method() - assert(n == 2) -} - -{ - class Parent { - __init() { - self.name = 'Parent' - } - } - class Child: Parent {} - let c = Child() - -- __init method is not inherited - assert('not found' == getattr(c, '__init', 'not found')) -} - --- Inheritance from self is not possible -{ - class A {f() {}} - class A: A {} -- superclass is 'A' from the previous line - let a = A() - a.f() -} - --- Test 'super': -{ - - class Parent { - __init() { - self.name = 'Parent' - } - } - class Child: Parent { - __init() { - super.__init() - } - } - let c = Child() - assert(c.name == 'Parent') -} - -{ - class A { - method() { - return 'A' - } - } - - class B: A { - method() { - return 'B' - } - - test() { - return super.method() - } - } - - class C: B {} - - assert('A' == C().test()) -} - -{ - class A { - __init() { - self.v = 'A' - } - } - class B: A { - __init() {} - } - let b = B() - assert(null == getattr(b, 'v', null)) - - class B: A { - __init(x) { - super.__init() - } - } - let b = B('unused') - assert('A' == getattr(b, 'v', null)) -} - -{ - fn test(code) { - load(code)() - } - assert(0 != try(test, 'class _ABC: _ABC {}')) -- '_ABC' not declared (declared/defined at '}') - assert(0 != try(test, 'class A: 1 {}')) -- inherit from primitive - assert(0 != try(test, 'class A: [] {}')) -- inherit from builtin type - - -- In the second statement, the superclass 'A' refers to the class defined - -- in the first statement (class name is 'registered' at the '{'). - class A {} - class A: A {} -} - -{ - class Slice { - __init() { - self.value = [1, 2, 3] - } - __getslice(begin, end) { - return self.value[begin:end] - } - __setslice(begin, end, seq) { - self.value[begin:end] = seq - } - } - let slice = Slice() - assert(slice[:] == [1, 2, 3]) - assert(slice[1:-1] == [2]) - slice[3:] = [4, 5, 6] - assert(slice.value == [1, 2, 3, 4, 5, 6]) -} - -{ - class A { - __init(n) { - self.n = n - } - __null() { - return self.n < 0 ?? null :: self - } - } - - { - fn test(n) { - let r = A(n)? - return r.n - } - assert(test(-2).n == -2) - assert(test(-1).n == -1) - assert(test(0) == 0) - assert(test(1) == 1) - } - - { - fn test(n) { - return A(n) ?: n - } - assert(test(-2) == -2) - assert(test(-1) == -1) - assert(test(0).n == 0) - assert(test(1).n == 1) - } -} - -{ - class A { - __init(n) { - self.n = n - } - __null() { - -- Return an integer, rather than 'self'. When a given instance - -- of A 'x' is not semantically null, the expression 'x?' evaluates - -- to 'x.n', with no return. The expression 'x :? 123' also evaluates - -- to 'x.n'. - return self.n < 0 ?? null :: self.n - } - } - - fn test(n) { - let r = A(n)? - -- r == A(n).n - return r - } - assert(test(-1).n == -1) - assert(test(0) == 0) - - fn test(n) { - return A(n) ?: 'rhs' - } - assert(test(-1) == 'rhs') - assert(test(0) == 0) - assert(test(1) == 1) -} - -{ - class Comparable { - __init(a) { - self.a = a - } - __eq(b) { - return self.a == int(b) - } - __lt(b) { - return self.a < int(b) - } - __le(b) { - return self.a <= int(b) - } - __gt(b) { - return self.a > int(b) - } - __ge(b) { - return self.a >= int(b) - } - __int() { - return self.a - } - } - let c1 = Comparable(1) - assert(0 < c1 && c1 < 2) - assert(0 <= c1 && c1 <= 2) - assert(1 <= c1 && c1 <= 1) - assert(c1 > 0 && 2 > c1) - assert(c1 >= 0 && 2 >= c1) - assert(c1 >= 1 && 1 >= c1) - - let c2 = Comparable(2) - assert(c1 < c2 && c2 > c1) - assert(c1 <= c2 && c2 >= c1) - assert(c1 <= c1 && c2 >= c2) -} - diff --git a/test/scripts/map.paw b/test/scripts/map.paw index 6e9d814..190a58a 100644 --- a/test/scripts/map.paw +++ b/test/scripts/map.paw @@ -1,6 +1,6 @@ // map.paw -{ +pub fn test_getters() { let map = [ 1: 'abc', 2: 'def', @@ -10,16 +10,20 @@ assert(map[2] == 'def') assert(map[3] == 'ghi') } -{ + +pub fn test_empty_maps_with_annotations() { let map: [bool: int] = [:] - let map: [float: string] = [:] - let map: [string: bool] = [:] + let map: [float: str] = [:] + let map: [str: bool] = [:] } -{ + +fn call(map: [int: int]) {} +fn call2(map: [int: [int: int]]) {} + +pub fn test_inference() { let map = [1: [2, 3]] // [int: [int]] let map = [1: [2: 3]] // [int: [int: int]] -} -{ + let map = [1: [2: 3], 4: [:], 5: [:]] let map = [1: [:], 2: [3: 4], 5: [:]] let map = [1: [:], 2: [:], 3: [4: 5]] @@ -32,8 +36,7 @@ let map: [int: [int]] = [1: [2, 3]] let map: [int: [int: int]] = [1: [2: 3]] -} -{ + let map = [:] map = [0: 0] @@ -45,13 +48,11 @@ map = [:] map = [0: 0] - fn test(map: [int: int]) {} let a = [:] - test(a) + call(a) - fn test(map: [int: [int: int]]) {} let a = [0: [:]] - test(a) + call2(a) let a = [:] let b = a diff --git a/test/scripts/matmul.paw b/test/scripts/matmul.paw new file mode 100644 index 0000000..5e902a3 --- /dev/null +++ b/test/scripts/matmul.paw @@ -0,0 +1,46 @@ +// Translated from https://github.com/attractivechaos/plb2/blob/master/src/lua/matmul.lua + +fn matgen(n: int) -> [[float]] { + let tmp = 1.0 / + n as float / + n as float + let a = [] + for i = 0, n { + _vector_push(a, []) + for j = 0, n { + let x = tmp * + (i - j) as float * + (i + j - 2) as float + _vector_push(a[i], x) + } + } + return a +} + +fn matmul(n: int, a: [[float]], b: [[float]]) -> [[float]] { + let c = [] + for i = 1, n { + let ci = [] + for j = 0, n { + _vector_push(ci, 0.0) + } + for k = 0, n { + let t = a[i][k] + let bk = b[k] + for j = 0, n { + ci[j] = ci[j] + t * bk[j] + } + } + _vector_push(c, ci) + } + return c +} + +pub fn main(args: [str]) -> float { + let n = 100 + let a = matgen(n) + let b = matgen(n) + let c = matmul(n, a, b) + let r = (c[n / 2][n / 2]) + return r +} diff --git a/test/scripts/nqueen.paw b/test/scripts/nqueen.paw new file mode 100644 index 0000000..fa3b40b --- /dev/null +++ b/test/scripts/nqueen.paw @@ -0,0 +1,45 @@ +// Translated from https://github.com/attractivechaos/plb2/blob/master/src/lua/nqueen.lua +fn nq_solve(n: int) -> int { + let m = 0 + let a = [] + let l = [] + let c = [] + let r = [] + let y0 = (1 << n) - 1 + for i = 0, n { + _vector_push(a, -1) + _vector_push(l, 0) + _vector_push(c, 0) + _vector_push(r, 0) + } + let k = 0 + while k >= 0 { + let y = ((l[k] | c[k]) | r[k]) & y0 + if ((y ^ y0) >> (a[k] + 1)) != 0 { + let i = a[k] + 1 + while i < n && (y & (1 << i)) != 0 { + i = i + 1 + } + if k < n - 1 { + let z = 1 << i + a[k] = i + k = k + 1 + l[k] = (l[k - 1] | z) << 1 + c[k] = c[k - 1] | z + r[k] = (r[k - 1] | z) >> 1 + } else { + m = m + 1 + k = k - 1 + } + } else { + a[k] = -1 + k = k - 1 + } + } + return m +} + +pub fn main() { + let n = 5 + let m = nq_solve(n) +} diff --git a/test/scripts/string.paw b/test/scripts/string.paw index 126b30d..900c879 100644 --- a/test/scripts/string.paw +++ b/test/scripts/string.paw @@ -1,90 +1,78 @@ // string.paw -{ - assert(!'') - assert('s') - assert(!!'s') - - assert('str'.clone() == 'str') +pub fn test_starts_with() { + let str = 'abcdef' + assert(_string_starts_with(str, 'abcdef')) + assert(_string_starts_with(str, 'abcde')) + assert(_string_starts_with(str, 'abc')) + assert(_string_starts_with(str, 'a')) + assert(_string_starts_with(str, '')) + assert(!_string_starts_with(str, 'bcdef')) + assert(!_string_starts_with(str, 'abcdf')) + assert(!_string_starts_with(str, 'abd')) + assert(!_string_starts_with(str, 'ac')) } -{ +pub fn test_ends_with() { let str = 'abcdef' - assert(str.starts_with('abcdef')) - assert(str.starts_with('abcde')) - assert(str.starts_with('abc')) - assert(str.starts_with('a')) - assert(str.starts_with('')) - assert(str.ends_with('abcdef')) - assert(str.ends_with('bcdef')) - assert(str.ends_with('def')) - assert(str.ends_with('f')) - assert(str.ends_with('')) - assert(!str.starts_with('bcdef')) - assert(!str.starts_with('abcdf')) - assert(!str.starts_with('abd')) - assert(!str.starts_with('ac')) - assert(!str.ends_with('abcde')) - assert(!str.ends_with('acdef')) - assert(!str.ends_with('cef')) - assert(!str.ends_with('df')) + assert(_string_ends_with(str, 'abcdef')) + assert(_string_ends_with(str, 'bcdef')) + assert(_string_ends_with(str, 'def')) + assert(_string_ends_with(str, 'f')) + assert(_string_ends_with(str, '')) + assert(!_string_ends_with(str, 'abcde')) + assert(!_string_ends_with(str, 'acdef')) + assert(!_string_ends_with(str, 'cef')) + assert(!_string_ends_with(str, 'df')) +} + +pub fn test_slices() { + let s = 'abc' + assert(s[:] == 'abc') + assert(s[0:#s] == 'abc') + + assert(s[:-1] == 'ab') + assert(s[:#s-1] == 'ab') + assert(s[:-2] == 'a') + assert(s[:#s-2] == 'a') + assert(s[:-3] == '') + assert(s[:#s-3] == '') + + assert(s[1:] == 'bc') + assert(s[-2:] == 'bc') + assert(s[2:] == 'c') + assert(s[-1:] == 'c') + assert(s[3:] == '') + assert(s[0:0] == '') +} + +pub fn test_find() { + let check = |s, sub, n| { + assert(n == _string_find(s, sub)) + } + check('abc', 'a', 0) + check('abc', 'b', 1) + check('abc', 'c', 2) + check('abc', 'd', -1) } -//{ -// let s = 'abc' -// assert(s[:] == 'abc') -// assert(s[:null] == 'abc') -// assert(s[null:] == 'abc') -// assert(s[null:null] == 'abc') -// assert(s[0:#s] == 'abc') -// -// assert(s[:-1] == 'ab') -// assert(s[:#s-1] == 'ab') -// assert(s[:-2] == 'a') -// assert(s[:#s-2] == 'a') -// assert(s[:-3] == '') -// assert(s[:#s-3] == '') -// -// assert(s[1:] == 'bc') -// assert(s[-2:] == 'bc') -// assert(s[2:] == 'c') -// assert(s[-1:] == 'c') -// assert(s[3:] == '') -// assert(s[0:0] == '') -// -// // clamped -// assert(s[4:] == '') -// assert(s[:-4] == '') -//} -// -//// String find: -//{ -// fn check(s: string, sub: string, n: int) { -// assert(n == s.find(sub)) -// } -// check('abc', 'a', 0) -// check('abc', 'b', 1) -// check('abc', 'c', 2) -// check('abc', 'd', -1) -//} -// -//// String split/join: -//{ -// fn check(s, sep, parts) { -// let a = s.split(sep) -// for i = 0, #a { -// assert(a[i] == parts[i]) -// } -// assert(#a == #parts) -// let result = sep.join(a) -// assert(result == s); -// } -// check('abc', 'a', ['', 'bc']) -// check('abc', 'b', ['a', 'c']) -// check('abc', 'c', ['ab', '']) -// check('abc', 'd', ['abc']) -// -// let a = ',a,,b,,,c,,,,d,,,,,e,,,,,,'.split(',') -// assert(''.join(a) == 'abcde') -//} +pub fn test_split_and_join() { + let check = |s, sep, parts: [str]| { + let a = _string_split(s, sep) + for i = 0, #a { + assert(a[i] == parts[i]) + } + assert(#a == #parts) + let result = _string_join(sep, a) + assert(result == s); + } + check('abc', 'a', ['', 'bc']) + check('abc', 'b', ['a', 'c']) + check('abc', 'c', ['ab', '']) + check('abc', 'd', ['abc']) + + let s = ',a,,b,,,c,,,,d,,,,,e,,,,,,' + let a = _string_split(s, ',') + assert(_string_join('', a) == 'abcde') +} diff --git a/test/test.c b/test/test.c index 77eae73..51cdc24 100644 --- a/test/test.c +++ b/test/test.c @@ -155,9 +155,13 @@ static const char *test_pathname(const char *name) return s_buf; } -paw_Env *test_open(paw_Alloc alloc, struct TestAlloc *state) +paw_Env *test_open(paw_Alloc alloc, struct TestAlloc *state, size_t heap_size) { - return paw_open(alloc ? alloc : test_alloc, state); + return paw_open(&(struct paw_Options){ + .heap_size = heap_size, + .alloc = alloc, + .ud = state, + }); } void test_close(paw_Env *P, struct TestAlloc *a) @@ -178,7 +182,6 @@ static void check_ok(paw_Env *P, int status) } } -#include "debug.h" int test_open_file(paw_Env *P, const char *name) { const char *pathname = test_pathname(name); @@ -217,7 +220,7 @@ void test_recover(paw_Env *P, paw_Bool fatal) void test_script(const char *name, struct TestAlloc *a) { - paw_Env *P = test_open(test_alloc, a); + paw_Env *P = test_open(test_alloc, a, 0); check_ok(P, test_open_file(P, name)); check_ok(P, paw_call(P, 0)); test_close(P, a); diff --git a/test/test.h b/test/test.h index 68ccb2b..8444eea 100644 --- a/test/test.h +++ b/test/test.h @@ -9,10 +9,12 @@ X(primitive) \ X(function) \ X(closure) \ + X(string) \ X(struct) \ X(tuple) \ X(enum) \ X(vector) \ + X(map) \ X(poly_function) \ X(poly_struct) \ X(poly_enum) @@ -47,7 +49,7 @@ struct TestReader { void *test_alloc(void *ud, void *ptr, size_t size0, size_t size); const char *test_reader(paw_Env *X, void *ud, size_t *size); -paw_Env *test_open(paw_Alloc alloc, struct TestAlloc *state); +paw_Env *test_open(paw_Alloc alloc, struct TestAlloc *state, size_t heap_size); void test_close(paw_Env *P, struct TestAlloc *a); int test_open_file(paw_Env *P, const char *pathname); int test_open_string(paw_Env *P, const char *source); diff --git a/test/test_alloc.c b/test/test_alloc.c index fe3c11e..86d4723 100644 --- a/test/test_alloc.c +++ b/test/test_alloc.c @@ -9,7 +9,7 @@ static void driver(void (*test_callback)(paw_Env *P)) { struct TestAlloc a = {0}; - paw_Env *P = test_open(test_alloc, &a); + paw_Env *P = test_open(test_alloc, &a, 1024 * 1024 * 8); test_callback(P); test_close(P, &a); } @@ -19,10 +19,20 @@ static void open_and_close(paw_Env *P) paw_unused(P); } +static void test_utils(void) +{ + for (size_t i = 1; i < PAW_ALIGN; ++i) { + check(PAW_ROUND_UP(i) == PAW_ALIGN); + } + check(PAW_ROUND_UP(PAW_ALIGN) == PAW_ALIGN); + check(PAW_ROUND_UP(PAW_ALIGN + 1) == 2 * PAW_ALIGN); + check(PAW_ROUND_UP(PAW_ALIGN * 2 + 1) == 3 * PAW_ALIGN); +} + static void alloc_and_free(paw_Env *P, size_t size) { - void *ptr = pawZ_alloc(P, NULL, 0, size); - pawZ_alloc(P, ptr, size, 0); + void *ptr = pawZ_alloc(P, NULL, size); + pawZ_alloc(P, ptr, 0); } #define MAX_DEFER 32768 @@ -48,7 +58,7 @@ static void check_defer_data(const void *ptr, size_t size) static void alloc_and_defer(paw_Env *P, size_t size) { check(s_ndefer < MAX_DEFER); - void *ptr = pawZ_alloc(P, NULL, 0, size); + void *ptr = pawZ_alloc(P, NULL, size); const int index = s_ndefer++; s_defer[index] = (struct DeferredAlloc){ .size = size, @@ -62,7 +72,7 @@ static void free_deferred_ptrs(paw_Env *P) while (s_ndefer > 0) { struct DeferredAlloc defer = s_defer[--s_ndefer]; check_defer_data(defer.ptr, defer.size); - pawZ_alloc(P, defer.ptr, defer.size, 0); + pawZ_alloc(P, defer.ptr, 0); } } @@ -105,9 +115,6 @@ static void test_lots_of_allocations(paw_Env *P) alloc_pattern(P, CAST_SIZE(rand() % 100 + 1)); } for (size_t i = 0; i < 100; ++i) { - if(i==18){ - - } alloc_pattern(P, CAST_SIZE(rand() % 10000 + 1)); } free_deferred_ptrs(P); @@ -115,7 +122,8 @@ static void test_lots_of_allocations(paw_Env *P) int main(void) { -// driver(open_and_close); -// driver(test_small_allocations); + test_utils(); + driver(open_and_close); + driver(test_small_allocations); driver(test_lots_of_allocations); } diff --git a/test/test_error.c b/test/test_error.c index 526445b..80138cc 100644 --- a/test/test_error.c +++ b/test/test_error.c @@ -28,7 +28,7 @@ static void test_compiler_error(int expect, const char *name, const char *item, char buffer[4096]; write_main(buffer, item, text); - paw_Env *P = paw_open(NULL, NULL); + paw_Env *P = paw_open(&(struct paw_Options){0}); int status = pawL_load_chunk(P, name, buffer); check(status == expect); @@ -40,7 +40,7 @@ static void test_runtime_error(int expect, const char *name, const char *item, c char buffer[4096]; write_main(buffer, item, text); - paw_Env *P = paw_open(NULL, NULL); + paw_Env *P = paw_open(&(struct paw_Options){0}); int status = pawL_load_chunk(P, name, buffer); check(status == PAW_OK); @@ -207,6 +207,15 @@ static void test_syntax_error(void) test_compiler_error(PAW_ESYNTAX, "nested_struct", "", "struct S {x: int}"); test_compiler_error(PAW_ESYNTAX, "nested_enum", "", "enum E {X}"); test_compiler_error(PAW_ESYNTAX, "toplevel_var", "let v = 1", ""); + test_compiler_error(PAW_ESYNTAX, "bad_float", "", "let f = -1.0-"); + test_compiler_error(PAW_ESYNTAX, "bad_float_2", "", "let f = 1-.0-"); + test_compiler_error(PAW_ESYNTAX, "bad_float_3", "", "let f = 1e--1"); + test_compiler_error(PAW_ESYNTAX, "bad_float_4", "", "let f = 1e++1"); + test_compiler_error(PAW_ESYNTAX, "bad_float_5", "", "let f = 1e-1.0"); + test_compiler_error(PAW_ESYNTAX, "bad_float_6", "", "let f = 1e+1.0"); + test_compiler_error(PAW_ESYNTAX, "bad_float_7", "", "let f = 1e-1e1"); + test_compiler_error(PAW_ESYNTAX, "bad_float_8", "", "let f = 1e+1e1"); + test_compiler_error(PAW_ESYNTAX, "bad_float_9", "", "let f = 1.0.0"); } static void test_arithmetic_error(void) @@ -253,7 +262,6 @@ static void test_map_error(void) int main(void) { - test_compiler_error(PAW_ESYNTAX, "struct_unit_with_braces_on_init", "struct A", "let a = A{}"); test_name_error(); test_syntax_error(); test_type_error(); diff --git a/test/test_impl.c b/test/test_impl.c index 8967582..6888595 100644 --- a/test/test_impl.c +++ b/test/test_impl.c @@ -70,18 +70,18 @@ static void map_free(paw_Env *P, Map *map) pawC_pop(P); } -static paw_Int map_get(paw_Env *P, Map *map, paw_Int k) +static paw_Int map_get(Map *map, paw_Int k) { const Value key = {.i = k}; - const Value *pvalue = pawH_get(P, map, key); + const Value *pvalue = pawH_get(map, key); paw_assert(pvalue != NULL); return pvalue->i; } -static const paw_Int *map_try(paw_Env *P, Map *map, paw_Int k) +static const paw_Int *map_try(Map *map, paw_Int k) { const Value key = {.i = k}; - const Value *pvalue = pawH_get(P, map, key); + const Value *pvalue = pawH_get(map, key); return pvalue ? &pvalue->i : NULL; } @@ -92,28 +92,10 @@ static void map_put(paw_Env *P, Map *map, paw_Int k, paw_Int v) pawH_insert(P, map, key, value); } -static void map_del(paw_Env *P, Map *map, paw_Int k) +static void map_del(Map *map, paw_Int k) { const Value key = {.i = k}; - pawH_remove(P, map, key); -} - -static void dump_map(Map *m) -{ - printf("Map{\n"); - for (size_t i = 0; i < m->capacity; ++i) { - const MapMeta *mm = pawH_meta(m, i); - printf(" %.4zu: ", i); - if (mm->state == MAP_ITEM_OCCUPIED) { - printf("%" PRId64 ": %" PRId64 "\n", - pawH_key(m, i)->i, pawH_value(m, i)->i); - } else if (mm->state == MAP_ITEM_ERASED) { - printf("\n"); - } else if (mm->state == MAP_ITEM_VACANT) { - printf("\n"); - } - } - printf("}\n"); + pawH_erase(map, key); } static void test_map1(paw_Env *P) @@ -122,9 +104,9 @@ static void test_map1(paw_Env *P) map_put(P, m, 1, 1); map_put(P, m, 2, 2); map_put(P, m, 3, 3); - check(1 == map_get(P, m, 1)); - check(2 == map_get(P, m, 2)); - check(3 == map_get(P, m, 3)); + check(1 == map_get(m, 1)); + check(2 == map_get(m, 2)); + check(3 == map_get(m, 3)); map_free(P, m); } @@ -138,17 +120,17 @@ static void test_map2(paw_Env *P) map_put(P, m, 5, 5); map_put(P, m, 6, 6); - map_del(P, m, 1); - map_del(P, m, 2); - map_del(P, m, 4); - map_del(P, m, 5); - - check(NULL == map_try(P, m, 1)); - check(NULL == map_try(P, m, 2)); - check(3 == map_get(P, m, 3)); - check(NULL == map_try(P, m, 4)); - check(NULL == map_try(P, m, 5)); - check(6 == map_get(P, m, 6)); + map_del(m, 1); + map_del(m, 2); + map_del(m, 4); + map_del(m, 5); + + check(NULL == map_try(m, 1)); + check(NULL == map_try(m, 2)); + check(3 == map_get(m, 3)); + check(NULL == map_try(m, 4)); + check(NULL == map_try(m, 5)); + check(6 == map_get(m, 6)); map_free(P, m); } @@ -165,11 +147,9 @@ static void test_map3(paw_Env *P) check(CAST_SIZE(paw_length(P, -1)) == paw_countof(known)); // Fill the map with nonnegative integers (may have repeats). - paw_Int integers[N]; for (int i = 0; i < N; ++i) { const paw_Int ival = test_randint(0, 10000); map_put(P, m, ival, ival); - integers[i] = ival; } check(CAST_SIZE(paw_length(P, -1)) <= N + paw_countof(known)); @@ -178,16 +158,14 @@ static void test_map3(paw_Env *P) paw_Int itr = PAW_ITER_INIT; while (pawH_iter(m, &itr)) { const Value key = *pawH_key(m, CAST_SIZE(itr)); - if (v_int(key) >= 0) { - map_del(P, m, key.i); - } + if (v_int(key) >= 0) map_del(m, key.i); } check(CAST_SIZE(pawH_length(m)) <= paw_countof(known)); // Check known items. for (size_t i = 0; i < paw_countof(known); ++i) { - const paw_Int value = map_get(P, m, known[i]); + const paw_Int value = map_get(m, known[i]); check(value == known[i]); } @@ -232,7 +210,10 @@ static void test_stack(paw_Env *P) static void driver(void (*callback)(paw_Env *)) { struct TestAlloc a = {0}; - paw_Env *P = paw_open(test_alloc, &a); + paw_Env *P = paw_open(&(struct paw_Options){ + .alloc = test_alloc, + .ud = &a, + }); callback(P); test_close(P, &a); } diff --git a/test/test_oom.c b/test/test_oom.c index 26b2d44..c98217e 100644 --- a/test/test_oom.c +++ b/test/test_oom.c @@ -2,7 +2,7 @@ #include "call.h" #include "env.h" -void *oom_alloc(void *ud, void *ptr, size_t size0, size_t size) +static void *oom_alloc(void *ud, void *ptr, size_t size0, size_t size) { struct TestAlloc *a = ud; if (a->extra + size0 < size) { @@ -13,7 +13,7 @@ void *oom_alloc(void *ud, void *ptr, size_t size0, size_t size) return test_alloc(ud, ptr, size0, size); } -void run_tests(paw_Env *P) +static int run_tests(paw_Env *P) { struct GlobalVec *gvec = &P->gv; for (int i = 0; i < gvec->size; ++i) { @@ -22,21 +22,30 @@ void run_tests(paw_Env *P) struct GlobalVar *gvar = &gvec->data[i]; if (gvar->name->length >= kLength && 0 == memcmp(gvar->name->text, kPrefix, kLength)) { - printf("running oom.%s...\n", gvar->name->text); pawC_pushv(P, gvar->value); - check(PAW_OK == paw_call(P, 0)); + const int status = paw_call(P, 0); + if (status != PAW_OK) return status; } } + return PAW_OK; } -static int script_aux(const char *name, struct TestAlloc *a) +static int script_aux(const char *name, paw_Alloc alloc, struct TestAlloc *a, size_t heap_size) { - paw_Env *P = paw_open(oom_alloc, a); + paw_Env *P = paw_open(&(struct paw_Options){ + .heap_size = heap_size, + .alloc = alloc, + .ud = a, + }); if (P == NULL) return PAW_EMEMORY; int status = test_open_file(P, name); if (status == PAW_OK) run_tests(P); - test_close(P, a); + if (a == NULL) { + paw_close(P); + } else { + test_close(P, a); + } return status; } @@ -44,20 +53,39 @@ static void script(const char *name) { struct TestAlloc a = {0}; size_t nextra = 10; + int count = 0; int rc; do { // Run the script, allowing twice the number of bytes to be allocated // each time. Eventually, it should be able to allocate enough memory // to complete. - rc = script_aux(name, &a); + rc = script_aux(name, oom_alloc, &a, 0); a.extra = nextra; nextra *= 2; + ++count; + } while (rc == PAW_EMEMORY); + check(rc == PAW_OK); + check(count > 0); + printf("simulated OOM count: %d\n", count); +} + +static void real_oom(const char *name) +{ + size_t heap_size = 1; + int count = 0; + int rc; + do { + rc = script_aux(name, NULL, NULL, heap_size); + heap_size *= 2; + ++count; } while (rc == PAW_EMEMORY); check(rc == PAW_OK); + check(count > 0); + printf("actual OOM count: %d\n", count); } int main(void) { -#define RUN_SCRIPT(name) script(#name); +#define RUN_SCRIPT(name) real_oom(#name); script(#name); TEST_SCRIPTS(RUN_SCRIPT) } diff --git a/test/test_rt.c b/test/test_rt.c index 3a7c98c..b97fc99 100644 --- a/test/test_rt.c +++ b/test/test_rt.c @@ -8,7 +8,7 @@ void run_tests(const char *name, struct TestAlloc *a) { - paw_Env *P = test_open(test_alloc, a); + paw_Env *P = test_open(test_alloc, a, 0); check(PAW_OK == test_open_file(P, name)); struct GlobalVec *gvec = &P->gv; diff --git a/test/test_so.c b/test/test_so.c index 148eeac..1465b35 100644 --- a/test/test_so.c +++ b/test/test_so.c @@ -16,7 +16,7 @@ int main(void) " } \n" "} \n"; struct TestAlloc a = {0}; - paw_Env *P = test_open(NULL, &a); + paw_Env *P = test_open(NULL, &a, 1 << 20); int status = test_open_string(P, source); handle_error(P, status, PAW_TRUE);