Skip to content

Commit

Permalink
Support allocator instances, introduce string builder and more changes
Browse files Browse the repository at this point in the history
* Allocators now can have instances
* Rename stringbuffer to stringbuilder and add more useful methods to it
* Move string format function to stringbuilder
* Add stringview.sub method
* Rename generic_allocator to general_allocator
* Add the FixedLinearAllocator to perform efficient allocations when
not using the GC, more useful allocators will come later
  • Loading branch information
edubart committed Aug 8, 2020
1 parent 64cba5a commit 4c67531
Show file tree
Hide file tree
Showing 25 changed files with 735 additions and 433 deletions.
6 changes: 3 additions & 3 deletions docs/pages/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -836,14 +836,14 @@ Memory can be allocated using C malloc and free.

```nelua
require 'memory'
require 'allocators.generic'
require 'allocators.general'
local Person = @record{name: string, age: integer}
local p: Person* = generic_allocator.new(@Person)
local p: Person* = general_allocator:new(@Person)
p.name = "John"
p.age = 20
print(p.name, p.age)
generic_allocator.delete(p)
general_allocator:delete(p)
p = nilptr
```

Expand Down
14 changes: 7 additions & 7 deletions examples/linkedlist.nelua
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require 'allocators.generic'
require 'allocators.general'

local allocator = @generic_allocator
local allocator: auto = general_allocator

## local make_list = generalize(function(T)
local T = @#[T]#
Expand All @@ -11,7 +11,7 @@ local allocator = @generic_allocator
}

local function new_node(prev: ListNodeT*, next: ListNodeT*, value: T): ListNodeT*
local node = allocator.new(@ListNodeT)
local node = allocator:new(@ListNodeT)
node.prev = prev
node.next = next
node.value = value
Expand Down Expand Up @@ -76,7 +76,7 @@ local allocator = @generic_allocator
node.next.prev = node.prev
end
local next = node.next
allocator.delete(node)
allocator:delete(node)
return next
end

Expand All @@ -90,7 +90,7 @@ local allocator = @generic_allocator
if unlikely(self.tail == node) then
self.tail = nilptr
end
allocator.delete(node)
allocator:delete(node)
end

function ListT:remove_last()
Expand All @@ -103,14 +103,14 @@ local allocator = @generic_allocator
if unlikely(self.head == node) then
self.head = nilptr
end
allocator.delete(node)
allocator:delete(node)
end

function ListT:clear()
local it = self.head
while it do
local next = it.next
allocator.delete(it)
allocator:delete(it)
it = next
end
self.head = nilptr
Expand Down
6 changes: 3 additions & 3 deletions examples/overview.nelua
Original file line number Diff line number Diff line change
Expand Up @@ -408,15 +408,15 @@ do -- Dereferencing and referencing
end

require 'memory'
require 'allocators.generic'
require 'allocators.general'

do -- Allocating memory
local Person = @record{name: string, age: integer}
local p: Person* = generic_allocator.new(@Person)
local p: Person* = general_allocator:new(@Person)
p.name = "John"
p.age = 20
print(p.name, p.age)
generic_allocator.delete(p)
general_allocator:delete(p)
p = nilptr
end

Expand Down
8 changes: 5 additions & 3 deletions lib/allocators/default.nelua
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
## if not pragmas.nogc then
require 'allocators.gc'
global default_allocator = @gc_allocator
global default_allocator: auto = &gc_allocator
global DefaultAllocator: auto = GCAllocator
## else
require 'allocators.generic'
global default_allocator = @generic_allocator
require 'allocators.general'
global default_allocator: auto = &general_allocator
global DefaultAllocator: auto = GeneralAllocator
## end
107 changes: 107 additions & 0 deletions lib/allocators/fixedlinear.nelua
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
-- Fixed Linear Allocator
--
-- This allocator allocates everything in a contiguous fixed array.
-- The memory will reside on the stack if declared inside a function
-- or on the static memory storage if declared in a top scope.
-- Reallocations and deallocations does not free space unless for the most recent allocation.
-- To free space `dealloc_all` should be called once all operations on its allocations are finished.
--
-- The purpose of this allocator is to have many quick allocations with almost
-- no runtime cost when the maximum used space is known ahead.
-- Also useful to deallocate many small allocated objects at once.
-- Also useful for temporary allocations.
--
-- When declaring it on the stack if the allocator is not reused then there is no need to perform
-- deallocations, just leave the scope ends to have a quick cleanup.
--
-- Take care to not use a large space when using it on the stack, or the program may crash
-- with not enough stack space.
--
-- By default allocations are aligned to 8 bytes unless explicit told otherwise.
-- By default allocations are filled with zeros even for normal alloc/realloc functions.
-- When there is not enough space nilptr is returned on allocations.

