Jack Language if/while statement semantic question

classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

Jack Language if/while statement semantic question

cadet1620
Administrator
The semantics of the if/while statement's logical test are not well specified in the book, and the implementation is inconsistent in JackCompiler (v2.5).

I discovered that my compiler and JackCompiler were not generating equivalent code for while statements when I compiled my Memory.jack with JackCompiler and it failed. (I developed it using my compiler.)

The problem was my linked list traversal:

    let block = freeList;
    while (block) {
        ...
        let block = block[0];
    }
I've written this same code so many times, in so many languages, that it was interesting that it failed. I did some investigating.

What's happening is that JackCompiler generates:

    push block
    not
    if-goto break-label
If block is a non-null pointer one expects the while body to execute, but it does not because not is bitwise, not logical. For "if (test)", JackCompiler generates:
    push test
    if-goto true-label
    goto false-label
    label true-label
Which works as expected.

 

So my question is: What are the correct semantics for if/while statements in Jack?

  • 8.2.1 specifies for VM command if-goto: "if the value is not zero" which implies the the Jack if/while should behave likewise,
  • figure 9.8 specifies "Typical if statement" and "Typical while statement",
  • figure 11.5 shows emitted code for if and while as "VM code for computing ~(cond)"
[Alas, I've been in the industry long enough to know that "The source code/tools are the documentation." — I've rewritten my loop as "while (~(block=0))" so that it will work with JackCompiler.]

None of the test programs turn up this subtlety. Here's one that will:

    /** Test if/while statements with integer conditions. */    
    class Main {
        function void main() {
            var int test;
            var boolean fail;

            let test = 1;
            if (test) {
                do Output.printString("if(1) tested True"); }
            else {
                do Output.printString("ERROR: if(1) tested False"); }
            do Output.println();

            let fail = true;
            let test = 1;
            while (test) {
                do Output.printString("while(1) tested True");
                let fail = false;
                let test = 0;
            }
            if (fail) {
                do Output.printString("ERROR: while(1) tested False"); }
            
            return;
        }
    }
--Mark
Reply | Threaded
Open this post in threaded view
|

Re: Jack Language if/while statement semantic question

The111
I found this out recently too.  In short, the supplied compiler treats if(exp) as if(true) as long as exp is non-zero.  But with while(exp), it is only while(true) if exp is -1.

My compiler actually ended up doing the same thing, but only because while developing my compiler, I intentionally compared my output .vm files to .vm files compiled by the supplied compiler, and kept tweaking my compiler until it produced identical .vm files.  I figured that was the easiest way to get my compiler working properly, even if it was a little cheap. ;-)

Note: "true" as I use it above is in the logical pseudocode sense, nothing to do with Hack binary.
jrd
Reply | Threaded
Open this post in threaded view
|

Re: Jack Language if/while statement semantic question

jrd
Responding to Mark's original question above and noting that I've been using the same secondary error-checking procedure as The111 (i.e.,  ...compared my output .vm files to .vm files compiled by the supplied compiler, and kept tweaking my compiler until it produced identical .vm files.), I note that the supplied JackCompiler seems to produce the following generate code format for If statements:

if (cond)
                s1
else
                s2


JackCompiler generated code:

    VM code for computing (cond)
    if-goto L1
    goto L2
label L1
    VM code for executing s1
    goto L3
label L2
    VM code for executing S2
label L3
....

Note the JackCompiler generated code is different in two respects from the sample If statement generated code that is given on p. 233 of the Nand2Tetris book.  Firstly, the JackCompiler tests for a positive cond (as opposed to ~(cond) as the book directs).  Secondly, the JackCompiler uses 3 labels (i.e., L1, L2, L3) to implement the routine - whereas the book only uses 2 labels for this routine.

Separately, I note that the JackCompiler generated code seems to agree identically with the While statement generated code that is given on p. 233 of the Nand2Tetris book.  So, with respect to While statements, I do NOT notice the same discrepancy or issue.

Note that all of the above was discovered while working with the convertToBin program - where you can unit test the if/while statements, each in isolated subroutines.

* So, my question is which generated code structure should I ultimately use in my compiler for my If statements?  Should I defer to the code outputted by the actual supplied JackCompiler (noting Mark's comment above " "The source code/tools are the documentation.") or should I instead use the version provided by p.233 of the book (which will result in different code when comparing to the JackCompiler)?  Or, have I possibly made a mistake altogether - which is why my generated code has discrepancies?

Any thoughts please?  Thx.

- JRD
Reply | Threaded
Open this post in threaded view
|

Re: Jack Language if/while statement semantic question

cadet1620
Administrator
I don't think that there is a "correct" answer.

If you are using comparison against the generated VM as a correctness test, then you should match JackCompiler's behavior.

I chose to write my compiler to adhere to the semantics I'm used to. A Java programmer would be used to if and while requiring that the test argument be a boolean and might choose to follow those semantics.

What I suspect happened was that the original developer was fluent in Java (since the tools are written in Java), and a later, less fluent, developer noticed that if statements were not behaving in the way he expected—execute the block if the argument is non-zero—and fixed them. But he didn't realize that the same bug was in while statements, so they weren't fixed.

I also suspect that the "bug fix" didn't generate an entry in the book's edit list since that would likely have caused somebody to notice that while statements also needed fixing.

--Mark
jrd
Reply | Threaded
Open this post in threaded view
|

Re: Jack Language if/while statement semantic question

jrd
Understood and info helpful.

BTW - While we're on the subject of differences between the book text and the supplied JackCompiler outputted code, I notice that there's also a discrepancy in how a boolean "true" is implemented.

On p. 235 of the book, the text states that "true" is mapped to the constant -1, by pushing constant 1 and negating it (i.e., push constant 1, neg).

However, in practice, the Jack compiler seems to be coding "true" also as constant -1, but instead by pushing constant 0 and then calling the bitwise not command (i.e., push constant 0, not).

Do they both yield same result/is there any difference in implementations?  Any reason to prefer one over the other?

- JRD
Reply | Threaded
Open this post in threaded view
|

Re: Jack Language if/while statement semantic question

cadet1620
Administrator
Both "1, neg" and "0, not" result in the same binary value, so there's no inherent reason to choose one over the other.

I prefer "0, not". It makes more sense to me to use logical operators to make logical constants.

--Mark