Closed WillClinger closed 7 years ago
Its quite possible that I made a mistake in my reading of:
http://www.ccs.neu.edu/home/lth/larceny/notes/note13-malcode.html
(Update: an earlier version of this comment had some mistakes its reasoning, namely a fencepost error in my thinking about what REGr
was supposed to denote.)
In particular, we have:
lambda x,n,doc
If n < r, loads RESULT with the procedure formed from the
code x and the values of registers REG0-REGn. If n >= r,
loads RESULT with the procedure formed from the values of
registers REG0-REG{r-1} and the first n-r+1 values taken
from the list in REGr. The code x consists of a bytevector
of pure code and a vector of constants. The documentation
is used only for debugging.
So, okay, there's a threshold value r
. That sounds like it could correspond to that magic number 31
in @WillClinger's description. From the text above, it seems like it should corresponds to the number of (software) registers on the target, since it talks about loading a list into REGr
, which would imply to me that r
should be 31
.
On further investigation, we find:
Implementation-specific parameters.
The highest numbered general register is REGr, where r = R-1.
See the end of this file for implementation notes, notably the
values of R, MINOFFSET, MAXOFFSET, MINIMM, MAXIMM, MAXSLOT, and MAXRIB.
So, hmm, the value of r
is actually R-1
according to the docs
But then what is R
at the bottom of the file:
Implementation notes.
Minimal parameter values.
R ? (Certainly at least 3, or op3 won't work)
...
SPARC implementation.
R 32
Standard-C implementation.
R 32
...
This makes it seem like R
is the number of software registers, while r
is R - 1
(i.e. the index of the final software register, i.e. the magic number 31).
(Second update: still more errors corrected.)
Anyway, looking again at the documentation for the lambda
macscheme instruction, note that the documented ranges are n < r
for the non-list case and n >= r
for the list case.
So that implies to me that IAssassin is following the documented semantics, even though that may not be what Twobit actually implements. (That was wrong.)
IAssassin is not following the documented semantics; it appears to be assuming that when n == r
that it should copy the value directly from REGr, rather than reading it as the first element of a list stored in REGr. That explains the semantics observed in this ticket: we are creating the singleton list and storing it in REGr, but then IAssassin is treating that list itself as the value of the final free variable.
(Incidentally, is there are good reason to use n < r
/n >= r
rather than n <= r
/n > r
? It seems like the latter semantics would avoid a cons in the case where n == r
... but maybe the former semantics has advantages elsewhere.)
(I would be curious to know if this same bug exhibits itself on the Petit/C or Petit/NASM backend, especially since the IAssassin backend was largely directly ported from the Petit/NASM backend, apart from a few opcodes that needed special treatment due to its non-Petit nature.)
Fixed by changeset 9241f06ec697cf729fee3eca212e84a4a02920e8
IAssassin was incorrect. Comparing the IAssassin code to the documentation is confusing because the documentation uses r
to mean R-1
, while the IAssassin code uses r
to mean something completely different: the operand n
of the $lambda
instruction.
The bug does not show up in Petit Larceny (Standard-C) on Linux. I haven't checked the Petit/NASM backend, so I'll leave this ticket open until that's been checked.
By the way, I ran across this bug while adding R7RS define-library
support to lib/R6RS/r6rs-expander.sch
. Adding or subtracting print statements in unexecuted code could cause an unrelated part of the expander to try to call a value cell instead of calling the procedure held within that cell.
So I suspected a compiler bug, but my test case contained over 2000 lines of code (excluding comments). I found the bug by examining a 26416-line listing of the generated MacScheme machine assembly code. That was kinda fun.
@WillClinger ah, I see where I went wrong in my analysis of the documentation above.
Felix wrote:
After all, is there are good reason to use
n < r
/n >= r
rather thann <= r
/n > r
? It seems like the latter semantics would avoid a cons in the case wheren == r
...
The reason is simplicity of code generation. With the semantics Lars and I documented in 1999, there are only two cases: n < r
and n >= r
. Avoiding one cons in the n = r
case would add a third case to some parts of Twobit. In particular: the code generated for procedure calls never passes a bare argument in the last register; for procedure calls with r=R-1
or more arguments, that last register always contains a list of arguments. The code generated for lambda expressions can therefore reuse the part of the code generator that evaluates arguments for a procedure call.
The Fence back-end gets this right, probably because it was forked off the SPARC back-end: src/asm/Fence/pass5p2.sch:emit-init-procedure-slots. (The ARM back-end, built on Fence, in any case only has hardware registers so the bug would have shown up in real code pretty quickly.)
The IAssassin code generation bug has been fixed, so leaving this ticket open is misleading. If Petit/Nasm has a similar bug, we should open a new ticket for that.
I'm afraid I'll forget to check, so I won't close this ticket right now. I am, however, downgrading it to minor and moving the milestone.
The IAssassin bug has been fixed, and issue #758 has been created to remind us to check for this bug in the Petit/Nasm version in case we go back to using that version, so I'm closing this.
In current IAssassin versions of Larceny:
evaluates to
When one more argument is added to both
foo
and to the lambda expression, the output is correct.This is a bug in the code generator, but I'm not sure whether it's a bug in Twobit or in the IAssassin back end. Twobit produces this MacScheme machine code:
Explanation:
With the current IAssassin (IA32) version of Larceny, the Twobit compiler generates code for an abstract MacScheme machine with 32 registers, of which 31 are used for arguments. The
lambda
instruction creates a procedure that closes over the values in some number of those registers. When closing over less than 31 values, the semantics is straightforward. When closing over 32 or more,reg31
holds a list of all values to close over past the first 30 (not counting the contents ofreg0
). When closing over exactly 31 registers, there are two reasonable semantics. Evidently Twobit is using one of the two reasonable semantics, but the IAssassin assembler is using the other.I don't know which is right. I'll have to consult the MacScheme machine documentation.