- {run,bds,frs}_set_size functions were very similar; I've updated them to
follow the same naming convention and execution order to indicate that.
- these functions are now renamed to xxx_set_limit -that simplifies some code
- there were inconsistencies in how we've treated boot sizes (limit vs size)
All these operations were propagated up to the condition system in order to
reset the stack margin in the case of a non-local exit. We can do that easily
with open-coded unwind protect within stacks.
Objects have a well defind extent so there is no need to rely on GC for
them. This change allows us to move stack initialization before garbage
collector is introduced into the system (or even without any GC).
This commit removes initial bindings array from the process and allocates it
only in the bds stack. To make fields in the structure less confusing we rename
initial_bindings slot to inherit_bindings_p.
On observable behavior change is that bindings are inherited when the process is
enabled, not when it is created. That was not specified in documentation so it
should be fine to change this behavior. Moreover it makes more sense from the
programmer perspective -- we want to inherit bindings of the process that starts
our thread, not the one that creates it.
Also remove an unused operator FEstack_advance. Macro definition names do not
change and they expand (when appropriate) to the inline function.
New function names refer to data_stack, while upcased macro names mentions only
the STACK. This is in line with how forth refers to the data stack when it is
not ambiguous.
We didn't export *TEST-NAME*, FAILED and TEST-FAILURE from 2am-ecl, so the macro
FAIL did not work correctly.
The test mix.0010.file-stream-fd contained a safeguard for a bug from 2016, no
need to guard against that today.
The GC is enabled when we create initial symbols. The function make_this_symbol
initializes symbols from a static table as follows:
1. initialize symbol structure fields
2. associate the symbol with its package
3. associate the symbol with its function
4. increment cl_num_symbols_in_core
Most notably in point 2:
s->symbol.name = ecl_make_constant_base_string(name,-1);
GC's stacks_scanner marks
[cl_symbols; cl_symbols+cl_num_symbols_in_core]
in other words, if the GC kicks in between 1. and 4., the symbol name may be
garbage collected, messing the symbol name and package exports.
To mitigate that, we assign intermediate values on the stack and increment
the number of symbols in core after the symbol is initialized:
1. store initialization values on stack -- cl_object foo = alloc();
2. initialize symbol structure fields
3. increment cl_num_symbols_in_core
4. associate the symbol with its package and function
Since the stack frame is always marked fully, decrementing the pointer keeps a
reference to the unbound object effectively preventing its garbage collection
until it is overwritten by another binding or the stack is closed.
Just like with other stacks we call ecl_dealloc on the old stack. Previously
we had stack frames that could have referenced it, but we can treat it like
other stacks, because there are not lingering references.
Previously we've cached the stack base and dereferenced from there, but when the
stack is resized, this reference is invalidated and there is no good fix it in
all frames (we don't store back references).
This commit replaces pointers with indexes, so the stack frame is always
displaced onto the current lisp stack.
ad6f1f7f10 (2008-05-12) allowed for displacing
stack frames onto the values vector. This commit seems to be before we've stored
them on lisp stack.
As for the rationale why we revert that change:
1. There may be a slight performance improvement because the standard gf
dispatch won't be forced to copy the stack frame
2. Stack frame operators assume that they are displaced onto env->stack*, for
example ecl_stack_frame_push increments env->stack_top
3. If we fully buy into assumption that the stack frame is displaced onto
env->stack* then we may replace pointers with indexes and protect the user from
accessing invalidated memory when the stack grows
The last point will be implemented in the next commit. It is worth noting, that
frames auto-heal themselves if their own operation is the cause of the stack
growth, but all other stack frames are left invalid after the operation.
OP_FLET previously had to first create functions referencing the env "before"
and then add these functions to the env "after". This is because we were
addressing from the stack top:
env: bottom[0:var]top
fn1: ref(top-1)
fn2: ref(top-1)
env: bottom[0:var, 1:fn1, 2:f2n]top
Otherwise fn2 referencing top-1 would point to fn1 instead of var.
Now that locals are addressed from the stack bottom we can add functions eagerly
to locals without consing intermediate sequence of functions:
env: bottom[0:var]top
fn1: ref(bottom+0)
env: bottom[0:var, 1:fn1]top
fn2: ref(bottom+0)
env: bottom[0:var, 1:fn1, 2:f2n]top
That saves us one loop (nfun iterations).
A similar observation applies to LABELS, although we still need the closure
fixup because earlier function may reference a function defined later, so we
still need a second loop. That said we don't have to cons a separate sequence of
functions, because we may iterate over locals vector instead.
Both changes save us from an operator that adds a collection to the lcl_env, so
we remove tack_lcl macro and its expansion function foot_lcl.
Previously when unbinding we've skipped blocks and tags and let the frame exit
to collect the frame variable. This commit simplifies managing the environment
in the compiler.
Previously our calculations were off because:
- we've counted special variables
- we didn't decrement the env size when unbound
One could argue that we need only to increment it, but then we'd end up with the
size too big. Consider:
(progn
(let ((a 1)) (foobar))
(let ((b 1)) (foobar)))
In this snippet we have two local variables, but we never look at locals beyond
the first element.
env_size is not computed correctly (neither max size nor current size), these
assertions are meant to help with mending the issue in order to correctly
determine the size of the locals environment.
c_lcl_idx computes the location of the entry in the locals env. It does traverse
the list again, so it adds a little overhead, but this will allow us to
customize it later to compute a location from the bottom.
We are going to change the representation of the local environment, but first we
make identify accessors and put them behind macros.
While doing so the OP_LABELS has been changed to look similar to OP_FLET. Among
other things we cons separately functions into fun_env, but this inefficiency
will be removed later when we address local entries from the frame.base.
As noted in the comment, GCC 12.0 onwards generates an invalid warning about
array bounds when we cast a stack allocated object to cl_object and use it
warning: array subscript ‘union cl_lispunion[0]’ is partly outside array
bounds of ‘struct ecl_stack_frame[1]’ [-Warray-bounds=]
The code is conforming, but apparently GCC IR has generated a dead branch that
accesses the array out of bounds. For time being we disable this warning.
See also:
https://gitlab.com/libeigen/eigen/-/issues/2506https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106274https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105523
and generally explroe gcc bugzilla for this kind of false positive.