- Allow EVEX printer generation for vpmovsx* (#x20-#x25) and vpmovzx*
(#x30-#x35) in map 0F38 in avx2-inst-printer-list.
- Use xmmreg/mem for 4x/8x widening conversions and half-ymmreg/mem for 2x
widening conversions so source operands disassemble with their correct
architectural register width (XMM or YMM).
- Define evex-ymmreg-b to incorporate EVEX.X / B' (bit 4) for register-direct
operands in EVEX-ymm-ymm-imm format, correctly decoding registers 16-31 for
vpslldq, vpsrldq, and shift/rotate instructions.
- Widen register bounds checks in print-ymmreg-rm, print-ymmreg-vvvv,
print-ymmreg, and print-xmmreg from (unsigned-byte 4) to (mod 32).
- Add regression tests in tests/simd-pack-512.pure.lisp.
(incf x y) actually expands into (setq x (+ y x)), and
iteration-step-values only checks the first argument. Using
combination-match brings commutative matching automatically.
Then the set isn't reoptimized when y changes, again looking for the
first argument.
Which helps loops where the index wrapped around or transformed by
intermediate immutable variable bindings.
This particularly helps type inference for SSA form programs where
multiple sets correspond to fresh variable bindings that are related
through the bindings.
Which allows us to do bounds elimination for the attached test cases.
The constraint propagation code to handle general local calls now
resembles something like a cross between the code that handles LET and
SET, corresponding to the fact that local call binds variables as LET
does but also must clear existing information while propagating some
of the old information as SET does.
Assignment lambdas have first order control flow like let lambdas
do. So we can just do global constraint propagation around their
blocks like normal instead of having to reason about higher-order
control flow through local calls.
This speeds things up by not having to do conset bookkeeping in local
calls and stopping the leading blocks optimization. Possibly as a
consequence, it also seems to derive more constraints this way as
shown in the attached test case.
In any case, this is clearly how assignment lambdas should be
handled (by that I mean the same type propagation logic as with other
local call function kinds, but without needing higher order control
flow reasoning), so mark the old TODO item around assignment lambdas
as finished.
I asked Gemini to do the math on this, and it said that it's no surprise
a flake was seen with only 20 objects. Changing to 60 objects reduces the
chance of spurious failure to 1 in 5e-16
Optimize non-tail full calls followed by "easy" cleanups that do not execute
arbitrary forms. "Easy" includes DX allocations, special var unbinding, popping
the CATCH block stack. Then the unknown values can be directly returned as if
by tail call, bypassing the RETURN-MULTIPLE helper routine.
We annotate return LVAR as :pass-through, suppress register restoration
after the call (:save-p :compute-only), and emit return as LEAVE+RET.
Cleanup VOPs (unbind-n, %catch-breakup) must preserve the carry flag, and use
only clobberable temporary registers.
For CATCH, decouple the normal return path from the NLX path. The normal path
eliminates use of the stack for buffering the mv-save area and then restoring
it from the stack.
Teach environment analysis how to detect when functions which have
references in other functions (potentially itself) are automatically
stack allocatable. This is done by marrying the closure environment
analysis with some of the existing escape analysis utilities, and by
combining those analysis with a step to annotate the correct derived
dynamic extent lifetime, which further required some IR1 flow-graph
hacking utilities to revoke tail call semantics when
necessary. Additionally, the escape analysis performed for functions
is also now somewhat unified with how values are handled, so such
functions are now also stack allocated when all their references
deliver values to e.g. dx-safe functions. In fact, dx-safe as an
ir1-info annotation should probably just be removed in favor of
user-defined dx declarations.
We have thus deleted the arbitrary restriction on auto-dxification of
functions that made us just give up the moment a reference to a
potentially dxable function was in a different environment.
This fixes the existing expected test failures we had regarding being
able to auto stack allocate self-referential functions. We also add a
bunch of new tests to exercise stack-allocatability and correctness of
mutually referential functions which close over each other as well.
We can now remove some explicit dynamic extent declarations in the
system that are now automatically inferred by the compiler, since it
is safer that way in case the functions are changed. It may work to
remove more declarations but these are clearly the instances that the
compiler now handles.
Some test cases provided by Gemini. Everything else written by me.
While it could be argued that
(lambda (x)
(declare (optimize speed))
(declare (type (array * (4 4)) x))
(adjust-array x '(3 5))
(array-dimension (the (array t) x) 0))
should be able to return 4 (I think this is rather dubious though),
clearly it is not correct for the attached test case to be able to do
so. Because declared types do not know anything about variable
subsitution, do not try and squeeze more type information from a
declared type if there is more than one reference to the variable.
While it is unfortunate that a test had to be disabled as a worse type
is derived, I don't see how to safely derive a better type without
making other cases incorrect. This could be fixed as a follow-up.
Notably
(lambda (x)
(declare (type (or null (cons fixnum)) x))
(setf (car x) 'foo)
(if x
(+ (car x) 4)
0))
simply crashes without this fix and is not safe, which is not good
even if it's undefined behavior. The corresponding test case with a
variable substitution is not only unsafe but incorrect despite being
defined behavior.
Fixes lp#2165835.
We were sometimes merging dynamic extent lifetimes in such a way that
the stack lvar of a dynamic extent later in the flowgraph was used
instead of the earliest one. Fix this by checking the state of the
stack and making sure that dynamic extents always end up taking on the
stack lvars of dynamic extents that happen before them in the
flowgraph when merging their lifetimes.
Fix found with assistance from Gemini Pro after much heavy hinting
from me. Extended commentary and test case reduction by me.
Fixes lp#2156347.
Instead of waiting for environment analysis to scan set nodes for
dxable values, we can just create a dynamic extent per lambda and
associate it with the value of a SET during IR1
translation. Environment analysis can then run as normal and only
needs to worry about checking whether the environments of the value
and the dynamic extent are the same.
Doing so provides the following benefits (notably fixing a bug):
* We make handling of DX and sets more uniform, so that we no longer
need to handle a case where let conversion has happened or not. This
has the following benefits:
** Assignment lambdas don't confuse DX anymore. This fixes
lp#2163043.
** Flushed initial values don't cause pessimization anymore, fixing a
FIXME.
* It is now straightforward/more clear how to make the compiler
handle free dynamic-extent declarations.
* We can optimize or transform away SET nodes (think copy propagation
or SSA type optimizations) during IR1 optimization without worrying about additional DX
bookkeeping.
Also, revise some commentary about dynamic extent handling across
locall and envanal.
The optimization of not scrubbing the part of the control stack that you
think you don't have to scrub is contrary to the requirement of some tests.
Real use would not often care. As mentioned in the prior commit, the
scrubbing logic was actually deficient going back nearly 15 years,
so the new logic is merely differently deficient but geared toward avoiding
touching all memory of all stacks of all threads on every GC.
Create a dedicated area of 'struct thread' to return multiple-values in excess
of the arg-passing registers. Gating feature is :TLS-BASED-MV-RETURN and not
enabled by default. The limit of 64 (inclusive) values is the minimum to get
most existing tests to pass unchanged. After the optimizations outlined below
are done, the feature will be more compelling, and we can see about making it
opt-out rather than opt-in, or making MULTIPLE-VALUES-LIMIT configurable.
(I'm sure that some user will claim to need 1000 values.)
The benefit will be fully realized in some subsequent changes, namely:
freeing a DX alloc, unbinding a special var, and breaking up a catch block
will all return from their frame without invoking RETURN-MULTIPLE. Similarly,
some "simple" unwind-protect forms such as release-mutex could too- anything
that does not clobber the thread-local area during a cleanup.
Also this alleviates a big impediment to creating an LLVM-based backend
which could explore performance differences among the call conventions
offered, since micro-management of the stack becomes less critical.
Co-authored with Antigravity