alisp.org: make a backlog for tasks
This commit is contained in:
106
alisp.org
106
alisp.org
@@ -83,7 +83,6 @@ easier. We're not going to do anything more advanced than the API
|
|||||||
i.e. no parsing.
|
i.e. no parsing.
|
||||||
**** DONE Design the tagged union
|
**** DONE Design the tagged union
|
||||||
**** DONE Design the API
|
**** DONE Design the API
|
||||||
*** WIP Figure out the possible parse errors
|
|
||||||
*** DONE Design what a "parser function" would look like
|
*** DONE Design what a "parser function" would look like
|
||||||
The general function is something like ~stream -> T | Err~. What
|
The general function is something like ~stream -> T | Err~. What
|
||||||
other state do we need to encode?
|
other state do we need to encode?
|
||||||
@@ -91,38 +90,68 @@ other state do we need to encode?
|
|||||||
*** TODO Write a parser for symbols
|
*** TODO Write a parser for symbols
|
||||||
*** TODO Write a parser for lists
|
*** TODO Write a parser for lists
|
||||||
*** TODO Write a parser for vectors
|
*** TODO Write a parser for vectors
|
||||||
*** TODO Write a generic parser that returns a generic expression
|
*** TODO Write the general parser
|
||||||
** TODO Test system registration of allocated units :test:
|
** Backlog
|
||||||
In particular, does clean up work as we expect? Do we have situations
|
*** TODO Design Big Integers
|
||||||
where we may double free or not clean up something we should've?
|
We currently have 62 bit integers implemented via immediate values
|
||||||
** TODO Design garbage collection scheme :design:gc:
|
embedded in a pointer. We need to be able to support even _bigger_
|
||||||
|
integers. How do we do this?
|
||||||
|
*** TODO Design garbage collection scheme :design:gc:
|
||||||
Really, regardless of what I do, we need to have some kind of garbage
|
Really, regardless of what I do, we need to have some kind of garbage
|
||||||
collection header on whatever we allocate e.g. references if we
|
collection header on whatever managed objects we allocate.
|
||||||
reference count for GC.
|
|
||||||
*** TODO Mark stage
|
|
||||||
When some item is being used by another, we need a way to adjust the
|
|
||||||
metadata such that the system is aware of it being used.
|
|
||||||
|
|
||||||
For example, say I have X, Y as random allocated objects. Then I
|
Firstly, the distinction between managed and unmanaged objects:
|
||||||
construct CONS(X, Y). Then, ref(X) and ref(Y) need to be incremented
|
- Managed objects are allocations that are generated as part of
|
||||||
to say I'm using them.
|
evaluating user code i.e. strings, vectors, conses that are all made
|
||||||
*** TODO Sweep
|
as part of evaluating code.
|
||||||
Say I have an object that I construct, C. If ref(C) = 0, then C is no
|
- Unmanaged objects are allocations we do as part of the runtime.
|
||||||
longer needed, and is free.
|
These are things that we expect to have near infinite lifetimes
|
||||||
|
(such as the symbol table, vector of allocated objects, etc).
|
||||||
|
|
||||||
There are two components to this:
|
We need to perform garbage collection against the managed objects, and
|
||||||
- we need a way of decrementing references if an object is no longer needed.
|
leave the unmanaged objects to the runtime.
|
||||||
- we need a way of running through everything we've allocated so far
|
**** TODO Mark stage
|
||||||
to figure out what's free to take away.
|
We need to mark all objects that are currently accessible from the
|
||||||
|
environment. This means we need to have a root environment which we
|
||||||
|
mark all our accessible objects from. Any objects that aren't marked
|
||||||
|
by this obviously are inaccessible, so we can then sweep them.
|
||||||
|
|
||||||
Once we've filtered out what we don't need anymore, what should we do
|
How do we store this mark on our managed objects? I think the
|
||||||
with them? Naive approach would be to just actually ~free~ the cells
|
simplest approach would be to allocate an extra 8 bytes just before
|
||||||
in question. But I think the next item may be a better idea.
|
any managed object we allocate i.e. [8 byte buffer] <object>. Then,
|
||||||
*** TODO Use previous allocations if they're free to use
|
during the mark phase, we can walk back those 8 bytes and
|
||||||
If we have no references to a cell, this cell is free to use. In
|
inspect/mutate the mark.
|
||||||
other words, if I later allocate something of the same type, instead
|
**** TODO Sweep
|
||||||
of allocating a new object, why not just use the one I've already got?
|
Once we've marked all objects that are accessible, we need to
|
||||||
|
investigate all the objects that aren't. We do have
|
||||||
|
[[file:alisp.h::vec_t memory;][this]] which provides a global map of
|
||||||
|
all the stuff we've allocated so far ([[file:alisp.h::void
|
||||||
|
sys_register(sys_t *, lisp_t *);][sys_register]] is used to add to
|
||||||
|
this, and any managed object is expected to register).
|
||||||
|
|
||||||
|
We can iterate through the map and collect all the unmarked objects.
|
||||||
|
What do we do with these?
|
||||||
|
|
||||||
|
1) They are technically freestanding objects allocated through
|
||||||
|
~calloc~, so we could just free them.
|
||||||
|
2) Manage some collection of previous allocations to reuse in our next
|
||||||
|
allocation.
|
||||||
|
|
||||||
|
Option (1) is obvious and relatively clean to setup in our current
|
||||||
|
idea:
|
||||||
|
- Say at index I we have an object that is unmarked
|
||||||
|
- Free the associated object at index I
|
||||||
|
- Swap the end of the array with the cell at index I, then decrement
|
||||||
|
the size of the container
|
||||||
|
|
||||||
|
This is an O(1) time operation.
|
||||||
|
|
||||||
|
Option (2) is also relatively straightforward, but we need another
|
||||||
|
counter in order to make it work:
|
||||||
|
- Say at index I we have an object that is unmarked
|
||||||
|
- Swap the end of the array with the cell at index I, then decrement
|
||||||
|
the size of the container
|
||||||
|
**** TODO Use previous allocations if they're free to use
|
||||||
This way, instead of deleting the memory or forgetting about it, we
|
This way, instead of deleting the memory or forgetting about it, we
|
||||||
can reuse it. We need to be really careful to make sure our ref(X) is
|
can reuse it. We need to be really careful to make sure our ref(X) is
|
||||||
actually precise, we don't want to trample on the user's hard work.
|
actually precise, we don't want to trample on the user's hard work.
|
||||||
@@ -151,18 +180,25 @@ Latter approach time complexity:
|
|||||||
|
|
||||||
Former approach is better time complexity wise, but latter is way
|
Former approach is better time complexity wise, but latter is way
|
||||||
better in terms of simplicity of code. Must deliberate.
|
better in terms of simplicity of code. Must deliberate.
|
||||||
** TODO Design Big Integers
|
*** TODO Test system registration of allocated units :test:
|
||||||
We currently have 62 bit integers implemented via immediate values
|
In particular, does clean up work as we expect? Do we have situations
|
||||||
embedded in a pointer. We need to be able to support even _bigger_
|
where we may double free or not clean up something we should've?
|
||||||
integers. How do we do this?
|
*** TODO Design Strings
|
||||||
** DONE Test value constructors and destructors :test:
|
We have ~sv_t~ so our basic C API is done. We just need pluggable
|
||||||
|
functions to construct and deconstruct strings as lisps.
|
||||||
|
*** TODO Capitalise symbols (TBD) :optimisation:design:
|
||||||
|
Should we capitalise symbols? This way, we limit the symbol table's
|
||||||
|
possible options a bit (potentially we could design a better hashing
|
||||||
|
algorithm?) and it would be kinda like an actual Lisp.
|
||||||
|
** Completed
|
||||||
|
*** DONE Test value constructors and destructors :test:
|
||||||
Test if ~make_int~ works with ~as_int,~ ~intern~ with ~as_sym~.
|
Test if ~make_int~ works with ~as_int,~ ~intern~ with ~as_sym~.
|
||||||
Latter will require a symbol table.
|
Latter will require a symbol table.
|
||||||
** DONE Test containers constructors and destructors :test:
|
*** DONE Test containers constructors and destructors :test:
|
||||||
Test if ~make_vec~ works with ~as_vec~, ~cons~ with ~as_cons~ AND
|
Test if ~make_vec~ works with ~as_vec~, ~cons~ with ~as_cons~ AND
|
||||||
~CAR~, ~CDR~.
|
~CAR~, ~CDR~.
|
||||||
|
|
||||||
We may need to think of effective ways to deal with NILs in ~car~ and
|
We may need to think of effective ways to deal with NILs in ~car~ and
|
||||||
~cdr~. Maybe make functions as well as the macros so I can choose
|
~cdr~. Maybe make functions as well as the macros so I can choose
|
||||||
between them?
|
between them?
|
||||||
*** DONE Write more tests
|
**** DONE Write more tests
|
||||||
|
|||||||
Reference in New Issue
Block a user