| Commit message (Collapse) | Author | Age | Files | Lines |
|
|
|
|
|
|
|
|
|
|
| |
* stdlib/compiler.tl (compiler (comp-atom, comp-dwim),
safe-const-reduce, igno-notfound): Use ignore
rather than use for marking unused variable.
* stdlib/copy-file.tl (copy-files, copy-path-rec,
remove-path-rec, chmod-rec, chown-rec): Likewise.
* stdlib/optimize.tl (basic-block print): Likewise.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
We want the ignore function to go away; but if variables
are mentioned, to suppress unused warnings on them.
* stdlib/compiler.tl (%functional-funs%): Remove nilf
from list since we are handling it specially.
(compiler comp-fun-form): Recognize (ignore ...)
and (nilf ...) forms, transforming
them into (progn .... nil). In the case of ignore,
walk the arguments: if any look like variables, mark
them used.
Also, add the use function to the pattern which handles
identity, since it is a synonym.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Now that we have the t convention in macro parameters,
we can use it to suppress many cases of unused variables
in the compiler.
* stdlib/compiler.tl (compiler (comp-setq, comp-lisp1-setq,
comp-setqf, comp-cond, comp-ift, comp-switch,
comp-unwind-protect, comp-return, comp-handler-bind,
comp-catch, eliminate-frame, comp-lambda-impl,
comp-fun, comp-or, comp-prog1, comp-arith-form,
comp-arith-neg-form, comp-fun-form, comp-apply-call,
comp-for, comp-tree-bind, comp-mac-param-bind,
comp-mac-env-param-bind, comp-tree-case, comp-lisp1-value,
comp-dwim, comp-prof, comp-load-time-lit), expand-quasi-mods,
expand-dohash, expand-each, expand-defvar, expand-defun,
expand-defmacro, expand-defsymacro, lambda-apply-transform,
usr:compile): Fix unused variable warnings mostly by using
the t mechanism in tree-case or mac-param-bind. In
some cases, (use var) is used where it would be detrimental
to diagnostic quality to replace identifiers in the
pattern with t. A few unused "else" variables were renamed
and used.
(safe-const-reduce, ign-notfound): Fix unused exception
clause unused parameters using (use param).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* eval.c (expand_params_rec, bind_macro_params): Handle t
specially everywhere a parameter can occur. Expansion
allows the syntax through without extending the
environment with a t variable; binding walks over
the structure without binding a variable.
* stdlib/compiler.tl (expand-bind-mac-params): Likewise,
handle occurrences of t, suppressing the generation of
and assignment to variables, while ensuring that
initializing expressions are evaluated.
* tests/011/tree-bind.tl: New file.
* txr.1: Documented.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
We fix numerous unused variable situations uncovered
by the new diagnostic. Most of those occurring inside
tree-bind or mac-param-bind forms remain unfixed.
These are caused by the idiom of binding a dummy variable
as a placeholder in the structure. I am instead going to
introduce a mechanism into tree-bind/mac-param-bind
for indicating an ignored piece of structure.
* stdlib/compiler.tl (compiler (comp-if, eliminate-frame,
comp-lambda-impl, comp-typep, comp-fun-form, expand-and,
reduce-or, compiler-emit-warnings, usr:compile
with-compile-opts): Eliminate unused variables in structural
pattern matches in various ways: eliminating predicate
argument variables, replacing place holder variables by
@nil, or just using the variables when possible.
(compiler compile-in-toplevel): Remove unused saved-nlev variable.
(compiler comp-atom): Use (use oreg) form to suppress
unused parameter warning.
(compiler comp-return-form): Eliminate unused binfo variable.
The lookup-block method is called for the side effect of
marking the block used, so we keep that call.
(compiler comp-let): Unused variable specials is gone.
(compiler comp-or): Unused variable lastfrag is gone,
as is the assignment to it. There is a reason assignment
isn't use!
(compiler comp-inline-lambda): Get rid of the two variables
called dummy by folding the associated calculation into
an adjacent initform using progn and prog1.
(comp-tree-case): Remove unused ncases, lerrtest and lnext
variables.
(safe-const-eval): Remove unused reduced-form variable,
and simplify code, eliminating another local.
|
|
|
|
|
| |
* stdlib/compiler.tl (opt-controlled-diag): If a the compiler
option's value is t, treat it as :warn.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Unused variables in tree-bind forms are not generating
diagnostics with source location info. We are missing
some rlcp calls.
* stdlib/compiler.tl (compiler comp-catch): The generated
lambda here ends up transformed to a let by the
lambda-apply-transfom function. We must propagate source
info to it, otherwise unused catch clause parameters
get diagnosed without it.
(compiler (comp-for, comp-mac-param-bind,
comp-mac-env-param-bind, comp-tree-case): Confer source
location info onto the err-form argument of
expand-bind-mac-params.
(expand-bind-mac-params): Pass source location info
from err-form onto the generated let* form.
Thus, diagnostics related to variables in that let*
get reported against that form's location.
(lambda-apply-transform): Pass source location info
from the lambda expression to the generated let.
* stdlib/except.tl (usr:catch): Pass source loc info
from each clause source code to the transformed
clause. The transformed clause will turn into a lambda
which will turn into a let in comp-catch, and then
into a let in lambda-apply-transform.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* autoload.c (compiler_set_entries): Register slot symbol
"unused".
* stdlib/compiler.tl (compile-opts): New slot, unused.
(%warning-syms%): List unused symbol.
(env lookup-var): Support optional mark-used parameter,
just like lookup-fun.
(env unused-check): New method.
(compiler comp-var): Pass t to mark-used parameter of
lookup-var to register the use.
(compiler (comp-let, comp-var)): Call unused-check
method after sub-compilations are done to dump
diagnostics about unused variables.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Introducing a compiler options system, so we can
control diagnostics and such. We begin with
three options for diagnosing shadowing.
* autoload.c (compiler_set_entries): Register a
structure name compiler-opts, a with-compile-opts
function name, *compile-opts* variable name, and
slots shadow-fun, shadow-var and shadow-cross.
* stdlib/compiler.tl (compile-opts): New struct.
(%warning-syms%): New macro.
(*compile-opts*): New special variable.
(when-opt, with-compile-opts): New macros.
(opt-controlled-diag): New function.
(env extend-var): Call extend-var* method instead of
repeating code.
(env extend-var*): Implement shadow-var and shadow-cross
diagnostic options.
(env extend-fun): Implement shadow-fun and shadow-cross
diagnostic options.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* LICENSE, LICENSE-CYG, METALICENSE, Makefile, alloca.h,
args.c, args.h, arith.c, arith.h, autoload.c, autoload.h,
buf.c, buf.h, cadr.c, cadr.h, chksum.c, chksum.h,
chksums/crc32.c, chksums/crc32.h, combi.c, combi.h, configure,
debug.c, debug.h, eval.c, eval.h, ffi.c, ffi.h, filter.c,
filter.h, ftw.c, ftw.h, gc.c, gc.h, glob.c, glob.h, gzio.c,
gzio.h, hash.c, hash.h, itypes.c, itypes.h, jmp.S,
lex.yy.c.shipped, lib.c, lib.h, linenoise/linenoise.c,
linenoise/linenoise.h, match.c, match.h, parser.c, parser.h,
parser.l, parser.y, protsym.c, psquare.h, rand.c, rand.h,
regex.c, regex.h, signal.c, signal.h, socket.c, socket.h,
stdlib/arith-each.tl, stdlib/asm.tl, stdlib/awk.tl,
stdlib/build.tl, stdlib/cadr.tl, stdlib/compiler.tl,
stdlib/constfun.tl, stdlib/conv.tl, stdlib/copy-file.tl,
stdlib/debugger.tl, stdlib/defset.tl, stdlib/doloop.tl,
stdlib/each-prod.tl, stdlib/error.tl, stdlib/except.tl,
stdlib/ffi.tl, stdlib/getopts.tl, stdlib/getput.tl,
stdlib/hash.tl, stdlib/ifa.tl, stdlib/keyparams.tl,
stdlib/match.tl, stdlib/op.tl, stdlib/optimize.tl,
stdlib/package.tl, stdlib/param.tl, stdlib/path-test.tl,
stdlib/pic.tl, stdlib/place.tl, stdlib/pmac.tl,
stdlib/quips.tl, stdlib/save-exe.tl, stdlib/socket.tl,
stdlib/stream-wrap.tl, stdlib/struct.tl, stdlib/tagbody.tl,
stdlib/termios.tl, stdlib/trace.tl, stdlib/txr-case.tl,
stdlib/type.tl, stdlib/vm-param.tl, stdlib/with-resources.tl,
stdlib/with-stream.tl, stdlib/yield.tl, stream.c, stream.h,
struct.c, struct.h, strudel.c, strudel.h, sysif.c, sysif.h,
syslog.c, syslog.h, termios.c, termios.h, time.c, time.h,
tree.c, tree.h, txr.1, txr.c, txr.h, unwind.c, unwind.h,
utf8.c, utf8.h, vm.c, vm.h, vmop.h, win/cleansvg.txr,
y.tab.c.shipped: Copyright year bumped to 2023.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
When an invalid call expression is constant folded, such
as (call 'abs 1 2), runaway recursion occurs. This is
because due to the wrong number of arguments being passed
to abs, the safe-const-reduce function returns the
expression unmodified. The comp-apply-call method then
passes it to compile, wrongly assuming a reduction had
taken place, and so everything repeats.
* stdlib/compiler.tl (comp-apply-call): Detect when
safe-const-reduce has hit a fixed point by returning
the input form. In that case, we don't call the compiler
top-level entry point, but the comp-fun-form method
directly; the wrong function call will be compiled without
constant folding and throw an error at run-time.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* stdlib/compiler.tl (comp-catch): Under an optimization level
of at least 1, if no symbols are being caught, or if the
try expression is a safe constant expression, then just
compile the try expression. Furthermore, if there is only one
exception symbol being caught, and a catch clause is for a
subtype of that symbol, we eliminate the run-time
exception-subtype-p test. This will always be true if the catch
macros are being used, because the list of symbols is derived
from the clauses. Lastly, if there is only one exception symbol
being caught, any clause which doesn't match that symbol is
now eliminated as dead code. That shouldn't happen unless
the sys:catch operator is used directly.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The bad situation reproduced as a miscompilation of some prof
forms at *opt-level* 5 or above.
The basic idea is that there is a situation like this
prof t2
... profiled code here producing value in t8
mov t2 t8
end t2
end t2
The code block produces a value in t8, which is copied into
t2, and executes the end instruction. This instruction does not
fall through to the next one but passes control back to the
prof instruction. The prof instruction then stores the result
value, which came from t2, back into the t2 register and
resumes the program at the end t2.
The first bad thing that happens is that the end instructions
get merged together into one basic block. The optimizer then
treats them without regard for the prof instruction, as if
they were a linear sequence. It looks like the register move
mov t2 t8
is wasteful and so it eliminates it, rewriting the end instruction
to:
end t8
end t8
Of course, the second instruction is now wrong because prof is
still producing the result in t2.
To fix this without changing the instruction set, I'm introducing
another pseudo-op that represents end, called xend. This is
similar to jend, except that jend is regarded as an unconditional
branch whereas xend isn't. The special thing about xend is
that a basic block in which it occcurs is marked as non-joinable.
It will not be joined with the following basic block.
* stdlib/asm.tl (xend): New alias opcode for end.
* stdlib/compiler.tl (comp-prof): Use xend to end prof fragment,
rather than plain end.
* stdlib/optimize.tl (basic-block): New slot, nojoin.
If true, block cannot be joined with next one.
(basic-blocks jump-ops): Add xend to list of jump ops,
so that a basic block will terminate on xend.
(basic-blocks link-graph): Set the nojoin flag on a
basic block which contains (and thus ends with) xend.
(basic-blocks local-liveness): Add xend to the case
in def-ref that handles end.
(basic-blocks (peephole, join-blocks)): Refuse to join
blocks marked nojoin.
* tests/019/comp-bugs.tl: New file with miscompiled
test case that was returning 42 instead of (42 0 0 0)
as a result of the wrong register's value being returned.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The scoping is not behind handled correctly for optional
variables. The init-forms are being evaluated in a scope
in which all the variables are already visible, instead
of sequentially. Thus, for instance, variable rebinding
doesn't work, as in (lambda (: (x x)) ...). When the
argument is missing, x ends up with the value : because
the expression refers to the new x, rather than the
outer x.
* stdlib/compiler.tl (compiler comp-lambda-impl):
Perform the compilation of the init-forms earlier.
Use the same new trick that is used for let*:
the target for the code fragment is a locaton obtained
from get-loc, which is then attached to a variable
afterward. The spec-sub helper is extended with a loc
parameter to help with this case.
* tests/012/lambda.tl: New test case that fails without
this fix.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* stdlib/compiler.tl (env rename-var): Method removed.
(compiler comp-let): Instead of initially creating
a let* variable as a gensym, and then renaming it
after compiling the init expression, we now just
obtain the location not bound to a variable, use the
location when compiling the init form, and bind
the location to a variable right after. This is
cleaner since the only thing we are mutating now is
the environment, and we are not wastefully allocating
a gensym. The real motivation is that this is building
up to a bugfix in compiling optional variables in
lambda: stay tuned!
|
|
|
|
|
|
|
|
|
|
|
|
| |
* stdlib/compiler.tl (env get-loc): New method for
allocating v-reg, split out of extend-var and
extend-var*. Now there is a check for the v-cntr
overflow.
(env (extend-var, extend-var*)): Taken an optional
loc parameter, so the caller can optionally allocate
a v-reg location using get-loc, and then specify
that location when creating a variable. If the
argument is omitted, use get-loc.
|
|
|
|
|
|
|
|
|
| |
* stdlib/compiler.tl (env (extend-var, extend-var*)):
Return the variable binding rather than the alist
containing it.
(compiler (comp-catch, comp-let, comp-tree-case)):
Drop use of cdar on return value of extend-var
to ferret out the binding from the alist.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The compile-file function must only try a different
path, such as with a suffix, if a given path fails
to open due to non-existence. If the failure is for
another reason, like permissions. In that case we want
to propagate the failure.
* stdlib/compiler.tl (ign-notfound): New macro.
This lets through all errors, catching only path-not-found,
converting that to a nil result.
(open-compile-streams): Use ign-notfound instead of
ignerr when trying to open an input file for reading.
Also, we lose the ignerr wrapping around the open-file.
We let any error whatsoever just bubble out so that the user
is better informed about what went wrong. The requirement
to close the input stream is handled by the obvious
unwind-protect.
|
|
|
|
|
|
|
|
|
| |
* stdlib/compiler.tl (translate-hash-bang): New function.
(compile-file-conditionally): Use translate-hash-bang to
treat hash bang line.
* txr.1: Revised hash bang treatment by file compiler
documented.
|
|
|
|
|
|
|
|
| |
* stdlib/compiler.tl (open-compile-streams): If the in-path
is unsuffixed, try opening it without adding any suffix first.
If that fails, then try .tl in that order.
* txr.1: Documented.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* stdlib/compiler.tl (compile-file-conditionally): Recognize a
potential package-manipulating form not checking whether its
main operator is in %pakage-manip% list, but whether any
global functions that its compiled image references are in
that list. This is the same approach that is used in
dump-compiled-objects. This fix is needed for correctly
recognizing defpackage as a package-manipulating form.
defpackage macro-expands to a let form which contains a call
to make-package. Testing whether let is in %package-manip% is
useless; of course it isn't, and the test overlooks
make-package.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* eval.c (eval_init): Register isecp intrinsic.
* lib.c (isecp): New function.
* lib.h (isecp): Declared.
* stdlib/compiler.tl (lambda-apply-transform,
dump-compiled-objects): Use isecp instead of isec, since the
actual intersection of symbols isn't needed, only whether it
exists.
* txr.1: Documented.
* stdlib/doc-syms.tl: Updated.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Commit 93edcde038209335122964432bd35dee0c2ecb04, made in
August 2021, accidentally removed the blank line after the
copyright header in most stdlib files.
stdlib{asm.tl, awk.tl, build.tl, compiler.tl, copy-file.tl,
debugger.tl, doloop.tl, each-prod.tl, error.tl, except.tl,
ffi.tl, getopts.tl, getput.tl, hash.tl, ifa.tl, match.tl,
op.tl, package.tl, param.tl, path-test.tl, pic.tl, place.tl,
pmac.tl, quips.tl, save-exe.tl, socket.tl, stream-wrap.tl,
tagbody.tl, termios.tl, trace.tl, txr-case.tl, type.tl,
vm-param.tl, with-resources.tl, with-stream.tl, yield.tl}:
Ensure there is a blank line after the copyright header.
|
|
|
|
|
|
|
|
| |
* stdlib/compiler.tl (compiler compile): Handle typep symbol
via comp-typep method.
(compiler comp-typep): New method. This recognizes some
absolute truths: every object is of type t, and no object is
of type nil.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
*LICENSE, LICENSE-CYG, METALICENSE, Makefile, alloca.h,
args.c, args.h, arith.c, arith.h, buf.c, buf.h, cadr.c,
cadr.h, chksum.c, chksum.h, chksums/crc32.c, chksums/crc32.h,
combi.c, combi.h, configure, debug.c, debug.h, eval.c, eval.h,
ffi.c, ffi.h, filter.c, filter.h, ftw.c, ftw.h, gc.c, gc.h,
glob.c, glob.h, hash.c, hash.h, itypes.c, itypes.h, jmp.S,
lex.yy.c.shipped, lib.c, lib.h, linenoise/linenoise.c,
linenoise/linenoise.h, lisplib.c, lisplib.h, match.c, match.h,
parser.c, parser.h, parser.l, parser.y, protsym.c, psquare.h,
rand.c, rand.h, regex.c, regex.h, signal.c, signal.h,
socket.c, socket.h, stdlib/arith-each.tl, stdlib/asm.tl,
stdlib/awk.tl, stdlib/build.tl, stdlib/cadr.tl,
stdlib/compiler.tl, stdlib/constfun.tl, stdlib/conv.tl,
stdlib/copy-file.tl, stdlib/debugger.tl, stdlib/defset.tl,
stdlib/doloop.tl, stdlib/each-prod.tl, stdlib/error.tl,
stdlib/except.tl, stdlib/ffi.tl, stdlib/getopts.tl,
stdlib/getput.tl, stdlib/hash.tl, stdlib/ifa.tl,
stdlib/keyparams.tl, stdlib/match.tl, stdlib/op.tl,
stdlib/optimize.tl, stdlib/package.tl, stdlib/param.tl,
stdlib/path-test.tl, stdlib/pic.tl, stdlib/place.tl,
stdlib/pmac.tl, stdlib/quips.tl, stdlib/save-exe.tl,
stdlib/socket.tl, stdlib/stream-wrap.tl, stdlib/struct.tl,
stdlib/tagbody.tl, stdlib/termios.tl, stdlib/trace.tl,
stdlib/txr-case.tl, stdlib/type.tl, stdlib/vm-param.tl,
stdlib/with-resources.tl, stdlib/with-stream.tl,
stdlib/yield.tl, stream.c, stream.h, struct.c, struct.h,
strudel.c, strudel.h, sysif.c, sysif.h, syslog.c, syslog.h,
termios.c, termios.h, time.c, time.h, tree.c, tree.h, txr.1,
txr.c, txr.h, unwind.c, unwind.h, utf8.c, utf8.h, vm.c, vm.h,
vmop.h, win/cleansvg.txr, y.tab.c.shipped: Copyright year
bumped to 2022.
|
|
|
|
|
|
|
|
|
|
| |
* 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
|
|
|
|
|
| |
* 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* 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/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.
|
|
|
|
|
| |
* stdlib/compiler.tl (compiler comp-fun-form): Reduce
single-argument logior and logand calls to just the argument.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
At optimization level 2 or higher, an issue occurs whereby
code generation exhibits instabilities. The same code is
compiled slightly differently (but not incorrectly) depending
on irrelevant circumstances, due to some different registers
being used.
* stdlib/compiler.tl (compiler eliminate-frame): Do not free
the newly allocated t-registers inside a dohash loop.
We have a separate list of them in order; just hand that off
to free-tregs. The dohash loop is not ordered, because it
traverses a hash, which is keyed by object identities; i.e.
machine addresses assigned by memory allocation.
|
|
|
|
|
|
|
|
|
|
|
| |
The open-compile-streams function was calling trim-right with the
arguments in the wrong order, resulting in an output path equal
to the suffix of the input path.
Regression introduced in 8d8fee2e506806d9c117b17432ef3a5ec0d6f457.
* stdlib/compiler.tl (open-compile-streams): Swap in-path and
suff arguments in trim-right call.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* Makefile, alloca.h, args.c, args.h, arith.c, arith.h, buf.c,
buf.h, chksum.c, chksum.h, chksums/crc32.c, chksums/crc32.h,
combi.c, combi.h, debug.c, debug.h, eval.c, eval.h, ffi.c,
ffi.h, filter.c, filter.h, ftw.c, ftw.h, gc.c, gc.h, glob.c,
glob.h, hash.c, hash.h, itypes.c, itypes.h, jmp.S, lib.c,
lib.h, lisplib.c, lisplib.h, match.c, match.h, parser.c,
parser.h, parser.l, parser.y, rand.c, rand.h, regex.c,
regex.h, signal.c, signal.h, socket.c, socket.h,
stdlib/asm.tl, stdlib/awk.tl, stdlib/build.tl,
stdlib/compiler.tl, stdlib/constfun.tl, stdlib/conv.tl,
stdlib/copy-file.tl, stdlib/debugger.tl, stdlib/defset.tl,
stdlib/doloop.tl, stdlib/each-prod.tl, stdlib/error.tl,
stdlib/except.tl, stdlib/ffi.tl, stdlib/getopts.tl,
stdlib/getput.tl, stdlib/hash.tl, stdlib/ifa.tl,
stdlib/keyparams.tl, stdlib/match.tl, stdlib/op.tl,
stdlib/optimize.tl, stdlib/package.tl, stdlib/param.tl,
stdlib/path-test.tl, stdlib/pic.tl, stdlib/place.tl,
stdlib/pmac.tl, stdlib/quips.tl, stdlib/save-exe.tl,
stdlib/socket.tl, stdlib/stream-wrap.tl, stdlib/struct.tl,
stdlib/tagbody.tl, stdlib/termios.tl, stdlib/trace.tl,
stdlib/txr-case.tl, stdlib/type.tl, stdlib/vm-param.tl,
stdlib/with-resources.tl, stdlib/with-stream.tl,
stdlib/yield.tl, stream.c, stream.h, struct.c, struct.h,
strudel.c, strudel.h, sysif.c, sysif.h, syslog.c, syslog.h,
termios.c, termios.h, time.c, time.h, tree.c, tree.h, txr.c,
txr.h, unwind.c, unwind.h, utf8.c, utf8.h, vm.c, vm.h, vmop.h:
License reformatted.
* lex.yy.c.shipped, y.tab.c.shipped, y.tab.h.shipped: Updated.
|
|
|
|
|
| |
* stdlib/compiler.tl (open-compile-streams): If in-path end in
a path separator character, throw an error.
|
|
|
|
|
| |
* compiler.tl (open-compile-streams): Use path-cat instead of
quasiliteral.
|
|
|
|
|
|
|
|
|
| |
* stdlib/compiler.tl (open-compile-streams): Replace usage of
%file-suff-rx% with a call to short-suffix.
Streamline (subjectively) the obtention of ip-nosuff. Replace
calls to ends-with with a casequal on the suffix. Actually make
use of ip-nosuff.
(%file-suff-rx%): Delete (now unused) variable.
|
|
|
|
|
|
|
| |
* stdlib/compiler.tl (lambda-apply-transform): When processing
optional argument from apply-list, push an entry into
check-opts, so the fixup code is generated for it.
New test cases pass now.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The compiler test case fails on cases which pass a : value to
an optional argument, which is supposed to trigger defaulting.
* stdlib/compiler.tl (lambda-apply-transform): When processing
an optional argument, if the argument is other than a constant
expression evaluating to the : symbol, add an entry into a
new check-opts list. This is later traversed to add code
before the lambda body to check the optional parmeters for :
values and do the init-form processing. The test cases pass
with this, but it needs to be done in the case when optional
values come from the apply list also; this is not being
tested.
|
|
|
|
|
| |
* stdlib/compiler.tl (lambda-apply-transform): In one case,
the add call is missing to actually emit the rest parameter.
|
|
|
|
|
|
|
|
| |
* stdlib/compiler.tl (lambda-apply-transform): There is a bug
in the case when all required and optional parameters have
been satisfied. In the sub-case when there are no fixed
parameters, we need to handle the run-time situation of there
being a non-empty apply list, but no rest variable.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
When we compile an immediately applied lambda like
(apply (lambda (a b c) ...) list), we are not emitting code to
handle the run-time situation when there are too many elements
in the list. This shows up in exception handling, for
instance; the compiled version of this executes silently and
returns 42:
(catch (throw 'foo 5) (foo () 42)))
foo is must have a parameter to capture the 5; interpreted
code enforces this.
* stdlib/compiler.tl (lambda-apply-transform): In the
apply-list case, at the end of binding all the optional
arguments, if the parameter list doesn't have a rest
parameter to take any remaining items, we emit code to check
that there aren't any; that everything has been popped out of
the apply list, leaving it nil. If not, we call the
run-time support function lambda-excess-apply-list.
* stdlib/error.tl (lambda-excess-apply-list): New function.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This patch improves the constantp function dramatically. It
now performs a full expansion of its argument, and recognizes
all of the constant foldable functions that the compiler
recognizes.
* eval.c (const_foldable_s): New symbol variable.
(const_foldable_hash): New static variable.
(constantp_noex): Look up function in the hash table of const
foldable functions, including in the case when it appears in a
dwim form as in [+ 2 2] which is (dwim + 2 2). In this case,
recursively check the arguments for constantp_noex.
We get the hash table of foldable functions from the
sys:%const-foldable% variable, which comes from an autoloaded
module.
(constantp): Fully expand the input form, not just m
macroexpand.
(eval_init): Register the const_foldable_s variable.
* lisplib.c (constfun_instantiate, constfun_set_entries): New
static functions.
(lisplib_init): Register auto-loading of constfun module
via new static functions.
* stdlib/compiler.tl; Load the constfun module if
%const-foldable% is not defined.
(%const-foldable-funs%, %const-foldable%): Removed from here.
* stdlib/constfun.tl: New file.
(%const-foldable-funs%, %const-foldable%): Moved here.
* txr.1: Documented changes to constantp.
|