| Commit message (Collapse) | Author | Age | Files | Lines |
|
|
|
| |
* stdlib/quips.tl (%quips%): New entry.
|
|
|
|
|
|
|
|
|
|
|
|
| |
* RELNOTES: Updated.
* configure (txr_ver): Bumped version.
* stdlib/ver.tl (lib-version): Bumped.
* txr.1: Bumped version and date.
* txr.vim, tl.vim: Regenerated.
|
|
|
|
|
|
|
| |
* txr.1: New section about special variable
*struct-clause-expander*.
* stdlib/doc-syms.tl: Updated.
|
|
|
|
|
|
|
|
|
| |
* stdlib/match.tl (expand-quasi-match): Add regex cases with
bound variable.
* tests/011/patmatch.tl: Test cases for this.
* txr.1: Documented.
|
|
|
|
|
| |
* stdlib/match.tl (expand-quasi-match): Fix too few arguments
to compile-error for format args.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
With :mass-delegate, it is possible to generate delegation
methods in bulk. All of the methods of a struct type can be
mirrored by delegates in another struct type just by writing
a single :mass-delegate clause.
* stdlib/struct.tlk (:mass-delegate): New struct clause macro.
* tests/012/oop.tl: New tests.
* txr.1: Documented.
* stdlib/doc-syms.tl: Updated.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* lisplib.c (struct_set_entries): Trigger autoload on new
symbols define-struct-clause and *struct-clause-expander*.
* stdlib/struct.tl (*struct-clause-expander*): New variable.
(defstruct): expand-slot local function now returns list of
expanded slots, not a single slot; every case in the tree-case
is converted to return a list. The syntax of a slot clause is
first expanded through *struct-clause-expander hash; if that
works then the resulting list is further scanned for
expansions.
(define-struct-clause): New macro.
(:delegate): New struct clause defined with
define-struct-clause. Provides single-slot delegation.
* tests/012/oop.tl: Tests for :delegate.
* txr.1: Documented define-struct-clause and :delegate.
* stdlib/doc-syms.tl: Updated.
|
|
|
|
|
|
|
|
| |
* stdlib/struct.tl (defstruct): Move the large tree-case in
the loop which calculates the expanded slot syntax into a
local function called expand-slot. This anticipates the
addition of application-defined slot expanders, which will
produce output that has to be recursed upon.
|
|
|
|
|
|
|
|
|
|
|
|
| |
* eval.c (eval_init): Register pairlis intrinsic.
* lib.c, lib.h (pairlis): New function.
* tests/012/seq.tl: New test cases.
* txr.1: Documented.
* stdlib/doc-syms.tl: Updated.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* eval.c (eval_init): Register new intrinsics.
* lib.c, lib.h (subq, subql, subqual, subst): New functions.
* tests/012/seq.tl: New test cases.
* stdlib/optimize.tl (subst): Function removed. The new subst
drop-in replaces this one.
* txr.1: Documented.
* stdlib/doc-syms.tl: Updated.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* stdlib/arith-each.tl (sys:vars-check): New function, copy
and pasted from each-prod.tl.
(sys:arith-each): New macro.
(sum-each, sum-each*, mul-each, mul-each*): Reworked using
sys:arith-each macro. This macro uses logic borrowed from
a stripped-down expand-each in the compiler.
* stdlib/each-prod.tl (sys:expand-each-prod,
sys:expand-arith-each-prod*): Add the block nil around the
mapping call, taking care that the initialization forms
are evaluated outside of the block, and their values bound to
gensyms that then form the function arguments.
* txr.1: Document the missing requirements for all the
affected macros that there must be an anonymous block around
the body, which, if used, determines the return value.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* tree.c (tree_min_node, tree_min, tree_del_min_node,
tree_del_min): New functions.
(tree_init): tree-min-node, tree-min, tree-del-min-node,
tree-del-min: New intrinsics registered.
* tree.h (tree_min_node, tree_min, tree_del_min_node,
tree_del_min): Declared.
* txr.1: Documented.
* tests/010/tree.tl: New tests.
* stdlib/doc-syms.tl: Updated.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* tree.c (tr_insert): New argument for allowing duplicate.
If it is true, suppresses the case of replacing a node,
causing the logic to fall through to traversing right, so the
duplicate key effectively looks like it is greater than the
existing duplicates, and gets inserted as the rightmost
duplicate.
(tr_do_delete_specific, tr_delete_specific): New static functions.
(tree_insert_node): New parameter, passed to tr_insert.
(tree_insert): New parameter, passed to tree_insert_node.
(tree_delete_specific_node): New function.
(tree): New parameter to allow duplicate keys in the elements
sequence.
(tree_construct): Pass t to tree to allow duplicate elements.
(tree_init): Update registrations of tree, tree-insert and
tree-insert-node. Register tree-delete-specific-node function.
* tree.h (tree, tree_insert_node, tree_insert): Declarations
updated.
(tree_delete_specific_node): Declared.
* lib.c (seq): Pass t argument to tree_insert, allowing
duplicates.
* parser.c (circ_backpatch): Likewise.
* parser.y (tree): Pass t to new argument of tree, so
duplicates are preserved in the element list of the #T
literal.
* y.tab.c.shipped: Updated.
* tests/010/tree.tl: Test cases for duplicate keys.
* txr.1: Documented.
* stdlib/doc-syms.tl: Updated.
|
|
|
|
|
|
|
|
|
|
| |
* stdlib/compiler.tl (compile): The symbol-function function
returns true for lambda and that's where we are handling
lambda expressions. However, the (set (symbol-function ...) ...)
then fails: that requires a function name that designates
a mutable function location. Let's restructure the code with
match-case, and handle the lambda pattern separately via
compile-toplevel.
|
|
|
|
|
|
|
|
|
| |
* stdlib/optimize.tl (basic-blocks late-peephole): In one
pattern, an instruction that is recognized as (jend ...)
is inadvertently rewritten to (end ...). Since this is the
last optimization stage, currently, and end and jend are
synonyms for the same opcode, it doesn't matter.
But it could turn into a bug; let's fix it.
|
|
|
|
|
|
| |
* stdlib/optimize (basic-block print): Print the label of the
next block, rather than the block itself. This reduces the
verbosity during debugging.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This work addresses the following issues, which cause compiled
functions to use more stack space than necessary.
Firstly, the compiler doesn't allocate registers tightly.
Every closure's registers can start at t2, but this isn't
done. Secondly, data flow optimizations eliminate registers,
leaving gaps in the register allocation. Code ends up strange:
you may see register t63 used in a function where the next
highest register below that is t39: evidence that a large
number of temporary variables got mapped to registers and
eliminated.
In this change, an optimization is introduced, active at
*opt-level* 6 which compacts the t registers for every
closure: every closure's registers are renumbered starting
from t2. Then, each closure' generating close instruction
is also updated to indicate the accurate number of registers
ensuring no space is wasted on the stack when the closure
is prepared for execution.
* stdlib/compiler.tl (compiler optimize): At optimization
level 6, insert a call to basic-blocks compact-tregs
just before the instruction are pulled out and put through
the late peephole pass.
* stdlib/optimize.tl (basic-block): New slot, closer.
This is pronounced "clozer", as in one who closes.
For any basic block that is the head of a closure (the entry
point ito the closure code), this slot is set to point
to the previous block: the one which ends in the close
instruction which creates this closure: the closer. This is
important because the close instruction can use t registers
for arguments, and those registers belong to the closure.
Those argument registers must be included in the renaming.
(basic-blocks): New slots closures and cl-hash. The former
lists the closure head basic blocks; all the basic blocks
which are the entry blocks of a closure. The cl-hash
associates each head block with a list of all the blocks
(including the head block).
(basic-blocks identify-closures): New method. This scans the
list of blocks, identifying the closure heads, and associating
them with their closers, to establish the bb.closure list.
Then for each closure head in the list, the graph is searched
to find all the blocks of a closure, and these lists are put
into the bb.cl-hash.
(basic-block fill-treg-compacting-map): This method scans
a basic block, ferreting out all of it t registers, and
adds renaming entries for them into a hash that is passed in.
If the block is the head of a closure, then the close
instruction of the block's closer is also scanned for t
registers: but only the arguments, not the destination
register.
(basic-block apply-treg-compacting-map): This method
renames the t register of a block using the renaming map.
It follows the registers in the same way as
fill-treg-compacting-map, and consquently also goes into
the close instruction to rename the argument registers, as
necessary. When tweaking the close instruction, it also
updates the instruction's number-of-tregs field with the
newly calculated number, which comes from the map size.
(basic-blocks compact-tregs): This method ties it together,
using the above methods to identify the basic blocks belonging
to closures, build register renaming maps for them, and hen
apply the renaming.
|
|
|
|
|
|
|
|
|
|
|
| |
I discovered this off chance by searching for occurrences
of (let ,(zip ...) ...) or (let (,*(zip ...)) ...) in the
code base, noticing an incorrect one.
* stdlib/place.tl (sys:register-simple-accessor): Remove
spurious list around ,(zip temps args).
* tests/012/defset.tl: Test cases for define-accessor added.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* txr.1: Adding the missing requirement that each-match
and the other macros in that family must have an implicit
anonymous block around the body forms. This is a requirements
bug, effectively: the programmer expects these operators to be
consistent with the each operator, as part of the same family.
* match.tl (each-match-expander): Implement the requirement.
Since we are using mapping functions, we must use temporary
variables: the evaluation of the expressions which produce the
sequence argument values to the mapping functions must be
outside of the anonymous block. The block must surround only
the function call.
* tests/011/patmatch.tl: Add small test case covering this.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* eval.c (eval_init): nrot, rot intrinsics registered.
* lib.c (nrot, rot): New functions.
* lib.h (nrot, rot): Declared.
* tests/012/seq.tl: New test cases.
* txr.1: Documented.
* stdlib/doc-syms.tl: Updated.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* stdlib/op.tl (ret, aret): Simplify implementation, without
progn or @rest, or interpolation of multiple args.
We use identity* to allow the resulting function to
allow and ignore multiple arguments.
* txr.1: Strangely, an an edit in commit 99131c676,
on Sep 26, 2014, reverted the more accurate equivalence
(ret x) <--> (op identity (progn @rest x))
back to the original documentation
(ret x) <--> (op identity x)
which matched an older implementation. Anyway, that's moot
now; the documentation is updated to give the new equivalence
via identity*.
|
|
|
|
| |
* stdlib/quips.tl (%quips%): New one.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* eval.c (eval_init): Register tuples* intrinsic.
* lib.c (tuples_star_func): New static function.
(tuples_star): New function.
* lib.h (tuples_star): Declared.
* tests/012/seq.tl: New test cases.
* txr.1: Documented.
* stdlib/doc-syms.tl: Updated.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This is related to the pattern in the previous commit. When we have a
situation like this:
lab1
mov tn nil
lab2
ifq tn nil lab4
lab3
gcall tn ...
We know that if lab1 is entered, then lab2 will necessarily
fall through: the lab4 branch is not taken because tn is nil.
But then, tn is clobbered immediately in lab3 by the gcall tn.
In other words, the value stored into tn by lab1 is never used.
Therefore, we can remove the "mov tn nil" instruction and
move the l1 label.
lab2
ifq tn nil lab4
lab1
lab3
gcall tn ...
There are 74 hits for this pattern in stdlib.
* stdlib/optimize.tl (basic-blocks late-peephole): Implement the
above pattern.
|
|
|
|
|
|
|
|
|
|
|
| |
* stdlib/optimize.tl (basic-blocks late-peephole): This pattern doesn't
match any more because of code removed by the previous commit.
If we shorten it by removing the lab1 block, then it matches.
Because the pattern is shorter, the reduction being performed
by the replacement is no longer needed; it has already been done.
The remaining value is that threads the jump from lab3 to lab4.
This missing threading is what I noticed when evaluating the
effects of the previous patch; this restores it.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* stdlib/optimize.tl (basic-blocks merge-jump-thunks): For each
group of candidate jump-blocks, search the entire basic block
list for one more jump block which is identical to the
others, except that it doesn't end in a jmp, but rather
falls through to the same target that the group jumps to.
That block is then included in the group, and also becomes the
default leader since it is pushed to the front.
(basic-blocks late-peephole): Remove the peephole pattern
which tried to attack the same problem. The new approach is
much more effective: when compiling stdlib, 77 instances occur in which
such a block is identified and added! The peephole pattern only
matched six times.
|
|
|
|
| |
* stdlib/quips.tl (sys:%quips%): New entries.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
There are six hits for this in stdlib, two of them in
optimize.tl itself. The situation is like:
label1
(instruction ...)
(jmp label3)
label2
(instruction ...)
label3
where (instruction ...) is identical in both places.
label1 and label2 are functionally identical blocks, which
means that the pattern can be rewritten as:
label1
label2
(instruction ...)
label3
When the label1 path is taken it's faster due to the
elimination of the jmp, and code size is reduced by two
instructions.
This pattern may possibly the result of an imperfection in the
design of the basic-blocks method merge-jump-thunks.
The label1 and label2 blocks are functionally identical.
But merge-jump-thunks looks strictly for blocks that end in a
jmp instruction. It's possible that there was a jmp
instruction and the end of the label2 block, which got
eliminated before merge-jump-thunks, which is done late, just
before late-peephole.
* stdlib/optimize.tl (basic-blocks late-peephole): New rule
for the above pattern.
|
|
|
|
|
|
|
|
| |
* stdlib/getput.tl (file-get-buf, command-get-buf): If the
number of bytes to read is specified, we use an unbuffered
stream. A buffered stream can read more bytes in order to
fill a buffer, which is undesirable when dealing with a
device or pipe.
|
|
|
|
|
|
|
|
|
|
|
|
| |
* RELNOTES: Updated.
* configure (txr_ver): Bumped version.
* stdlib/ver.tl (lib-version): Bumped.
* txr.1: Bumped version and date.
* txr.vim, tl.vim: Regenerated.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
I've noticed a wasteful instruction pattern in the compiled
code for the sys:awk-code-move-check function:
7: 2C020007 movsr t2 t7
8: 3800000E if t7 14
9: 00000007
10: 20050002 gcall t2 1 t9 d1 t8 t6 t7
11: 00090001
12: 00080401
13: 00070006
14: 10000002 end t2
Here, the t2 register can be replaced with t7 in the gcall
and end instructions, and the movsr t2 t7 instruction can
be eliminated.
It looks like something that could somehow be targeted more generally
with a clever peephole pattern assisted by data-flow information,
but for now I'm sticking in a dumb late-peephole pattern which just
looks for this very specific pattern.
* stdlib/optimize.tl (basic-blocks late-peephole): Add new
pattern for eliminating the move, as described above.
There are several hits for this in the standard library in addition to
the awk module: in the path-test, each-prod and getopts files.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
In situations when the compiler evaluates a constant expression in order
to make some code generating decision, we don't just want to be using
safe-const-eval. While that prevents the compiler from blowing up, and
issues a diagnostic, it causes incorrect code to be generated: code
which does not incorporate the unsafe expression. Concrete example:
(if (sqrt -1) (foo) (bar))
if we simply evaluate (sqrt -1) with safe-const-eval, we get a
diagnostic, and the value nil comes out. The compiler will thus
constant-fold this to (bar). Though the diagnostic was emitted,
executing the compiled code does not produce the exception from
(sqrt -1) any more, but just calls bar.
In certain cases where the compiler relies on the evaluation of a
constant expression, we should bypass those cases when the expression is
unsafe.
In cases where the expression will be integrated into the output
code, we can test with constantp. The same is true in some other
mitigating circumstances. For instance if we test with constantp,
and then require safe-const-eval to produce an integer, we are
okay, because a throwing evaluation will not produce an integer.
* stdlib/compiler.tl (safe-constantp): New function.
(compiler (comp-if, comp-ift, lambda-apply-transform)): Use
safe-constantp rather than constantp for determining whether
an expression is suitable for compile-time evaluation.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
When the compiler evaluates constant expressions, it's
possible that they throw, for instance (/ 1 0).
We now handle it better; the compiler warns about it
and is able to keep working, avoiding constant-folding
the expression.
* stdlib/compiler.tl (eval-cache-entry): New struct type.
(%eval-cache%): New hash table variable.
(compiler (comp-arith-form, comp-fun-form)): Add some missing
rlcp calls to track locations for rewritten arithmetic
expressions, so we usefullly diagnose a (sys:b/ ...) and such.
(compiler (comp-if, comp-ift, comp-arith-form,
comp-apply-call, reduce-constant, lambda-apply-transform)):
Replace instances of eval of constantp expressions with
safe-const-eval, and instances of the result of eval being
quoted with safe-const-reduce.
(orig-form, safe-const-reduce, safe-const-eval,
eval-cache-emit-warnings): New functions.
(compile-top-level, with-compilation-unit): Call
eval-emit-cache-warnings to warn about constant expressions
that threw.
squash! compiler: handle constant expressions that throw.
|
|
|
|
|
|
|
|
| |
* txr.1: Fix spelling errors that have crept in due to
read-once and the quasiliteral fixes to matching.
* stdlib/doc-syms.tl: Forgotten refresh, needed by the
fix to the wrong random-float-inc name.
|
|
|
|
|
|
| |
* stdlib/compiler.tl (compiler comp-arith-neg-form): Instead
of the length check on the form, we can use a tree case to
require three argument.
|
|
|
|
|
| |
* stdlib/compiler.tl (compiler comp-arith-neg-form): Remove
algebraically incorrect transformation.
|
|
|
|
|
|
|
| |
* stdlib/compiler.tl (compiler comp-arith-form): There is no
need here to pass the form through reduce-constant, since
we are about to divide up its arguments and individualy reduce
them, much like what that function does.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Commit c8f12ee44d226924b89cdd764b65a5f6a4030b81 tried to fix
an aspect of this problem. I ran into an issue where the try
code produced a D register as its output, and this was
clobbered by the catch code. In fact, the catch code simply
must not clobber the try fragment's output register. No matter
what register that is, it is not safe. A writable T register
could hold a variable.
For instance, this infinitely looping code is miscompiled
such that it terminates:
(let ((x 42))
(while (eql x 42)
(catch
(progn (throw 'foo)
x)
(foo () 0))))
When the exception is caught by the (foo () 0) clause
x is overwritten with that 0 value.
The variable x is assigned to a register like t13,
and since the progn form returns x as it value, it
compiles to a fragment (tfrag) which indicates t13
as its output register.
The catch code wrongly borrows ohis as its own output
register, placing the 0 value into it.
* stdlib/compiler.tl (compiler comp-catch): Get rid of the
coreg local variable, replacing all its uses with oreg.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The compiler is lifting top-level lambdas, such as those
generated by defun, using the load-time mechanism. This has
the undesireable effect of unnecessarily placing the lambdas
into a D register.
* stdlib/compiler.tl (*top-level*): New special variable.
This indicates that the compiler is compiling code that
is outside of any lambda.
(compiler comp-lambda-impl): Bind *top-level* to nil when
compiling lambda, so its interior is no longer at the
top level.
(compiler comp-lambda): Suppress the unnecessary lifting
optimization if the lambda expression is in the top-level,
outside of any other lambda, indicated by *top-level* being
true.
(compile-toplevel): Bind *top-level* to t.
|
|
|
|
|
|
|
|
| |
* stdlib/compiler.tl (compile-toplevel): Recently, I removed
the binding of *load-time* to t from this function. That is
not quite right; we want to positively bind it to nil. A new
top-level compile starts out in non-load-time. Suppose that
some compile-time evaluation recurses into the compiler.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The checks for native Windows are incorrect, plus there
are some issues in the path-volume function.
We cannot check for native Windows at macro-expansion time simply by
calling (find #\\ path-sep-chars) because we compile on Cygwin where
that is false. What we must do is check for being on Windows at
macro-expansion time, and then in the "yes" branch of that decision, the
code must perform the path-sep-char test at run-time. In the "no"
branch, we can output smaller code that doesn't deal with Windows.
* stdlib/copy-file.tl (if-windows, if-native-windows): New macro, which
give a clear syntax to the above described testing.
(path-split): Use if-native-windows.
(path-volume): Use if-native-windows. In addition, fix some broken
tests. The tests for a UNC path "//whatever" cannot just test that the
first components are "", because that also matches the path "/".
It has t be that the first two components are "", and there are more
components. A similar issue occurs in the situation when there is
a drive letter. We cannot conclude that if the component after the
drive letter is "", then it's a drive absolute path, because that
situation occurs in a path like "c:" which is relative.
We also destructively manipulate the path to splice out the volume
part and turn it into a simple relative or absolute path. This is
because the path-simplify function deosn't deal with the volume prefix;
its logic like eliminating .. navigations from root do not work if the
prefix component is present.
(rel-path): We handle a missing error case here: one path has volume
prefix and the other doesn't. Also the error cases that can only occur
on Windows are wrapped with if-windows to remove them at compile time.
|
|
|
|
|
|
|
|
| |
* stdlib/match.tl (compile-match): Handle
the (sys:expr (sys:quasi ...)) case by recursing on
the (sys:quasi ...) part, thus making them equivalent.
This fixes the newly introduced broken test cases, and meets
the newly documented requirements.
|
|
|
|
|
|
|
| |
* stdlib/pic.tl (insert-commas): Use ifa to bind the
anaphoric variable it to [num (pred i)]. With the new
ifa behavior involving read-place, this now prevents
two accesses to the array.
|
|
|
|
|
|
|
|
|
|
| |
* stdlib/ifa.tl (ifa): When the form bound to the it
anaphoric variable is a place, such that we use placelet,
wrap the place in (read-once ...) so that multiple
evaluations of it don't cause multiple accesses of the
place.
* txr.1: Documented.
|
|
|
|
|
|
|
|
|
|
|
| |
* lisplib.c (place_set_entries): Trigger autoload on
read-once.
* stdlib/place.t (read-once): New function and place.
* txr.1: Documented.
* stdlib/doc-syms.tl: Updated.
|
|
|
|
|
|
|
|
|
|
|
|
| |
This function includes the 1.0 value excluded by random-float.
* rand.c (random_float_incl): New static function.
(rand_init): Register random_float_incl intrinsic.
* txr.1: Document, and add discussion about uniformity requirements
and what they mean and do not mean.
* stdlib/doc-syms.tl: Updated.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* stdlib/compiler.tl (compiler optimize): After the dataflow-driven
peephole optimization, call elim-dead-code again.
* stdlib/optimize.tl (basic-blocks check-bypass-empty): New method.
(basic-bocks elim-dead-code): After eliminating unreachable blocks
from the list, we use check-bypass-empty to squeeze out any
empty blocks: blocks that have no instructions in their list,
other than the leading label. This helps elim-next-jmp
to find more opportunities to eliminate a wasteful jump, because
sometimes these jumps straddle over empty blocks.
Furthermore, elim-next-jmp can generate more empty blocks itself;
so we check for this situation, delete the blocks and iterate.
|
|
|
|
|
|
|
|
|
|
|
| |
* stdlib/optimize.tl (basic-blocks elim-dead-code): When
clearing the links before recalculating the graph, also clear
the next field of every block, because link-graph only sets
this if necessary, assuming that the value is already nil.
Thus by not resetting it, we risk leaving stale values in
these .next fields. The code reachability calculation relies
on next fields, so if they falsely point to dead blocks, those
blocks could be falsely retained.
|
|
|
|
|
|
|
|
|
|
|
| |
* stdlib/compiler.tl (usr:compile-toplevel): Do not
bind *load-time* to t at the top level. The idea behind this
binding was to treat load-time as a transparent form that does
nothing if it occurs in the top-level since the top-level is
already at load-time. However, this is problematic because it
breaks the expectation that load-time calculations are
factored out of a form and done prior to its evaluation, even
if that form is top-level.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The immediate problem is that with-dyn-lib creates a defvarl,
but deffi uses load-time forms to refer to that. In compiled
code, these load-time evaluations will occur before the
defvarl exists. The conceptual problem is that with-dyn-lib
might not be a top-level form. It can be conditionally
executed, as it happens in stdlib/doc-syms.tl, which is now
broken. Let's not use load-time, but straight lexical
environments.
* stdlib/ffi.tl (with-dyn-lib): Translate to a simple let
which binds sys:ffi-lib as a lexical variable.
(sys:with-dyn-lib-check): Use lexical-var-p to test what
sys:ffi-lib is lexically bound as a variable.
(deffi, sys:deffi-cb-expander): Instead of gloval defvarl
variables, bind the needed pieces to lexical variables,
placing the generated defun into that scope.
|