require 'memory'
require 'allocators.interface'

local function align_addr(addr: usize, align: usize): usize <inline>
return (addr + (align-1)) & ~(align-1)
end

## local make_fixed_linear_allocator = generalize(function(N, align)
## align = align or 8
## staticassert(N % align == 0, 'FixedLinearAllocator: N must be multiple of align')
local ALIGN <comptime> = #[align]#
local N <comptime> = #[N]#
local FixedLinearAllocatorN <aligned(#[align]#)> = @record{
memory: byte[N],
last_pos: usize,
next_pos: usize
}

function FixedLinearAllocatorN:alloc(size: usize): pointer
local pos: usize = self.next_pos
local advanced_pos: usize = pos + size
if unlikely(advanced_pos > N) then return nilptr end
local p: pointer = &self.memory[pos]
self.last_pos = pos
self.next_pos = align_addr(advanced_pos, ALIGN)
return p
end

function FixedLinearAllocatorN:alloc0(size: usize): pointer <inline>
-- the allocator memory should already by pre initialized to zeros
return self:alloc(size)
end

function FixedLinearAllocatorN:realloc(p: pointer, size: usize): pointer
local pos: usize = (@usize)(p) - (@usize)(&self.memory[0])
if pos == self.last_pos then -- grow last allocation
local advanced_pos: usize = pos + size
if unlikely(advanced_pos > N) then return nilptr end
local old_next_pos: usize = self.next_pos
if likely(size > 0) then
self.next_pos = align_addr(advanced_pos, ALIGN)
else
self.next_pos = pos
end
-- fill with zeros the removed part in case of shrink
if unlikely(self.next_pos < old_next_pos) then
memory.zero(&self.memory[self.next_pos], old_next_pos - self.next_pos)
end
return p
else
if unlikely(size == 0) then return nilptr end
-- move to a new allocation
local newp: pointer = self:alloc(size)
if unlikely(newp == nilptr) then return nilptr end
return newp
end
end

function FixedLinearAllocatorN:realloc0(p: pointer, newsize: usize, oldsize: usize): pointer <inline>
-- the allocator memory should already by pre initialized to zeros
return self:realloc(p, newsize)
end

function FixedLinearAllocatorN:dealloc(p: pointer)
local pos: usize = (@usize)(p) - (@usize)(&self.memory[0])
if pos == self.last_pos then -- shrink last allocation
memory.zero(&self.memory[pos], self.next_pos - pos)
self.next_pos = pos
end
end

