diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f0d99a722..8f378e75e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -265,7 +265,7 @@ jobs: - name: Compile and run some examples run: | - cd resources + cd resources ../build/c3c compile examples/base64.c3 ../build/c3c compile examples/binarydigits.c3 ../build/c3c compile examples/brainfk.c3 @@ -285,7 +285,7 @@ jobs: ../build/c3c compile examples/contextfree/dynscope.c3 ../build/c3c compile examples/contextfree/guess_number.c3 ../build/c3c compile examples/contextfree/multi.c3 - ../build/c3c compile examples/contextfree/cleanup.c3 + ../build/c3c compile examples/contextfree/cleanup.c3 ../build/c3c compile-run examples/hello_world_many.c3 ../build/c3c compile-run examples/time.c3 ../build/c3c compile-run examples/fannkuch-redux.c3 diff --git a/lib/std/core/allocators/arena_allocator.c3 b/lib/std/core/allocators/arena_allocator.c3 index dfd1b7516..49ee2c063 100644 --- a/lib/std/core/allocators/arena_allocator.c3 +++ b/lib/std/core/allocators/arena_allocator.c3 @@ -30,9 +30,11 @@ struct ArenaAllocatorHeader @local char[*] data; } +/* + * @require ptr != null + **/ fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic { - if (!ptr) return; assert((uptr)ptr >= (uptr)self.data.ptr, "Pointer originates from a different allocator."); ArenaAllocatorHeader* header = ptr - ArenaAllocatorHeader.sizeof; // Reclaim memory if it's the last element. @@ -41,29 +43,26 @@ fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic self.used -= header.size + ArenaAllocatorHeader.sizeof; } } + fn usz ArenaAllocator.mark(&self) @dynamic => self.used; fn void ArenaAllocator.reset(&self, usz mark) @dynamic => self.used = mark; /** * @require !alignment || math::is_power_of_2(alignment) * @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big` - * @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big` - * @require offset <= size && offset >= 0 - * @require mem::aligned_offset(offset, ArenaAllocatorHeader.alignof) == offset + * @require size > 0 **/ fn void*! ArenaAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic { - if (!size) return null; alignment = alignment_for_allocation(alignment); usz total_len = self.data.len; if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE?; void* start_mem = self.data.ptr; - void* unaligned_pointer_to_offset = start_mem + self.used + ArenaAllocatorHeader.sizeof + offset; - void* aligned_pointer_to_offset = mem::aligned_pointer(unaligned_pointer_to_offset, alignment); - usz end = (usz)(aligned_pointer_to_offset - self.data.ptr) + size - offset; + void* unaligned_pointer_to_offset = start_mem + self.used + ArenaAllocatorHeader.sizeof; + void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment); + usz end = (usz)(mem - self.data.ptr) + size; if (end > total_len) return AllocationFailure.OUT_OF_MEMORY?; self.used = end; - void* mem = aligned_pointer_to_offset - offset; ArenaAllocatorHeader* header = mem - ArenaAllocatorHeader.sizeof; header.size = size; if (clear) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT); @@ -73,21 +72,11 @@ fn void*! ArenaAllocator.acquire(&self, usz size, bool clear, usz alignment, usz /** * @require !alignment || math::is_power_of_2(alignment) * @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big` - * @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big` - * @require offset <= size && offset >= 0 - * @require mem::aligned_offset(offset, ArenaAllocatorHeader.alignof) == offset + * @require old_pointer != null + * @require size > 0 **/ fn void*! ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignment, usz offset) @dynamic { - if (!size) - { - self.release(old_pointer, alignment > 0); - return null; - } - if (!old_pointer) - { - return self.acquire(size, true, alignment, offset); - } alignment = alignment_for_allocation(alignment); assert(old_pointer >= self.data.ptr, "Pointer originates from a different allocator."); usz total_len = self.data.len; @@ -95,7 +84,7 @@ fn void*! ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof; usz old_size = header.size; // Do last allocation and alignment match? - if (&self.data[self.used] == old_pointer + old_size && mem::ptr_is_aligned(old_pointer + offset, alignment)) + if (&self.data[self.used] == old_pointer + old_size && mem::ptr_is_aligned(old_pointer, alignment)) { if (old_size >= size) { @@ -111,7 +100,7 @@ fn void*! ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen return old_pointer; } // Otherwise just allocate new memory. - void* mem = self.acquire(size, false, alignment, offset)!; + void* mem = self.acquire(size, false, alignment, 0)!; mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT); return mem; } \ No newline at end of file diff --git a/lib/std/core/allocators/dynamic_arena.c3 b/lib/std/core/allocators/dynamic_arena.c3 index 4e36b7cc5..495cf6d76 100644 --- a/lib/std/core/allocators/dynamic_arena.c3 +++ b/lib/std/core/allocators/dynamic_arena.c3 @@ -59,11 +59,11 @@ struct DynamicArenaChunk @local } /** + * @require ptr * @require self.page `tried to free pointer on invalid allocator` */ fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic { - if (!ptr) return; DynamicArenaPage* current_page = self.page; if (ptr == current_page.current_stack_ptr) { @@ -73,19 +73,12 @@ fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic } /** + * @require size > 0 `Resize doesn't support zeroing` + * @require old_pointer != null `Resize doesn't handle null pointers` * @require self.page `tried to realloc pointer on invalid allocator` */ fn void*! DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @dynamic { - if (!size) - { - self.release(old_pointer, alignment > 0); - return null; - } - if (!old_pointer) - { - return self.acquire(size, true, alignment, offset); - } DynamicArenaPage* current_page = self.page; alignment = alignment_for_allocation(alignment); usz* old_size_ptr = old_pointer - DEFAULT_SIZE_PREFIX; @@ -109,7 +102,7 @@ fn void*! DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz a current_page.used += add_size; return old_pointer; } - void* new_mem = self.acquire(size, false, alignment, offset)!; + void* new_mem = self.acquire(size, false, alignment, 0)!; mem::copy(new_mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT); return new_mem; } @@ -135,10 +128,10 @@ fn void DynamicArenaAllocator.reset(&self, usz mark = 0) @dynamic * @require math::is_power_of_2(alignment) * @require size > 0 */ -fn void*! DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment, usz offset) @local +fn void*! DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment) @local { // First, make sure that we can align it, extending the page size if needed. - usz page_size = max(self.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof + offset, alignment) - offset); + usz page_size = max(self.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof, alignment)); // Grab the page without alignment (we do it ourselves) void* mem = allocator::malloc_try(self.backing_allocator, page_size)!; @@ -149,7 +142,7 @@ fn void*! DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment, usz o return err?; } page.memory = mem; - void* mem_start = mem::aligned_pointer(mem + offset + DynamicArenaChunk.sizeof, alignment) - offset; + void* mem_start = mem::aligned_pointer(mem + DynamicArenaChunk.sizeof, alignment); assert(mem_start + size < mem + page_size); DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem_start - 1; chunk.size = size; @@ -162,11 +155,11 @@ fn void*! DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment, usz o } /** + * @require size > 0 `acquire expects size > 0` * @require !alignment || math::is_power_of_2(alignment) */ fn void*! DynamicArenaAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic { - if (!size) return null; alignment = alignment_for_allocation(alignment); DynamicArenaPage* page = self.page; void* ptr = {| @@ -176,14 +169,14 @@ fn void*! DynamicArenaAllocator.acquire(&self, usz size, bool clear, usz alignme self.unused_page = page.prev_arena; page.prev_arena = null; } - if (!page) return self._alloc_new(size, alignment, offset); - void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset; + if (!page) return self._alloc_new(size, alignment); + void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment); usz new_used = start - page.memory + size; if ALLOCATE_NEW: (new_used > page.total) { if ((page = self.unused_page)) { - start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset; + start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment); new_used = start + size - page.memory; if (page.total >= new_used) { @@ -193,7 +186,7 @@ fn void*! DynamicArenaAllocator.acquire(&self, usz size, bool clear, usz alignme break ALLOCATE_NEW; } } - return self._alloc_new(size, alignment, offset); + return self._alloc_new(size, alignment); } page.used = new_used; assert(start + size == page.memory + page.used); diff --git a/lib/std/core/allocators/heap_allocator.c3 b/lib/std/core/allocators/heap_allocator.c3 index c02d3a705..f74d656d9 100644 --- a/lib/std/core/allocators/heap_allocator.c3 +++ b/lib/std/core/allocators/heap_allocator.c3 @@ -23,27 +23,17 @@ fn void SimpleHeapAllocator.init(&self, MemoryAllocFn allocator) fn void*! SimpleHeapAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic { - if (!size) return null; if (clear) { - return alignment > 0 ? @aligned_calloc(self._calloc, size, alignment, offset) : self._calloc(size); + return alignment > 0 ? @aligned_alloc(self._calloc, size, alignment) : self._calloc(size); } - return alignment > 0 ? @aligned_alloc(self._alloc, size, alignment, offset) : self._alloc(size); + return alignment > 0 ? @aligned_alloc(self._alloc, size, alignment) : self._alloc(size); } fn void*! SimpleHeapAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @dynamic { - if (!size) - { - self.release(old_pointer, alignment > 0); - return null; - } - if (!old_pointer) - { - return self.acquire(size, true, alignment, offset); - } return alignment > 0 - ? @aligned_realloc(self._calloc, self._free, old_pointer, size, alignment, offset) + ? @aligned_realloc(self._calloc, self._free, old_pointer, size, alignment) : self._realloc(old_pointer, size); } diff --git a/lib/std/core/allocators/libc_allocator.c3 b/lib/std/core/allocators/libc_allocator.c3 index 8f1ea142a..aafec93d9 100644 --- a/lib/std/core/allocators/libc_allocator.c3 +++ b/lib/std/core/allocators/libc_allocator.c3 @@ -6,22 +6,36 @@ module std::core::mem::allocator; import libc; const LibcAllocator LIBC_ALLOCATOR = {}; - - distinct LibcAllocator (Allocator) = uptr; +module std::core::mem::allocator @if(env::POSIX); +import std::os; +import libc; + fn void*! LibcAllocator.acquire(&self, usz bytes, bool clear, usz alignment, usz offset) @dynamic { - assert(alignment != 0 || offset == 0); if (clear) { - void* data = alignment ? @aligned_calloc(fn void*(usz bytes) => libc::calloc(bytes, 1), bytes, alignment, offset)!! : libc::calloc(bytes, 1); - return data ?: AllocationFailure.OUT_OF_MEMORY?; + void* data @noinit; + if (alignment > mem::DEFAULT_MEM_ALIGNMENT) + { + if (posix::posix_memalign(&data, alignment, bytes)) return AllocationFailure.OUT_OF_MEMORY?; + mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT); + return data; + } + return libc::calloc(1, bytes) ?: AllocationFailure.OUT_OF_MEMORY?; } else { - void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment, offset)!! : libc::malloc(bytes); - if (!data) return AllocationFailure.OUT_OF_MEMORY?; + void* data @noinit; + if (alignment > mem::DEFAULT_MEM_ALIGNMENT) + { + if (posix::posix_memalign(&data, alignment, bytes)) return AllocationFailure.OUT_OF_MEMORY?; + } + else + { + if (!(data = libc::malloc(bytes))) return AllocationFailure.OUT_OF_MEMORY?; + } $if env::TESTING: for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA; $endif @@ -31,7 +45,6 @@ fn void*! LibcAllocator.acquire(&self, usz bytes, bool clear, usz alignment, usz fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment, usz offset) @dynamic { - assert(alignment != 0 || offset == 0); if (!new_bytes) { self.release(old_ptr, alignment > 0); @@ -39,11 +52,101 @@ fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignmen } if (!old_ptr) { - return self.acquire(new_bytes, true, alignment, offset); + return self.acquire(new_bytes, false, alignment, 0); + } + if (alignment <= mem::DEFAULT_MEM_ALIGNMENT) return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?; + + void* new_ptr; + if (posix::posix_memalign(&new_ptr, alignment, new_bytes)) return AllocationFailure.OUT_OF_MEMORY?; + + $switch + $case env::DARWIN: + usz old_usable_size = darwin::malloc_size(old_ptr); + $case env::LINUX: + usz old_usable_size = linux::malloc_usable_size(old_ptr); + $default: + usz old_usable_size = new_bytes; + $endswitch + + usz copy_size = new_bytes < old_usable_size ? new_bytes : old_usable_size; + mem::copy(new_ptr, old_ptr, copy_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT); + libc::free(old_ptr); + return new_ptr; +} + +fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic +{ + libc::free(old_ptr); +} + +module std::core::mem::allocator @if(env::WIN32); +import std::os::win32; +import libc; + +fn void*! LibcAllocator.acquire(&self, usz bytes, bool clear, usz alignment, usz offset) @dynamic +{ + if (clear) + { + if (alignment > 0) + { + return win32::_aligned_recalloc(null, bytes, alignment) ?: AllocationFailure.OUT_OF_MEMORY?; + } + return libc::calloc(1, bytes) ?: AllocationFailure.OUT_OF_MEMORY?; + } + void* data = alignment > 0 ? win32::_aligned_malloc(bytes, alignment) : libc::malloc(bytes); + if (!data) return AllocationFailure.OUT_OF_MEMORY?; + $if env::TESTING: + for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA; + $endif + return data; +} + +fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment, usz offset) @dynamic +{ + if (alignment) + { + return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: AllocationFailure.OUT_OF_MEMORY?; + } + return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?; +} + +fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic +{ + if (aligned) + { + win32::_aligned_free(old_ptr); + return; + } + libc::free(old_ptr); +} + +module std::core::mem::allocator @if(!env::WIN32 && !env::POSIX); +import libc; + +fn void*! LibcAllocator.acquire(&self, usz bytes, bool clear, usz alignment, usz offset) @dynamic +{ + if (clear) + { + void* data = alignment ? @aligned_alloc(fn void*(usz bytes) => libc::calloc(bytes, 1), bytes, alignment)!! : libc::calloc(bytes, 1); + return data ?: AllocationFailure.OUT_OF_MEMORY?; } + else + { + void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment)!! : libc::malloc(bytes); + if (!data) return AllocationFailure.OUT_OF_MEMORY?; + $if env::TESTING: + for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA; + $endif + return data; + } +} + + +fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment, usz offset) @dynamic +{ if (alignment) { - void* data = @aligned_realloc(fn void*(usz bytes) => libc::calloc(bytes, 1), libc::free, old_ptr, new_bytes, alignment, offset)!!; + void* data = @aligned_realloc(fn void*(usz bytes) => libc::malloc(bytes), libc::free, old_ptr, new_bytes, alignment)!!; return data ?: AllocationFailure.OUT_OF_MEMORY?; } return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?; diff --git a/lib/std/core/allocators/on_stack_allocator.c3 b/lib/std/core/allocators/on_stack_allocator.c3 index 6d5b1610b..5595ec559 100644 --- a/lib/std/core/allocators/on_stack_allocator.c3 +++ b/lib/std/core/allocators/on_stack_allocator.c3 @@ -54,9 +54,11 @@ struct OnStackAllocatorHeader char[*] data; } +/** + * @require old_pointer + **/ fn void OnStackAllocator.release(&self, void* old_pointer, bool aligned) @dynamic { - if (!old_pointer) return; if (allocation_in_stack_mem(self, old_pointer)) return; on_stack_allocator_remove_chunk(self, old_pointer); self.release(old_pointer, aligned); @@ -98,44 +100,38 @@ fn OnStackAllocatorExtraChunk* on_stack_allocator_find_chunk(OnStackAllocator* a /** * @require size > 0 + * @require old_pointer != null * @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big` - * @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big` - * @require offset <= size && offset >= 0 - * @require mem::aligned_offset(offset, OnStackAllocatorExtraChunk.alignof) == offset **/ -fn void*! OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @dynamic +fn void*! OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz deprecated) @dynamic { if (!allocation_in_stack_mem(self, old_pointer)) { OnStackAllocatorExtraChunk* chunk = on_stack_allocator_find_chunk(self, old_pointer); assert(chunk, "Tried to realloc pointer not belonging to the allocator"); - return chunk.data = self.backing_allocator.resize(old_pointer, size, alignment, offset)!; + return chunk.data = self.backing_allocator.resize(old_pointer, size, alignment, 0)!; } OnStackAllocatorHeader* header = old_pointer - OnStackAllocatorHeader.sizeof; usz old_size = header.size; - void* mem = self.acquire(size, true, alignment, offset)!; + void* mem = self.acquire(size, false, alignment, 0)!; mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT); return mem; } /** * @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big` - * @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big` - * @require offset <= size && offset >= 0 - * @require offset == 0 || alignment > 0 - * @require mem::aligned_offset(offset, OnStackAllocatorHeader.alignof) == offset + * @require size > 0 **/ -fn void*! OnStackAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic +fn void*! OnStackAllocator.acquire(&self, usz size, bool clear, usz alignment, usz deprecated) @dynamic { - if (size == 0) return null; bool aligned = alignment > 0; alignment = alignment_for_allocation(alignment); usz total_len = self.data.len; void* start_mem = self.data.ptr; - void* unaligned_pointer_to_offset = start_mem + self.used + OnStackAllocatorHeader.sizeof + offset; - void* aligned_pointer_to_offset = mem::aligned_pointer(unaligned_pointer_to_offset, alignment); - usz end = (usz)(aligned_pointer_to_offset - self.data.ptr) + size - offset; + void* unaligned_pointer_to_offset = start_mem + self.used + OnStackAllocatorHeader.sizeof ; + void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment); + usz end = (usz)(mem - self.data.ptr) + size; Allocator* backing_allocator = self.backing_allocator; if (end > total_len) @@ -144,10 +140,9 @@ fn void*! OnStackAllocator.acquire(&self, usz size, bool clear, usz alignment, u defer catch allocator::free(backing_allocator, chunk); defer try self.chunk = chunk; *chunk = { .prev = self.chunk, .is_aligned = aligned }; - return chunk.data = backing_allocator.acquire(size, clear, aligned ? alignment : 0, offset)!; + return chunk.data = backing_allocator.acquire(size, clear, aligned ? alignment : 0, 0)!; } self.used = end; - void *mem = aligned_pointer_to_offset - offset; OnStackAllocatorHeader* header = mem - OnStackAllocatorHeader.sizeof; header.size = size; return mem; diff --git a/lib/std/core/allocators/temp_allocator.c3 b/lib/std/core/allocators/temp_allocator.c3 index f9536fe78..5b5a1c143 100644 --- a/lib/std/core/allocators/temp_allocator.c3 +++ b/lib/std/core/allocators/temp_allocator.c3 @@ -79,7 +79,7 @@ fn void! TempAllocator._free_page(&self, TempAllocatorPage* page) @inline @local return self.backing_allocator.release(mem, page.is_aligned()); } -fn void*! TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, usz alignment, usz offset) @inline @local +fn void*! TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, usz alignment) @inline @local { // Then the actual start pointer: void* real_pointer = page.start; @@ -94,46 +94,37 @@ fn void*! TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, *pointer_to_prev = page.prev_page; usz page_size = page.pagesize(); // Clear on size > original size. - void* data = self.acquire(size, size > page_size, alignment, offset)!; + void* data = self.acquire(size, size > page_size, alignment, 0)!; mem::copy(data, &page.data[0], page_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT); self.backing_allocator.release(real_pointer, page.is_aligned()); return data; } -fn void*! TempAllocator.resize(&self, void* pointer, usz size, usz alignment, usz offset) @dynamic +fn void*! TempAllocator.resize(&self, void* pointer, usz size, usz alignment, usz deprecated) @dynamic { - if (!size) - { - self.release(pointer, alignment > 0); - return null; - } - if (!pointer) - { - return self.acquire(size, true, alignment, offset); - } TempAllocatorChunk *chunk = pointer - TempAllocatorChunk.sizeof; if (chunk.size == (usz)-1) { assert(self.last_page, "Realloc of non temp pointer"); // First grab the page TempAllocatorPage *page = pointer - TempAllocatorPage.sizeof; - return self._realloc_page(page, size, alignment, offset); + return self._realloc_page(page, size, alignment); } // TODO optimize last allocation - TempAllocatorChunk* data = self.acquire(size, size > chunk.size, alignment, offset)!; + TempAllocatorChunk* data = self.acquire(size, size > chunk.size, alignment, 0)!; mem::copy(data, pointer, chunk.size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT); return data; } /** + * @require size > 0 * @require !alignment || math::is_power_of_2(alignment) * @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big` **/ -fn void*! TempAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic +fn void*! TempAllocator.acquire(&self, usz size, bool clear, usz alignment, usz deprecated) @dynamic { - if (!size) return null; alignment = alignment_for_allocation(alignment); void* start_mem = &self.data; void* starting_ptr = start_mem + self.used; @@ -141,7 +132,7 @@ fn void*! TempAllocator.acquire(&self, usz size, bool clear, usz alignment, usz void* mem = aligned_header_start + TempAllocatorChunk.sizeof; if (alignment > TempAllocatorChunk.alignof) { - mem = mem::aligned_pointer(mem + offset, alignment) - offset; + mem = mem::aligned_pointer(mem, alignment); } usz new_usage = (usz)(mem - start_mem) + size; @@ -159,19 +150,20 @@ fn void*! TempAllocator.acquire(&self, usz size, bool clear, usz alignment, usz TempAllocatorPage* page; // We have something we need to align. - if (alignment > mem::DEFAULT_MEM_ALIGNMENT || offset) + if (alignment > mem::DEFAULT_MEM_ALIGNMENT) { // This is actually simpler, since it will create the offset for us. - usz total_alloc_size = TempAllocatorPage.sizeof + size; + usz total_alloc_size = mem::aligned_offset(TempAllocatorPage.sizeof + size, alignment); if (clear) { - page = allocator::calloc_aligned(self.backing_allocator, total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)!; + mem = allocator::calloc_aligned(self.backing_allocator, total_alloc_size, alignment)!; } else { - page = allocator::malloc_aligned(self.backing_allocator, total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)!; + mem = allocator::malloc_aligned(self.backing_allocator, total_alloc_size, alignment)!; } - page.start = page; + page = (TempAllocatorPage*)mem - 1; + page.start = mem; page.size = size | PAGE_IS_ALIGNED; } else diff --git a/lib/std/core/allocators/tracking_allocator.c3 b/lib/std/core/allocators/tracking_allocator.c3 index f6144741c..646dc43f5 100644 --- a/lib/std/core/allocators/tracking_allocator.c3 +++ b/lib/std/core/allocators/tracking_allocator.c3 @@ -79,48 +79,36 @@ fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator* allocator) **/ fn usz TrackingAllocator.allocation_count(&self) => self.map.count; -fn void*! TrackingAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic +fn void*! TrackingAllocator.acquire(&self, usz size, bool clear, usz alignment, usz deprecated) @dynamic { - void* data = self.inner_allocator.acquire(size, clear, alignment, offset)!; + void* data = self.inner_allocator.acquire(size, clear, alignment, 0)!; + self.allocs_total++; + void*[MAX_BACKTRACE] bt; + backtrace::capture_current(&bt); + self.map.set((uptr)data, { data, size, bt }); + self.mem_total += size; self.allocs_total++; - if (data) - { - void*[MAX_BACKTRACE] bt; - backtrace::capture_current(&bt); - self.map.set((uptr)data, { data, size, bt }); - self.mem_total += size; - self.allocs_total++; - } return data; } -fn void*! TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @dynamic +fn void*! TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz deprecated) @dynamic { - void* data = self.inner_allocator.resize(old_pointer, size, alignment, offset)!; - if (old_pointer) - { - self.map.remove((uptr)old_pointer); - } - if (data) - { - void*[MAX_BACKTRACE] bt; - backtrace::capture_current(&bt); - self.map.set((uptr)data, { data, size, bt }); - self.mem_total += size; - self.allocs_total++; - } + void* data = self.inner_allocator.resize(old_pointer, size, alignment, 0)!; + self.map.remove((uptr)old_pointer); + void*[MAX_BACKTRACE] bt; + backtrace::capture_current(&bt); + self.map.set((uptr)data, { data, size, bt }); + self.mem_total += size; + self.allocs_total++; return data; } fn void TrackingAllocator.release(&self, void* old_pointer, bool is_aligned) @dynamic { - if (old_pointer) - { - if (catch self.map.remove((uptr)old_pointer)) - { - assert(false, "Attempt to release untracked pointer %p, this is likely a bug.", old_pointer); - } - } + if (catch self.map.remove((uptr)old_pointer)) + { + assert(false, "Attempt to release untracked pointer %p, this is likely a bug.", old_pointer); + } self.inner_allocator.release(old_pointer, is_aligned); } diff --git a/lib/std/core/mem.c3 b/lib/std/core/mem.c3 index 305acf603..9955c9da7 100644 --- a/lib/std/core/mem.c3 +++ b/lib/std/core/mem.c3 @@ -253,11 +253,12 @@ fn bool ptr_is_aligned(void* ptr, usz alignment) @inline macro void clear(void* dst, usz len, usz $dst_align = 0, bool $is_volatile = false, bool $inlined = false) { - $if $inlined: - $$memset_inline(dst, (char)0, len, $is_volatile, $dst_align); - $else - $$memset(dst, (char)0, len, $is_volatile, $dst_align); - $endif + $$memset(dst, (char)0, len, $is_volatile, $dst_align); +} + +macro void clear_inline(void* dst, usz $len, usz $dst_align = 0, bool $is_volatile = false) +{ + $$memset_inline(dst, (char)0, $len, $is_volatile, $dst_align); } /** @@ -269,17 +270,30 @@ macro void clear(void* dst, usz len, usz $dst_align = 0, bool $is_volatile = fal * @param $dst_align "the alignment of the destination if different from the default, 0 assumes the default" * @param $src_align "the alignment of the destination if different from the default, 0 assumes the default" * @param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away." - * @param $inlined "True if this copy should never call the OS memcpy." * * @require len == 0 || dst + len <= src || src + len <= dst : "Ranges may not overlap" **/ macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false, bool $inlined = false) { - $if $inlined: - $$memcpy_inline(dst, src, len, $is_volatile, $dst_align, $src_align); - $else - $$memcpy(dst, src, len, $is_volatile, $dst_align, $src_align); - $endif + $$memcpy(dst, src, len, $is_volatile, $dst_align, $src_align); +} + +/** + * Copy memory from src to dst efficiently, assuming the memory ranges do not overlap, it + * will always be inlined and never call memcopy + * + * @param [&out] dst "The destination to copy to" + * @param [&in] src "The source to copy from" + * @param $len "The number of bytes to copy" + * @param $dst_align "the alignment of the destination if different from the default, 0 assumes the default" + * @param $src_align "the alignment of the destination if different from the default, 0 assumes the default" + * @param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away." + * + * @require $len == 0 || dst + $len <= src || src + $len <= dst : "Ranges may not overlap" + **/ +macro void copy_inline(void* dst, void* src, usz $len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false) +{ + $$memcpy_inline(dst, src, $len, $is_volatile, $dst_align, $src_align); } /** @@ -305,19 +319,29 @@ macro void move(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_alig * @param len "The number of bytes to copy" * @param $dst_align "the alignment of the destination if different from the default, 0 assumes the default" * @param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away." - * @param $inlined "True if this copy should never call the OS memset." * * @ensure !len || (dst[0] == val && dst[len - 1] == val) **/ -macro void set(void* dst, char val, usz len, usz $dst_align = 0, bool $is_volatile = false, bool $inlined = false) +macro void set(void* dst, char val, usz len, usz $dst_align = 0, bool $is_volatile = false) { - $if $inlined: - $$memset_inline(dst, val, len, $is_volatile, $dst_align); - $else - $$memset(dst, val, len, $is_volatile, $dst_align); - $endif + $$memset(dst, val, len, $is_volatile, $dst_align); } +/** + * Sets all memory in a region to that of the provided byte. Never calls OS memset. + * + * @param [&out] dst "The destination to copy to" + * @param val "The value to copy into memory" + * @param $len "The number of bytes to copy" + * @param $dst_align "the alignment of the destination if different from the default, 0 assumes the default" + * @param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away." + * + * @ensure !$len || (dst[0] == val && dst[$len - 1] == val) + **/ +macro void set_inline(void* dst, char val, usz $len, usz $dst_align = 0, bool $is_volatile = false) +{ + $$memset_inline(dst, val, $len, $is_volatile, $dst_align); +} /** * @require values::@inner_kind(a) == TypeKind.SUBARRAY || values::@inner_kind(a) == TypeKind.POINTER * @require values::@inner_kind(b) == TypeKind.SUBARRAY || values::@inner_kind(b) == TypeKind.POINTER @@ -530,12 +554,14 @@ fn void* malloc(usz size) @builtin @inline @nodiscard fn void* tmalloc(usz size, usz alignment = 0, usz offset = 0) @builtin @inline @nodiscard { - return allocator::temp().acquire(size, false, alignment, offset)!!; + if (!size) return null; + return allocator::temp().acquire(size, false, alignment, 0)!!; } /** * @require $vacount < 2 : "Too many arguments." * @require $or($vacount == 0, $assignable($vaexpr(0), $Type)) : "The second argument must be an initializer for the type" + * @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead" **/ macro new($Type, ...) @nodiscard { @@ -548,11 +574,40 @@ macro new($Type, ...) @nodiscard $endif } +/** + * Allocate using an aligned allocation. This is necessary for types with a default memory alignment + * exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. + * @require $vacount < 2 : "Too many arguments." + * @require $or($vacount == 0, $assignable($vaexpr(0), $Type)) : "The second argument must be an initializer for the type" + **/ +macro new_aligned($Type, ...) @nodiscard +{ + $if $vacount == 0: + return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof); + $else + $Type* val = malloc_aligned($Type.sizeof, $Type.alignof); + *val = $vaexpr(0); + return val; + $endif +} + +/** + * @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead" + **/ macro alloc($Type) @nodiscard { return ($Type*)malloc($Type.sizeof); } +/** + * Allocate using an aligned allocation. This is necessary for types with a default memory alignment + * exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. + **/ +macro alloc_aligned($Type) @nodiscard +{ + return ($Type*)malloc_aligned($Type.sizeof, $Type.alignof); +} + macro new_clear($Type) @deprecated("Use mem::new") { return new($Type); @@ -588,16 +643,42 @@ macro new_temp_clear($Type) @deprecated("use mem::temp_new") return tcalloc($Type.sizeof); } + + +/** + * @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead" + **/ macro new_array($Type, usz elements) @nodiscard { return allocator::new_array(allocator::heap(), $Type, elements); } +/** + * Allocate using an aligned allocation. This is necessary for types with a default memory alignment + * exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. + **/ +macro new_array_aligned($Type, usz elements) @nodiscard +{ + return allocator::new_array_aligned(allocator::heap(), $Type, elements); +} + +/** + * @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead" + **/ macro alloc_array($Type, usz elements) @nodiscard { return allocator::alloc_array(allocator::heap(), $Type, elements); } +/** + * Allocate using an aligned allocation. This is necessary for types with a default memory alignment + * exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. + **/ +macro alloc_array_aligned($Type, usz elements) @nodiscard +{ + return allocator::alloc_array(allocator::heap(), $Type, elements); +} + macro talloc_array($Type, usz elements) @nodiscard @deprecated("use mem::temp_alloc_array") { return temp_alloc_array($Type, elements); @@ -633,9 +714,15 @@ fn void* calloc(usz size) @builtin @inline @nodiscard return allocator::calloc(allocator::heap(), size); } +fn void* calloc_aligned(usz size, usz alignment) @builtin @inline @nodiscard +{ + return allocator::calloc_aligned(allocator::heap(), size, alignment)!!; +} + fn void* tcalloc(usz size, usz alignment = 0, usz offset = 0) @builtin @inline @nodiscard { - return allocator::temp().acquire(size, false, alignment, offset)!!; + if (!size) return null; + return allocator::temp().acquire(size, false, alignment, 0)!!; } fn void* realloc(void *ptr, usz new_size) @builtin @inline @nodiscard @@ -643,13 +730,25 @@ fn void* realloc(void *ptr, usz new_size) @builtin @inline @nodiscard return allocator::realloc(allocator::heap(), ptr, new_size); } +fn void* realloc_aligned(void *ptr, usz new_size, usz alignment) @builtin @inline @nodiscard +{ + return allocator::realloc_aligned(allocator::heap(), ptr, new_size, alignment)!!; +} + fn void free(void* ptr) @builtin @inline { return allocator::free(allocator::heap(), ptr); } +fn void free_aligned(void* ptr) @builtin @inline +{ + return allocator::free_aligned(allocator::heap(), ptr); +} + fn void* trealloc(void* ptr, usz size, usz alignment = mem::DEFAULT_MEM_ALIGNMENT) @builtin @inline @nodiscard { + if (!size) return null; + if (!ptr) return tmalloc(size, alignment); return allocator::temp().resize(ptr, size, alignment, 0)!!; } diff --git a/lib/std/core/mem_allocator.c3 b/lib/std/core/mem_allocator.c3 index 386da66d4..4c0453ddd 100644 --- a/lib/std/core/mem_allocator.c3 +++ b/lib/std/core/mem_allocator.c3 @@ -15,8 +15,24 @@ interface Allocator { fn void reset(usz mark) @optional; fn usz mark() @optional; + /** + * @require !alignment || math::is_power_of_2(alignment) + * @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big` + * @require offset == 0 `offset no longer supported` + * @require size > 0 + **/ fn void*! acquire(usz size, bool clear, usz alignment, usz offset); + /** + * @require !alignment || math::is_power_of_2(alignment) + * @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big` + * @require offset == 0 `offset no longer supported` + * @require ptr != null + * @require new_size > 0 + **/ fn void*! resize(void* ptr, usz new_size, usz alignment, usz offset); + /** + * @require ptr != null + **/ fn void release(void* ptr, bool aligned); } @@ -40,6 +56,7 @@ macro void* malloc(Allocator* allocator, usz size) @nodiscard macro void*! malloc_try(Allocator* allocator, usz size) @nodiscard { + if (!size) return null; $if env::TESTING: char* data = allocator.acquire(size, false, 0, 0)!; mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT); @@ -56,6 +73,7 @@ macro void* calloc(Allocator* allocator, usz size) @nodiscard macro void*! calloc_try(Allocator* allocator, usz size) @nodiscard { + if (!size) return null; return allocator.acquire(size, true, 0, 0); } @@ -66,19 +84,27 @@ macro void* realloc(Allocator* allocator, void* ptr, usz new_size) @nodiscard macro void*! realloc_try(Allocator* allocator, void* ptr, usz new_size) @nodiscard { + if (!new_size) + { + free(allocator, ptr); + return null; + } + if (!ptr) return allocator.acquire(new_size, false, 0, 0); return allocator.resize(ptr, new_size, 0, 0); } macro void free(Allocator* allocator, void* ptr) { + if (!ptr) return; $if env::TESTING: - if (ptr) ((char*)ptr)[0] = 0xBA; + ((char*)ptr)[0] = 0xBA; $endif allocator.release(ptr, false); } macro void*! malloc_aligned(Allocator* allocator, usz size, usz alignment, usz offset = 0) @nodiscard { + if (!size) return null; $if env::TESTING: char* data = allocator.acquire(size, false, alignment, offset)!; mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT); @@ -90,18 +116,29 @@ macro void*! malloc_aligned(Allocator* allocator, usz size, usz alignment, usz o macro void*! calloc_aligned(Allocator* allocator, usz size, usz alignment, usz offset = 0) @nodiscard { + if (!size) return null; return allocator.acquire(size, true, alignment, offset); } macro void*! realloc_aligned(Allocator* allocator, void* ptr, usz new_size, usz alignment, usz offset = 0) @nodiscard { + if (!new_size) + { + free_aligned(allocator, ptr); + return null; + } + if (!ptr) + { + return malloc_aligned(allocator, new_size, alignment); + } return allocator.resize(ptr, new_size, alignment, offset); } macro void free_aligned(Allocator* allocator, void* ptr) { + if (!ptr) return; $if env::TESTING: - if (ptr) ((char*)ptr)[0] = 0xBA; + ((char*)ptr)[0] = 0xBA; $endif allocator.release(ptr, true); } @@ -166,11 +203,21 @@ macro new_array_try(Allocator* allocator, $Type, usz elements) @nodiscard return (($Type*)calloc_try(allocator, $Type.sizeof * elements))[:elements]; } +macro new_array_aligned(Allocator* allocator, $Type, usz elements) @nodiscard +{ + return ((Type*)calloc_aligned(allocator, $Type.sizeof * elements, $Type.alignof))[:elements]!!; +} + macro alloc_array(Allocator* allocator, $Type, usz elements) @nodiscard { return alloc_array_try(allocator, $Type, elements)!!; } +macro alloc_array_aligned(Allocator* allocator, $Type, usz elements) @nodiscard +{ + return ((Type*)malloc_aligned(allocator, $Type.sizeof * elements, $Type.alignof))[:elements]!!; +} + macro alloc_array_try(Allocator* allocator, $Type, usz elements) @nodiscard { return (($Type*)malloc_try(allocator, $Type.sizeof * elements))[:elements]; @@ -193,23 +240,17 @@ fn any* clone_any(Allocator* allocator, any* value) @nodiscard macro void*! Allocator.alloc_checked(&self, usz size) @deprecated("Use allocator::malloc_try") { - $if env::TESTING: - char* data = self.acquire(size, false, 0, 0)!; - mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT); - return data; - $else - return self.acquire(size, false, 0, 0); - $endif + return malloc_try(self, size); } macro void*! Allocator.calloc_checked(&self, usz size) @deprecated("Use allocator::calloc_try") { - return self.acquire(size, true, 0, 0); + return calloc_try(self, size); } macro void*! Allocator.realloc_checked(&self, void* ptr, usz new_size) @deprecated("Use allocator::realloc_try") { - return self.resize(ptr, new_size, 0, 0); + return realloc_try(ptr, new_size); } macro Allocator.new_array(&self, $Type, usz size, usz end_padding = 0) @deprecated("Use allocator::alloc_array") @@ -258,86 +299,60 @@ macro Allocator.clone(&self, value) @deprecated("Use allocator::clone") return x; } -macro void* Allocator.alloc(&self, usz size) @nodiscard @deprecated("Use allocator::malloc") +fn void* Allocator.alloc(&self, usz size) @nodiscard @deprecated("Use allocator::malloc") { - return self.alloc_checked(size)!!; + return malloc(self, size); } -macro void* Allocator.calloc(&self, usz size) @nodiscard @deprecated("Use allocator::calloc") +fn void* Allocator.calloc(&self, usz size) @nodiscard @deprecated("Use allocator::calloc") { - return self.acquire(size, true, 0, 0)!!; + return calloc(self, size); } -macro void* Allocator.realloc(&self, void* ptr, usz new_size) @nodiscard @deprecated("Use allocator::realloc") +fn void* Allocator.realloc(&self, void* ptr, usz new_size) @nodiscard @deprecated("Use allocator::realloc") { - return self.resize(ptr, new_size, 0, 0)!!; + return realloc(self, ptr, new_size); } -macro void*! Allocator.alloc_aligned(&self, usz size, usz alignment, usz offset = 0) @deprecated("Use allocator::alloc_aligned") +fn void*! Allocator.alloc_aligned(&self, usz size, usz alignment, usz offset = 0) @deprecated("Use allocator::malloc_aligned") { - $if env::TESTING: - char* data = self.acquire(size, false, alignment, offset)!; - mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT); - return data; - $else - return self.acquire(size, false, alignment, offset); - $endif + return malloc_aligned(self, size, alignment, 0); } -macro void*! Allocator.calloc_aligned(&self, usz size, usz alignment, usz offset = 0) @deprecated("Use allocator::calloc_aligned") + +fn void*! Allocator.calloc_aligned(&self, usz size, usz alignment, usz offset = 0) @deprecated("Use allocator::calloc_aligned") { - return self.acquire(size, true, alignment, offset); + return calloc_aligned(self, size, alignment, 0); } -macro void*! Allocator.realloc_aligned(&self, void* ptr, usz new_size, usz alignment = 0, usz offset = 0) @deprecated("Use allocator::realloc_aligned") + +fn void*! Allocator.realloc_aligned(&self, void* ptr, usz new_size, usz alignment = 0, usz offset = 0) @deprecated("Use allocator::realloc_aligned") { - return self.resize(ptr, new_size, alignment, offset); + return realloc_aligned(self, ptr, new_size, alignment, 0); } -macro void Allocator.free(&self, void* ptr) @deprecated("Use allocator::free") +fn void Allocator.free(&self, void* ptr) @deprecated("Use allocator::free") { - $if env::TESTING: - if (ptr) ((char*)ptr)[0] = 0xBA; - $endif - self.release(ptr, false); + free(self, ptr); } -macro void Allocator.free_aligned(&self, void* ptr) @deprecated("Use allocator::free_aligned") +fn void Allocator.free_aligned(&self, void* ptr) @deprecated("Use allocator::free_aligned") { - $if env::TESTING: - if (ptr) ((char*)ptr)[0] = 0xBA; - $endif - self.release(ptr, true); + free_aligned(self, ptr); } /** * @require bytes > 0 * @require alignment > 0 + * @require bytes <= isz.max **/ -macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment, usz offset) +macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment) { - usz header = mem::aligned_offset(AlignedBlock.sizeof + offset, alignment) - offset; + if (alignment < void*.alignof) alignment = void*.alignof; + usz header = AlignedBlock.sizeof + alignment; + usz alignsize = bytes + header; $if @typekind(#alloc_fn(bytes)) == OPTIONAL: - void* data = #alloc_fn(header + bytes)!; - $else - void* data = #alloc_fn(header + bytes); - $endif - void* mem = mem::aligned_pointer(data + header + offset, alignment) - offset; - assert(mem > data); - AlignedBlock* desc = (AlignedBlock*)mem - 1; - *desc = { bytes, data }; - return mem; -} - -/** - * @require bytes > 0 - * @require alignment > 0 - **/ -macro void*! @aligned_calloc(#calloc_fn, usz bytes, usz alignment, usz offset) -{ - usz header = mem::aligned_offset(AlignedBlock.sizeof + offset, alignment) - offset; - $if @typekind(#calloc_fn(bytes)) == OPTIONAL: - void* data = #calloc_fn(header + bytes)!; + void* data = #alloc_fn(alignsize)!; $else - void* data = #calloc_fn(header + bytes); + void* data = #alloc_fn(alignsize); $endif - void* mem = mem::aligned_pointer(data + header + offset, alignment) - offset; + void* mem = mem::aligned_pointer(data + AlignedBlock.sizeof, alignment); AlignedBlock* desc = (AlignedBlock*)mem - 1; assert(mem > data); *desc = { bytes, data }; @@ -364,12 +379,12 @@ macro void! @aligned_free(#free_fn, void* old_pointer) * @require bytes > 0 * @require alignment > 0 **/ -macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment, usz offset) +macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment) { AlignedBlock* desc = (AlignedBlock*)old_pointer - 1; void* data_start = desc.start; - void* new_data = @aligned_calloc(#calloc_fn, bytes, alignment, offset)!; - mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT); + void* new_data = @aligned_alloc(#calloc_fn, bytes, alignment)!; + mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, 1, 1); $if @typekind(#free_fn(data_start)) == OPTIONAL: #free_fn(data_start)!; $else diff --git a/lib/std/math/math.c3 b/lib/std/math/math.c3 index 156c3754d..fdd314a78 100644 --- a/lib/std/math/math.c3 +++ b/lib/std/math/math.c3 @@ -97,10 +97,10 @@ def complex_identity = complex::identity(); def Quaternionf = Quaternion(); def Quaternion = Quaternion(); -def quaternionf_identity = quaternion::identity(); -def quaternion_identity = quaternion::identity(); def QUATERNION_IDENTITY = quaternion::IDENTITY(); def QUATERNIONF_IDENTITY = quaternion::IDENTITY(); +def quaternion_identity = quaternion::identity(); +def quaternionf_identity = quaternion::identity(); def Matrix2f = Matrix2x2(); def Matrix2 = Matrix2x2(); @@ -120,6 +120,14 @@ def MATRIX3F_IDENTITY = matrix::IDENTITY3(); def MATRIX4_IDENTITY = matrix::IDENTITY4(); def MATRIX4F_IDENTITY = matrix::IDENTITY4(); + +/** + * @require types::is_numerical($typeof(x)) `The input must be a numerical value or numerical vector` + **/ +macro deg_to_rad(x) { + return x * PI / 180; +} + /** * @require types::is_numerical($typeof(x)) `The input must be a numerical value or numerical vector` **/ @@ -392,14 +400,14 @@ macro nearbyint(x) => $$nearbyint(x); /** * @require values::@is_promotable_to_floatlike(x) `The input must be a number or a float vector` - * @require $assignable(exp, $typeof(x)) || values::@is_int(exp) `The input must be an integer, castable to the type of x` + * @require $assignable(exp, $typeof(values::promote_int(x))) || values::@is_int(exp) `The input must be an integer, castable to the type of x` **/ macro pow(x, exp) { $if types::is_floatlike($typeof(exp)): - return $$pow(x, ($typeof(x))exp); + return $$pow(values::promote_int(x), ($typeof(values::promote_int(x)))exp); $else - return $$pow_int(x, exp); + return $$pow_int(values::promote_int(x), exp); $endif } @@ -441,6 +449,16 @@ macro rint(x) => $$rint(x); **/ macro round(x) => $$round(x); + +/** + * @require values::@is_floatlike(x) `The input must be a floating point value or float vector` + **/ +macro round_to_decimals(x, int decimal_places) +{ + var div = $$pow_int(($typeof(x))10, decimal_places); + return round(div * x) / div; +} + /** * @require values::@is_floatlike(x) `The input must be a floating point value or float vector` **/ diff --git a/lib/std/math/math_quaternion.c3 b/lib/std/math/math_quaternion.c3 index 3e814257d..938df7cef 100644 --- a/lib/std/math/math_quaternion.c3 +++ b/lib/std/math/math_quaternion.c3 @@ -9,8 +9,9 @@ union Quaternion Real[<4>] v; } -macro Quaternion identity() => { 0, 0, 0, 1 }; const Quaternion IDENTITY = { 0, 0, 0, 1 }; + +macro Quaternion identity() @deprecated("Replaced with QUATERNION_IDENTITY constant") => { 0, 0, 0, 1 }; macro Quaternion Quaternion.add(Quaternion a, Quaternion b) => Quaternion { .v = a.v + b.v }; macro Quaternion Quaternion.add_each(Quaternion a, Real b) => Quaternion { .v = a.v + b }; macro Quaternion Quaternion.sub(Quaternion a, Quaternion b) => Quaternion { .v = a.v - b.v }; @@ -70,33 +71,16 @@ fn Quaternion Quaternion.mul(a, Quaternion b) macro into_matrix(Quaternion* q, $Type) @private { - Quaternion q_norm = q.normalize(); - - var x = q_norm.i; - var y = q_norm.j; - var z = q_norm.k; - var w = q_norm.l; - - var xx = x + x; - var yy = y + y; - var zz = z + z; - - var xxx = xx * x; - var xxy = xx * y; - var xxz = xx * z; - - var yyy = yy * y; - var yyz = yy * z; - var zzz = zz * z; - - var yyw = yy * w; - var zzw = zz * w; - var xxw = xx * w; - - return $Type { - 1 - yyy - zzz, xxy - zzw, xxz + yyw, 0, - xxy + zzw, 1 - xxx - zzz, yyz - xxw, 0, - xxz - yyw, yyz + zzw, 1.0 - xxx - yyy, 0, - 0, 0, 0, 1, - }; + Quaternion rotation = q.normalize(); + var x = rotation.i; + var y = rotation.j; + var z = rotation.k; + var w = rotation.l; + + return $Type { + 1 - 2*y*y - 2*z*z, 2*x*y - 2*z*w, 2*x*z + 2*y*w, 0, + 2*x*y + 2*z*w, 1 - 2*x*x - 2*z*z, 2*y*z - 2*x*w, 0, + 2*x*z - 2*y*w, 2*y*z + 2*x*w , 1 - 2*x*x - 2*y*y, 0, + 0.0, 0.0, 0.0, 1.0, + }; } \ No newline at end of file diff --git a/lib/std/math/math_vector.c3 b/lib/std/math/math_vector.c3 index 986961704..5604e0624 100644 --- a/lib/std/math/math_vector.c3 +++ b/lib/std/math/math_vector.c3 @@ -206,7 +206,7 @@ macro matrix_look_at($Type, eye, target, up) @private vx[0], vx[1], vx[2], - vx.dot(eye), vy[0], vy[1], vy[2], - vy.dot(eye), vz[0], vz[1], vz[2], - vz.dot(eye), - 0, 0, 0, 1 + 0.0, 0.0, 0.0, 1 }; } diff --git a/lib/std/os/linux/heap.c3 b/lib/std/os/linux/heap.c3 new file mode 100644 index 000000000..1de0a759b --- /dev/null +++ b/lib/std/os/linux/heap.c3 @@ -0,0 +1,2 @@ +module std::os::linux @if(env::LINUX); +extern fn usz malloc_usable_size(void* ptr); \ No newline at end of file diff --git a/lib/std/os/macos/heap.c3 b/lib/std/os/macos/heap.c3 new file mode 100644 index 000000000..632776a88 --- /dev/null +++ b/lib/std/os/macos/heap.c3 @@ -0,0 +1,3 @@ +module std::os::darwin @if(env::DARWIN); + +extern fn usz malloc_size(void* ptr); diff --git a/lib/std/os/posix/heap.c3 b/lib/std/os/posix/heap.c3 new file mode 100644 index 000000000..0e3d02d91 --- /dev/null +++ b/lib/std/os/posix/heap.c3 @@ -0,0 +1,3 @@ +module std::os::posix @if(env::POSIX); + +extern fn CInt posix_memalign(void **memptr, usz alignment, usz size); \ No newline at end of file diff --git a/lib/std/os/win32/heap.c3 b/lib/std/os/win32/heap.c3 new file mode 100644 index 000000000..98b99835e --- /dev/null +++ b/lib/std/os/win32/heap.c3 @@ -0,0 +1,11 @@ +module std::os::win32 @if(env::WIN32); + +extern fn void* _aligned_malloc(usz size, usz alignment); +extern fn void* _aligned_realloc(void* memblock, usz size, usz alignment); +extern fn void* _aligned_recalloc(void* memblock, usz size, usz alignment); +extern fn void _aligned_free(void* memblock); +extern fn void _aligned_msize(void* memblock, usz alignment, usz offset); +extern fn void* _aligned_offset_malloc(usz size, usz alignment, usz offset); +extern fn void* _aligned_offset_realloc(void* memblock, usz size, usz alignment, usz offset); +extern fn void* _aligned_offset_recalloc(void* memblock, usz size, usz alignment, usz offset); +extern fn usz _msize(void* memblock); \ No newline at end of file diff --git a/releasenotes.md b/releasenotes.md index 0a9b199ef..be3a1c274 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -1,5 +1,26 @@ # C3C Release Notes +## 0.5.5 Change list + +### Changes / improvements +- Disallow multiple `_` in a row in digits, e.g. `1__000`. + +### Fixes +- Struct/union members now correctly rejects members without storage size #1147. +- `math::pow` will now correctly promote integer arguments. +- Pointer difference would fail where alignment != size (structs etc) #1150 +- Fixed array calculation for npot2 vectors. +- $$memcpy_inline and $$memset_inline fixed. + +### Stdlib changes +- Added `new_aligned` and `alloc_aligned` functions to prevent accidental under-alignment when allocating simd. +- Fixes to realloc of aligned allocations +- Use native Windows calls on aligned allocations on Windows. +- mem::copy_inline, mem::clear_inline and mem::set_inline added. +- mem::copy / clear / set no longer has an `$inline` attribute. +- Native aligned libc malloc on Windows & POSIX. +- Simplification of the allocator interface. + ## 0.5.4 Change list ### Changes / improvements diff --git a/resources/grammar/c3.l b/resources/grammar/c3.l index b4cbc4fd0..c2c567f3b 100644 --- a/resources/grammar/c3.l +++ b/resources/grammar/c3.l @@ -21,10 +21,10 @@ B64 [ \t\v\n\f]?[A-Za-z0-9+/][ \t\v\n\fA-Za-z0-9+/=]+ HEX [ \t\v\n\f]?[A-Fa-f0-9][ \t\v\n\fA-Fa-f0-9]+ INTTYPE ([ui](8|16|32|64|128)|[Uu][Ll]?|[Ll]) REALTYPE ([f](8|16|32|64|128)?) -INT {D}(_*{D})* -HINT {H}(_*{H})* -OINT {O}(_*{O})* -BINT {B}(_*{B})* +INT {D}(_?{D})* +HINT {H}(_?{H})* +OINT {O}(_?{O})* +BINT {B}(_?{B})* %x COMMENT RAW_STRING diff --git a/resources/testfragments/diagnostic.c3 b/resources/testfragments/diagnostic.c3 new file mode 100644 index 000000000..75d0d10d2 --- /dev/null +++ b/resources/testfragments/diagnostic.c3 @@ -0,0 +1,17 @@ +import std; +struct Overalign +{ + double[<33>] x; +} + +fn void main() +{ + List() l; + Overalign y; + for (int i = 0; i < 1000; i++) + { + io::printfn("Pushing %d", i); + l.push(y); + if (i > 3) io::printfn("Diff %d", (usz)l.get_ref(i) - (usz)l.get_ref(i - (usz)1)); + } +} diff --git a/resources/testfragments/toposort.c3 b/resources/testfragments/toposort.c3 index dd3379dd1..65a2378ef 100644 --- a/resources/testfragments/toposort.c3 +++ b/resources/testfragments/toposort.c3 @@ -1,6 +1,5 @@ module topologicalsort; - -extern fn void printf(char* x, ...); +import std::io; struct InputPair { @@ -24,10 +23,9 @@ struct TopoList fn void sort(InputPair[] pairs, uint elements) { InputPair[] result = mem::alloc_array(InputPair, pairs.len); - TopoList* top = mem::alloc_array(TopoList, elements); - for (int i = 0; i < pairs.len; i++) + TopoList* top = mem::new_array(TopoList, elements); + foreach (pair : pairs) { - InputPair pair = pairs[i]; assert(pair.value >= 0 && pair.value < elements); assert(pair.successor >= 0 && pair.successor < elements); top[pair.successor].count++; @@ -60,10 +58,10 @@ fn void sort(InputPair[] pairs, uint elements) } break; } - printf("Got %d elements.\n", count); - for (int i = 0; i < count; i++) + io::printfn("Got %d elements.", count); + foreach (val : intout[:count]) { - printf("%d\n", intout[i]); + io::printn(val); } } diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 56d330b9d..1c3871035 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -2837,6 +2837,7 @@ INLINE bool type_is_invalid_storage_type(Type *type) if (type == type_wildcard_optional) return true; switch (type->type_kind) { + case TYPE_VOID: case TYPE_MEMBER: case TYPE_UNTYPED_LIST: case TYPE_TYPEINFO: diff --git a/src/compiler/lexer.c b/src/compiler/lexer.c index a3638872b..db80addb8 100644 --- a/src/compiler/lexer.c +++ b/src/compiler/lexer.c @@ -417,6 +417,11 @@ static bool scan_number_suffix(Lexer *lexer, bool *is_float) } return true; } + +#define NEXT_AND_CHECK_NO_MULTIPLE_(lexer__) \ + do { if (next(lexer__) == '_' && prev(lexer__) == '_') { \ + return add_error_token_at_current(lexer__, "Multiple consecutive '_' are not allowed."); \ + } } while(0); /** * Parsing octals. Here we depart from the (error prone) C style octals with initial zero e.g. 0231 * Instead we only support 0o prefix like 0o231. Note that lexing here doesn't actually parse the @@ -429,7 +434,8 @@ static bool scan_oct(Lexer *lexer) return add_error_token_at_current(lexer, "An expression starting with '0o' should be followed by octal numbers (0-7)."); } next(lexer); - while (char_is_oct_or_(peek(lexer))) next(lexer); + while (char_is_oct_or_(peek(lexer))) NEXT_AND_CHECK_NO_MULTIPLE_(lexer); + if (char_is_digit(peek(lexer))) { return add_error_token_at_current(lexer, "An expression starting with '0o' should be followed by octal numbers (0-7)."); @@ -453,7 +459,7 @@ static bool scan_binary(Lexer *lexer) return add_error_token_at_current(lexer, "An expression starting with '0b' should be followed by binary digits (0-1)."); } next(lexer); - while (char_is_binary_or_(peek(lexer))) next(lexer); + while (char_is_binary_or_(peek(lexer))) NEXT_AND_CHECK_NO_MULTIPLE_(lexer); if (char_is_digit(peek((lexer)))) { return add_error_token_at_current(lexer, "An expression starting with '0b' should be followed by binary digits (0-1)."); @@ -512,7 +518,7 @@ static inline bool scan_hex(Lexer *lexer) return add_error_token_at_current(lexer, "'0x' starts a hexadecimal number, so the next character should be 0-9, a-f or A-F."); } next(lexer); - while (char_is_hex_or_(peek(lexer))) next(lexer); + while (char_is_hex_or_(peek(lexer))) NEXT_AND_CHECK_NO_MULTIPLE_(lexer); bool is_float = false; if (peek(lexer) == '.' && peek_next(lexer) != '.') { @@ -521,7 +527,7 @@ static inline bool scan_hex(Lexer *lexer) char c = peek(lexer); if (c == '_') return add_error_token_at_current(lexer, "'_' is not allowed directly after decimal point, try removing it."); if (char_is_hex(c)) next(lexer); - while (char_is_hex_or_(peek(lexer))) next(lexer); + while (char_is_hex_or_(peek(lexer))) NEXT_AND_CHECK_NO_MULTIPLE_(lexer); } char c = peek(lexer); if (c == 'p' || c == 'P') @@ -547,7 +553,7 @@ static inline bool scan_dec(Lexer *lexer) // Walk through the digits, we don't need to worry about // initial _ because we only call this if we have a digit initially. - while (char_is_digit_or_(peek(lexer))) next(lexer); + while (char_is_digit_or_(peek(lexer))) NEXT_AND_CHECK_NO_MULTIPLE_(lexer); // Assume no float. bool is_float = false; @@ -565,7 +571,7 @@ static inline bool scan_dec(Lexer *lexer) if (c == '_') return add_error_token_at_current(lexer, "'_' is not allowed directly after decimal point, try removing it."); // Now walk until we see no more digits. // This allows 123. as a floating point number. - while (char_is_digit_or_(peek(lexer))) next(lexer); + while (char_is_digit_or_(peek(lexer))) NEXT_AND_CHECK_NO_MULTIPLE_(lexer); } char c = peek(lexer); // We might have an exponential. We allow 123e1 and 123.e1 as floating point, so diff --git a/src/compiler/llvm_codegen.c b/src/compiler/llvm_codegen.c index 6ade2b2c6..1e1d9b34b 100644 --- a/src/compiler/llvm_codegen.c +++ b/src/compiler/llvm_codegen.c @@ -1693,6 +1693,6 @@ TypeSize llvm_store_size(GenContext *c, LLVMTypeRef type) TypeSize llvm_alloc_size(GenContext *c, LLVMTypeRef type) { - return (TypeSize)aligned_offset((AlignSize)LLVMStoreSizeOfType(c->target_data, type), llvm_abi_alignment(c, type)); + return (TypeSize)aligned_offset((AlignSize)LLVMABISizeOfType(c->target_data, type), llvm_abi_alignment(c, type)); } diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index 290628dd3..46ac64c09 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -4135,7 +4135,7 @@ void llvm_emit_binary(GenContext *c, BEValue *be_value, Expr *expr, BEValue *lhs { val = LLVMBuildSub(c->builder, LLVMBuildPtrToInt(c->builder, lhs_value, int_vec_type, ""), LLVMBuildPtrToInt(c->builder, rhs_value, int_vec_type, ""), ""); - LLVMValueRef divisor = llvm_emit_const_vector(llvm_const_int(c, type_isz, type_abi_alignment(element_type)), len); + LLVMValueRef divisor = llvm_emit_const_vector(llvm_const_int(c, type_isz, type_size(element_type)), len); val = LLVMBuildExactSDiv(c->builder, val, divisor, ""); break; } @@ -4150,7 +4150,7 @@ void llvm_emit_binary(GenContext *c, BEValue *be_value, Expr *expr, BEValue *lhs LLVMTypeRef int_type = llvm_get_type(c, type_isz); val = LLVMBuildSub(c->builder, LLVMBuildPtrToInt(c->builder, lhs_value, int_type, ""), LLVMBuildPtrToInt(c->builder, rhs_value, int_type, ""), ""); - val = LLVMBuildExactSDiv(c->builder, val, llvm_const_int(c, type_isz, type_abi_alignment(lhs_type->pointer)), ""); + val = LLVMBuildExactSDiv(c->builder, val, llvm_const_int(c, type_isz, type_size(lhs_type->pointer)), ""); break; } rhs_value = LLVMBuildNeg(c->builder, rhs_value, ""); @@ -4992,7 +4992,7 @@ LLVMValueRef llvm_emit_array_gep_raw_index(GenContext *c, LLVMValueRef ptr, LLVM { index_val = llvm_zext_trunc(c, index_val, idx_type); } - *alignment = type_min_alignment(llvm_store_size(c, element_type), array_alignment); + *alignment = type_min_alignment(llvm_abi_size(c, element_type), array_alignment); return llvm_emit_pointer_inbounds_gep_raw(c, element_type, ptr, index_val); } @@ -5041,7 +5041,7 @@ LLVMValueRef llvm_emit_const_vector(LLVMValueRef value, ArraySize len) LLVMValueRef llvm_ptr_mult(GenContext *c, LLVMValueRef offset, LLVMTypeRef pointee_type) { - ByteSize size = llvm_store_size(c, pointee_type); + ByteSize size = llvm_abi_size(c, pointee_type); if (size == 1) return offset; LLVMTypeRef offset_type = LLVMTypeOf(offset); @@ -5062,7 +5062,7 @@ LLVMValueRef llvm_emit_pointer_gep_raw(GenContext *c, LLVMTypeRef pointee_type, { return llvm_emit_ptradd_raw(c, ptr, llvm_ptr_mult(c, offset, pointee_type), 1); } - return llvm_emit_ptradd_raw(c, ptr, offset, llvm_store_size(c, pointee_type)); + return llvm_emit_ptradd_raw(c, ptr, offset, llvm_abi_size(c, pointee_type)); } LLVMValueRef llvm_emit_pointer_inbounds_gep_raw(GenContext *c, LLVMTypeRef pointee_type, LLVMValueRef ptr, LLVMValueRef offset) @@ -5071,7 +5071,7 @@ LLVMValueRef llvm_emit_pointer_inbounds_gep_raw(GenContext *c, LLVMTypeRef point { return llvm_emit_ptradd_inbounds_raw(c, ptr, llvm_ptr_mult(c, offset, pointee_type), 1); } - return llvm_emit_ptradd_inbounds_raw(c, ptr, offset, llvm_store_size(c, pointee_type)); + return llvm_emit_ptradd_inbounds_raw(c, ptr, offset, llvm_abi_size(c, pointee_type)); } LLVMValueRef llvm_emit_const_ptradd_inbounds_raw(GenContext *c, LLVMValueRef ptr, ByteSize offset) diff --git a/src/compiler/sema_builtins.c b/src/compiler/sema_builtins.c index e5fbcc437..f27f695a6 100644 --- a/src/compiler/sema_builtins.c +++ b/src/compiler/sema_builtins.c @@ -421,8 +421,15 @@ bool sema_expr_analyse_builtin_call(SemaContext *context, Expr *expr) if (!sema_check_builtin_args(args, (BuiltinArg[]) { BA_INTLIKE }, 1)) return false; rtype = args[0]->type->canonical; break; - case BUILTIN_MEMCOPY: case BUILTIN_MEMCOPY_INLINE: + assert(arg_count == 6); + if (!sema_check_builtin_args(args, + (BuiltinArg[]) { BA_POINTER, BA_POINTER, BA_SIZE, BA_BOOL, BA_SIZE, BA_SIZE }, + 6)) return false; + if (!sema_check_builtin_args_const(&args[2], 4)) return false; + rtype = type_void; + break; + case BUILTIN_MEMCOPY: case BUILTIN_MEMMOVE: assert(arg_count == 6); if (!sema_check_builtin_args(args, @@ -432,11 +439,17 @@ bool sema_expr_analyse_builtin_call(SemaContext *context, Expr *expr) rtype = type_void; break; case BUILTIN_MEMSET: + assert(arg_count == 5); + if (!sema_check_builtin_args(args, (BuiltinArg[]) { BA_POINTER, BA_CHAR, BA_SIZE, BA_BOOL, BA_SIZE }, + 5)) return false; + if (!sema_check_builtin_args_const(&args[3], 2)) return false; + rtype = type_void; + break; case BUILTIN_MEMSET_INLINE: assert(arg_count == 5); if (!sema_check_builtin_args(args, (BuiltinArg[]) { BA_POINTER, BA_CHAR, BA_SIZE, BA_BOOL, BA_SIZE }, 5)) return false; - if (!sema_check_builtin_args_const(&args[3], 2)) return false; + if (!sema_check_builtin_args_const(&args[2], 3)) return false; rtype = type_void; break; case BUILTIN_BITREVERSE: diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index bbb7ee1bc..51446af2b 100644 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -225,13 +225,22 @@ static inline bool sema_analyse_struct_member(SemaContext *context, Decl *parent switch (decl->decl_kind) { case DECL_VAR: + { assert(decl->var.kind == VARDECL_MEMBER); decl->resolve_status = RESOLVE_RUNNING; // Inferred types are not strictly allowed, but we use the int[*] for the flexible array member. - if (!sema_resolve_type_info(context, type_infoptrzero(decl->var.type_info), RESOLVE_TYPE_ALLOW_FLEXIBLE)) return decl_poison(decl); - decl->type = typeget(decl->var.type_info); + assert(type_infoptrzero(decl->var.type_info)); + TypeInfo *type_info = type_infoptr(decl->var.type_info); + if (!sema_resolve_type_info(context, type_info, RESOLVE_TYPE_ALLOW_FLEXIBLE)) return decl_poison(decl); + Type *type = type_info->type; + if (type_is_invalid_storage_type(type)) + { + RETURN_SEMA_ERROR(type_info, "Members cannot be of type %s.", type_quoted_error_string(type)); + } + decl->type = type; decl->resolve_status = RESOLVE_DONE; return true; + } case DECL_STRUCT: case DECL_UNION: case DECL_BITSTRUCT: diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index a2ae734bc..e05004c61 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -1276,7 +1276,7 @@ INLINE bool sema_call_expand_arguments(SemaContext *context, CalledDecl *callee, { if (!sema_analyse_expr(context, arg)) return false; Type *type = arg->type; - if (type_is_invalid_storage_type(type) || type == type_void) + if (type_is_invalid_storage_type(type)) { RETURN_SEMA_ERROR(arg, "A value of type %s cannot be passed as a variadic argument.", type_quoted_error_string(type)); @@ -1489,7 +1489,7 @@ static inline bool sema_call_analyse_invocation(SemaContext *context, Expr *call else { if (!sema_analyse_expr(context, val)) return false; - if (type_is_invalid_storage_type(val->type) || val->type == type_void) + if (type_is_invalid_storage_type(val->type)) { SEMA_ERROR(val, "A value of type %s cannot be passed as a variadic argument.", type_quoted_error_string(val->type)); @@ -1561,7 +1561,7 @@ static inline bool sema_call_analyse_invocation(SemaContext *context, Expr *call if (!type_is_any_interface_ptr(arg->type)) expr_insert_addr(arg); *optional |= IS_OPTIONAL(arg); if (!sema_call_check_contract_param_match(context, param, arg)) return false; - if (type_is_invalid_storage_type(type) || type == type_void) + if (type_is_invalid_storage_type(type)) { SEMA_ERROR(arg, "A value of type %s cannot be passed by reference.", type_quoted_error_string(type)); return false; @@ -6391,7 +6391,7 @@ static inline bool sema_expr_analyse_taddr(SemaContext *context, Expr *expr, boo if (!sema_analyse_expr(context, inner)) return false; Type *type = inner->type; - if (type_is_invalid_storage_type(type) || type == type_void) + if (type_is_invalid_storage_type(type)) { if (failed_ref) { @@ -7141,7 +7141,7 @@ static inline bool sema_expr_analyse_ct_alignof(SemaContext *context, Expr *expr Decl *decl = sema_expr_analyse_var_path(context, main_var); if (!decl) return false; Type *type = decl->type; - if (type_is_invalid_storage_type(type) || type == type_void) + if (type_is_invalid_storage_type(type)) { SEMA_ERROR(main_var, "Cannot use '$alignof' on type %s.", type_quoted_error_string(type)); return false; diff --git a/src/compiler/sema_types.c b/src/compiler/sema_types.c index 67eeccee6..114529836 100644 --- a/src/compiler/sema_types.c +++ b/src/compiler/sema_types.c @@ -299,7 +299,7 @@ INLINE bool sema_resolve_evaltype(SemaContext *context, TypeInfo *type_info, Res } TypeInfo *inner_type = inner->type_expr; if (!sema_resolve_type(context, inner_type, resolve_kind)) return false; - if (type_is_invalid_storage_type(inner_type->type)) + if (inner_type->type != type_void && type_is_invalid_storage_type(inner_type->type)) { SEMA_ERROR(expr, "Compile-time types may not be used with $evaltype."); return false; @@ -313,19 +313,20 @@ INLINE bool sema_resolve_typeof(SemaContext *context, TypeInfo *type_info) { Expr *expr = type_info->unresolved_type_expr; if (!sema_analyse_expr(context, expr)) return false; - if (type_is_invalid_storage_type(expr->type)) + Type *expr_type = expr->type; + if (expr_type != type_void && type_is_invalid_storage_type(expr->type)) { - if (expr->type == type_wildcard) + if (expr_type == type_wildcard) { RETURN_SEMA_ERROR(expr, "This expression has no concrete type."); } - if (expr->type == type_wildcard_optional) + if (expr_type == type_wildcard_optional) { RETURN_SEMA_ERROR(expr, "This optional expression is untyped."); } RETURN_SEMA_ERROR(expr, "Expected a regular runtime expression here."); } - type_info->type = expr->type; + type_info->type = expr_type; return true; } diff --git a/src/compiler/target.c b/src/compiler/target.c index 78619dc11..f6a6dc618 100644 --- a/src/compiler/target.c +++ b/src/compiler/target.c @@ -4,7 +4,7 @@ #include "compiler_internal.h" extern void LLVMSetTargetMachineUseInitArray(LLVMTargetMachineRef ref, bool use_init_array); - +static bool x64features_contains(X86Features *cpu_features, X86Feature feature); static ObjectFormatType object_format_from_os(OsType os, ArchType arch_type); static unsigned arch_pointer_bit_width(OsType os, ArchType arch); static ArchType arch_from_llvm_string(StringSlice string); @@ -47,7 +47,7 @@ bool arch_is_wasm(ArchType type) return type == ARCH_TYPE_WASM32 || type == ARCH_TYPE_WASM64; } -static AlignSize os_arch_max_alignment_of_vector(OsType os, ArchType arch, EnvironmentType type, ARMVariant variant) +static AlignSize os_arch_max_alignment_of_vector(OsType os, ArchType arch, EnvironmentType type, ARMVariant variant, X86Features* features) { switch (arch) { @@ -71,12 +71,14 @@ static AlignSize os_arch_max_alignment_of_vector(OsType os, ArchType arch, Envir case ARCH_TYPE_X86: if (os == OS_TYPE_WIN32) /* COFF */ { - return 8192; + return 8192 / 8; } if (os_is_apple(os)) { // With AVX512 - 512, AVX - 256 otherwise AVX - 128 - return 256; + if (x64features_contains(features, X86_FEAT_AVX512F)) return 512 / 8; + if (x64features_contains(features, X86_FEAT_AVX)) return 256 / 8; + return 128 / 8; } break; default: @@ -393,6 +395,7 @@ static char *x86_feature_name[] = { [X86_FEAT_AVXVNNIINT8] = "avxvnniint8", [X86_FEAT_AVXVNNIINT16] = "avxvnniint16", }; + static X86Feature x86feature_from_string(const char *str) { for (int i = 0; i <= X86_FEATURE_LAST; i++) @@ -1928,7 +1931,11 @@ void target_setup(BuildTarget *target) platform_target.abi = ABI_UNKNOWN; break; } - platform_target.align_max_vector = os_arch_max_alignment_of_vector(platform_target.os, platform_target.arch, platform_target.environment_type, platform_target.arm.variant); + platform_target.align_max_vector = os_arch_max_alignment_of_vector(platform_target.os, + platform_target.arch, + platform_target.environment_type, + platform_target.arm.variant, + &platform_target.x64.features); platform_target.align_max_tls = os_arch_max_alignment_of_tls(platform_target.os, platform_target.arch, platform_target.environment_type); diff --git a/src/version.h b/src/version.h index d67a62d42..97586da73 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define COMPILER_VERSION "0.5.4" +#define COMPILER_VERSION "0.5.5" diff --git a/test/test_suite/expressions/underscore_errors.c3 b/test/test_suite/expressions/underscore_errors.c3 new file mode 100644 index 000000000..e9038815c --- /dev/null +++ b/test/test_suite/expressions/underscore_errors.c3 @@ -0,0 +1,15 @@ +fn void main() +{ + int a = 1___0; // #error: consecutive + int a = 1_0; + float b = 1_0.3__4; // #error: consecutive + float b = 1_0.3_4; + int a2 = 0x1___0; // #error: consecutive + int a2 = 0x1_0; + float b2 = 0x1_0.3__4; // #error: consecutive + float b2 = 0x1_0.3_4; + int a3 = 0b1___0; // #error: consecutive + int a3 = 0b1_0; + int a3 = 0o1___0; // #error: consecutive + int a3 = 0o1_0; +} \ No newline at end of file diff --git a/test/test_suite/struct/struct_bad_member.c3 b/test/test_suite/struct/struct_bad_member.c3 new file mode 100644 index 000000000..1ecb955e5 --- /dev/null +++ b/test/test_suite/struct/struct_bad_member.c3 @@ -0,0 +1,7 @@ +struct Foo { + void bar; // #error: Members cannot be of +} + +fn void main(String[] args) { + Foo foo; +} diff --git a/test/unit/regression/pointer_diff.c3 b/test/unit/regression/pointer_diff.c3 new file mode 100644 index 000000000..cf61f99db --- /dev/null +++ b/test/unit/regression/pointer_diff.c3 @@ -0,0 +1,20 @@ +import std; +struct Foo +{ + int a,b,c,d; +} + +fn void pointer_diff() @test +{ + Foo* foo; + isz offset = &foo[1] - &foo[0]; + assert(offset == 1); +} + +fn void pointer_add() @test +{ + Foo* foo; + Foo* bar = foo + 2; + isz offset = bar - foo; + assert(offset == 2); +} \ No newline at end of file diff --git a/test/unit/regression/vecpointer.c3 b/test/unit/regression/vecpointer.c3 index c201d56e6..5b7283c54 100644 --- a/test/unit/regression/vecpointer.c3 +++ b/test/unit/regression/vecpointer.c3 @@ -1,5 +1,11 @@ module vecpointer @test; +fn void pointer_npot2_size() +{ + int[<9>][3] a; + assert((usz)&a[1] - (usz)&a[0] == 64); +} + fn void pointer_add_sub_diff() { int[5] a; diff --git a/test/unit/stdlib/collections/list.c3 b/test/unit/stdlib/collections/list.c3 index 3b34ab57e..358791ec7 100644 --- a/test/unit/stdlib/collections/list.c3 +++ b/test/unit/stdlib/collections/list.c3 @@ -4,6 +4,20 @@ import std::collections::list; def IntList = List(); def PtrList = List(); +struct Overalign +{ + double[<33>] x; +} + +fn void overaligned_type() +{ + List() l; + Overalign y; + for (int i = 0; i < 1000; i++) l.push(y); + assert((usz)l.get_ref(2) - (usz)l.get_ref(1) == Overalign.sizeof); +} + + fn void! delete_contains_index() { IntList test; diff --git a/test/unit/stdlib/math/math.c3 b/test/unit/stdlib/math/math.c3 index a176ec7a4..630924c19 100644 --- a/test/unit/stdlib/math/math.c3 +++ b/test/unit/stdlib/math/math.c3 @@ -144,4 +144,24 @@ fn void! test_trunc() @test $assert @typeis(math::trunc(f), float); double[<5>] vec = { -123.9, 123.9, 0.9, -0.9, 0 }; assert(math::trunc(vec) == double[<5>] { -123, 123, 0, 0, 0 }); +} + +fn void! test_round_decimals() @test +{ + double d = 0.532451241142; + float d_f = 0.532451241142; + assert(math::round_to_decimals(d, 2) == 0.53); + assert(math::round_to_decimals(d, 5) == 0.53245); + + assert(math::round_to_decimals(d_f, 2) == 0.53f); + assert(math::round_to_decimals(d_f, 5) == 0.53245f); +} + +fn void! test() @test +{ + double radians = math::deg_to_rad(45); + float radians_f = (float)math::deg_to_rad(45); + + assert(math::round_to_decimals(radians, 3) == 0.785); + assert(math::round_to_decimals(radians_f, 3) == 0.785f); } \ No newline at end of file diff --git a/test/unit/stdlib/math/matrix.c3 b/test/unit/stdlib/math/matrix.c3 index c086b2ae9..bb66d0b54 100644 --- a/test/unit/stdlib/math/matrix.c3 +++ b/test/unit/stdlib/math/matrix.c3 @@ -1,8 +1,6 @@ module math_matrix @test; import std::math; - - fn void test_mat4() { {| @@ -22,6 +20,69 @@ fn void test_mat4() Matrix4 value = { 56, 46, 36, 26, 152, 126, 100, 74, 56, 46, 36, 26, 152, 126, 100, 74 }; assert(calc.m == value.m); |}; + + {| + Matrix4 result = { + 0.988936, 0.000000, -0.148340, -0.988936, + -0.014599, 0.995146, -0.097325, -2.970838, + 0.147620, 0.098414, 0.984136, -20.765262, + 0.000000, 0.000000, 0.000000, 1.000000 + }; + + Matrix4f result_f = { + 0.988936, 0.000000, -0.148340, -0.988936, + -0.014599, 0.995146, -0.097325, -2.970838, + 0.147620, 0.098414, 0.984136, -20.765262, + 0.000000, 0.000000, 0.000000, 1.000000 + }; + + Matrix4 result_transposed = { + 0.988936, -0.014599, 0.147620, 0.000000, + 0.000000, 0.995146, 0.098414, 0.000000, + -0.148340, -0.097325, 0.984136, 0.000000, + -0.988936, -2.970838, -20.765262, 1.000000 + }; + + Matrix4f result_transposed_f = { + 0.988936, -0.014599, 0.147620, 0.000000, + 0.000000, 0.995146, 0.098414, 0.000000, + -0.148340, -0.097325, 0.984136, 0.000000, + -0.988936, -2.970838, -20.765262, 1.000000 + }; + + Matrix4 look_at = vector::matrix4_look_at({4.0, 5.0, 20.0}, {1.0, 3.0, 0.0}, {0.0, 1.0, 0.0}); + Matrix4f look_at_f = vector::matrix4f_look_at({4.0, 5.0, 20.0}, {1.0, 3.0, 0.0}, {0.0, 1.0, 0.0}); + + assert(math::round_to_decimals((double[<16>])look_at.m, 4) == math::round_to_decimals((double[<16>])result.m, 4)); + assert(math::round_to_decimals((float[<16>])look_at_f.m, 4) == math::round_to_decimals((float[<16>])result_f.m, 4)); + + assert(math::round_to_decimals((double[<16>])result_transposed.m, 4) == math::round_to_decimals((double[<16>])look_at.transpose().m, 4)); + assert(math::round_to_decimals((float[<16>])result_transposed_f.m, 4) == math::round_to_decimals((float[<16>])look_at_f.transpose().m, 4)); + |}; + + {| + Matrix4 result = { + 1.857087, 0.000000, 0.000000, + 0.000000, 0.000000, 2.414214, + 0.000000, 0.000000, 0.000000, 0.000000, + -1.000200, -0.200020, 0.000000, 0.000000, + -1.000000, 0.000000 + }; + + Matrix4f result_f = { + 1.857087, 0.000000, 0.000000, + 0.000000, 0.000000, 2.414214, + 0.000000, 0.000000, 0.000000, 0.000000, + -1.000200, -0.200020, 0.000000, 0.000000, + -1.000000, 0.000000 + }; + + Matrix4 perspective = matrix4_perspective(math::deg_to_rad(45), 1.3, 0.1, 1000); + Matrix4f perspective_f = matrix4f_perspective((float)math::deg_to_rad(45), 1.3, 0.1, 1000); + + assert(math::round_to_decimals((double[<16>])result.m, 4) == math::round_to_decimals((double[<16>])perspective.m, 4)); + assert(math::round_to_decimals((float[<16>])result_f.m, 4) == math::round_to_decimals((float[<16>])perspective_f.m, 4)); + |}; } diff --git a/test/unit/stdlib/math/quaternion.c3 b/test/unit/stdlib/math/quaternion.c3 new file mode 100644 index 000000000..5bd39e603 --- /dev/null +++ b/test/unit/stdlib/math/quaternion.c3 @@ -0,0 +1,38 @@ +module math_quaternion @test; +import std::math; + +fn void test() +{ + {| + Quaternion rotation = QUATERNION_IDENTITY; + Quaternionf rotation_f = QUATERNIONF_IDENTITY; + assert(rotation.v == {0,0,0,1}); + assert(rotation.v == {0,0,0,1}); + |}; + + {| + Quaternion rotation = QUATERNION_IDENTITY; + Matrix4 identity_matrix = MATRIX4_IDENTITY; + + Quaternionf rotation_f = QUATERNIONF_IDENTITY; + Matrix4f identity_matrix_f = MATRIX4F_IDENTITY; + + assert((double[<16>])rotation.to_matrix().m == (double[<16>])identity_matrix.m); + assert((float[<16>])rotation_f.to_matrixf().m == (float[<16>])identity_matrix_f.m); + |}; + + {| + Matrix4 result = { + 0.428571, -0.285714, 0.857143, 0.000000, + 0.857143, 0.428571, -0.285714, 0.000000, + -0.285714, 0.857143, 0.428571, 0.000000, + 0.000000, 0.000000, 0.000000, 1.000000 + }; + + Matrix4 rotation = Quaternion {0.5, 0.5, 0.5, 1}.to_matrix(); + Matrix4f rotation_f = Quaternionf {0.5, 0.5, 0.5, 1}.to_matrixf(); + + assert(math::round_to_decimals((double[<16>])result.m, 2) == math::round_to_decimals((double[<16>])rotation.m, 2)); + assert(math::round_to_decimals((float[<16>])result.m, 2) == math::round_to_decimals((float[<16>])rotation_f.m, 2)); + |}; +} \ No newline at end of file