-- Free all allocations, effectively resetting the allocator to the zeroed state.
function FixedLinearAllocatorN:dealloc_all()
memory.zero(&self.memory[0], #self.memory)
self.last_pos = 0
self.next_pos = 0
end

## implement_allocator_interface(FixedLinearAllocatorN)

## return FixedLinearAllocatorN
## end)

global FixedLinearAllocator: type = #[make_fixed_linear_allocator]#
56 changes: 30 additions & 26 deletions lib/allocators/gc.nelua
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@

require 'span'
require 'memory'
require 'allocators.generic'
require 'allocators.general'

## pragmas.nochecks = true

local jmp_buf <cimport,cinclude'<setjmp.h>',nodecl> = @record{dummy: usize}
local function setjmp(env: jmp_buf) <cimport,cinclude'<setjmp.h>',nodecl> end

local allocator = @generic_allocator
local allocator: auto = general_allocator

global GCFlags = @enum(usize) {
MARK = 1,
Expand Down Expand Up @@ -176,7 +176,7 @@ function GC:_rehash(newsize: usize): boolean
local oldsize: usize = self.nslots

self.nslots = newsize
self.items = allocator.spanalloc0(@GCItem, self.nslots)
self.items = allocator:spanalloc0(@GCItem, self.nslots)

if self.items.size == 0 then
self.nslots = oldsize
Expand All @@ -189,7 +189,7 @@ function GC:_rehash(newsize: usize): boolean
self:_add_ptr(olditems[i].ptr, olditems[i].size, olditems[i].flags, olditems[i].finalizer)
end
end
allocator.spandealloc(olditems)
allocator:spandealloc(olditems)
return true
end

Expand Down Expand Up @@ -305,8 +305,8 @@ function GC:_sweep() <noinline>
end
end

self.frees = allocator.spanrealloc(self.frees, self.nfrees)
if self.frees.size == 0 then
self.frees = allocator:spanrealloc(self.frees, self.nfrees)
if self.frees.size ~= self.nfrees then
return
end

Expand Down Expand Up @@ -348,11 +348,11 @@ function GC:_sweep() <noinline>
if self.frees[j].finalizer then
self.frees[j].finalizer(self.frees[j].ptr)
end
allocator.dealloc(self.frees[j].ptr)
allocator:dealloc(self.frees[j].ptr)
end
end

allocator.spandealloc(self.frees)
allocator:spandealloc(self.frees)
self.frees = {}
self.nfrees = 0
end
Expand All @@ -369,8 +369,8 @@ end
function GC:stop()
self:_unmark_all()
self:_sweep()
allocator.spandealloc(self.items)
allocator.spandealloc(self.frees)
allocator:spandealloc(self.items)
allocator:spandealloc(self.frees)
$self = {}
self.paused = true
end
Expand Down Expand Up @@ -420,28 +420,30 @@ end
function GC:_alloc(ptr: pointer, size: usize, flags: usize, finalizer: FinalizerCallback): pointer <inline>
if ptr then
if not self:add(ptr, size, flags, finalizer) then
allocator.dealloc(ptr)
allocator:dealloc(ptr)
ptr = nilptr
end
end
return ptr
end

function GC:alloc_opt(size: usize, flags: usize, finalizer: FinalizerCallback): pointer
local ptr: pointer = allocator.alloc(size)
local ptr: pointer = allocator:alloc(size)
return self:_alloc(ptr, size, flags, finalizer)
end

function GC:alloc0_opt(size: usize, flags: usize, finalizer: FinalizerCallback): pointer
local ptr: pointer = allocator.alloc0(size)
local ptr: pointer = allocator:alloc0(size)
return self:_alloc(ptr, size, flags, finalizer)
end

function GC:alloc(size: usize): pointer
check(size > 0_u, 'GCAllocator.alloc: size cannot be zero')
return self:alloc_opt(size, 0, nilptr)
end

function GC:alloc0(size: usize): pointer
check(size > 0_u, 'GCAllocator.alloc0: size must be greater than 0')
return self:alloc0_opt(size, 0, nilptr)
end

Expand All @@ -453,7 +455,7 @@ function GC:_realloc(qtr: pointer, ptr: pointer, size: usize): pointer <inline>

if not ptr then
if not self:add(qtr, size, 0, nilptr) then
allocator.dealloc(qtr)
allocator:dealloc(qtr)
qtr = nilptr
end
return qtr
Expand All @@ -475,12 +477,14 @@ function GC:_realloc(qtr: pointer, ptr: pointer, size: usize): pointer <inline>
end

function GC:realloc(ptr: pointer, size: usize): pointer
local qtr: pointer = allocator.realloc(ptr, size)
local qtr: pointer = allocator:realloc(ptr, size)
if not qtr then return nilptr end
return self:_realloc(qtr, ptr, size)
end

function GC:realloc0(ptr: pointer, size: usize, oldsize: usize): pointer
local qtr: pointer = allocator.realloc0(ptr, size, oldsize)
local qtr: pointer = allocator:realloc0(ptr, size, oldsize)
if not qtr then return nilptr end
return self:_realloc(qtr, ptr, size)
end

Expand All @@ -490,7 +494,7 @@ function GC:dealloc(ptr: pointer)
if p.finalizer then
p.finalizer(ptr)
end
allocator.dealloc(ptr)
allocator:dealloc(ptr)
self:rem(ptr)
end
end
Expand Down Expand Up @@ -534,28 +538,28 @@ end

require 'allocators.interface'

global gc_allocator = @record{}
global GCAllocator = @record{}

function gc_allocator.alloc(size: usize): pointer
check(size > 0_u, 'gc_allocator.alloc: size cannot be zero')
function GCAllocator:alloc(size: usize): pointer
return gc:alloc(size)
end

function gc_allocator.alloc0(size: usize): pointer
check(size > 0_u, 'gc_allocator.alloc0: size must be greater than 0')
function GCAllocator:alloc0(size: usize): pointer
return gc:alloc0(size)
end

function gc_allocator.realloc(p: pointer, size: usize): pointer
function GCAllocator:realloc(p: pointer, size: usize): pointer
return gc:realloc(p, size)
end

function gc_allocator.realloc0(p: pointer, newsize: usize, oldsize: usize): pointer
function GCAllocator:realloc0(p: pointer, newsize: usize, oldsize: usize): pointer
return gc:realloc0(p, newsize, oldsize)
end

function gc_allocator.dealloc(p: pointer)
function GCAllocator:dealloc(p: pointer)
gc:dealloc(p)
end

## implement_allocator_interface(gc_allocator)
## implement_allocator_interface(GCAllocator)

global gc_allocator: GCAllocator
Loading

0 comments on commit 4c67531

Please sign in to comment.