SirWumpus / post4

Post4 is an indirect threaded Forth dialect written in C.
Other
4 stars 1 forks source link

Unexpected stack underflow errors #18

Closed ruv closed 1 week ago

ruv commented 4 weeks ago

Commands in shell:

echo -e " ' quit catch \n abort" | ./post4
# output:
# -45 thrown: floating-point stack underflow

echo -e " ' quit catch \n -1 throw" | ./post4
# output:
# -4 thrown: stack underflow

echo " '^' parse abort ^   ' evaluate catch . " | ./post4
# output
# -6 thrown: return stack underflow
# double free or corruption (out)
# fish: Process 29943, './post4' 'echo " '^' parse abort ^   ' ev…' terminated by signal SIGABRT (Abort)

Commands in Forth:

s" -1 throw "  ' evaluate  catch .
\ output:
\ -6 thrown: return stack underflow
SirWumpus commented 4 weeks ago

Also another simple case leaves a junk value on the stack

elf$ ./post4
ok .s -1 ' throw catch .s
ds
ds
top-01 $00007d5794262298 $ffffffffffffffff
ok^D
elf$
SirWumpus commented 4 weeks ago

Also another simple case leaves a junk value on the stack

elf$ ./post4
ok .s -1 ' throw catch .s
ds
ds
top-01 $00007d5794262298 $ffffffffffffffff
ok^D
elf$

Ignore this one. Its not suppose to work. Doh.

ruv commented 3 weeks ago
.s -1 ' throw catch .s
\ output:
\ ds
\ ds
\ top-01 $00007d5794262298 $ffffffffffffffff

This is correct.

When catch returns a nonzero value at the top, the stack depth has the same value as just before catch was executed. It means that instead of the stack parameters that have been consumed by the called xt some random parameters can appear on the stack.

The following phrase:

  any-forth-program  ( i*x xt ) depth >r  catch  depth r> =   ( j*x 0 false | i*x 0 true | i*x ior true )

always returns true at the top when catch returns a nonzero value on the top.

This choice was made because catch does not know the stack effect (the number of input stack parameters) of the xt it executes.

In this example:

-1 ' throw ( n1 xt )
catch ( x2 n1 )

throw (that is called by catch) consumes n1 (and nothing produces). When the stack depth is restored (since n1 is nonzero), some unspecified value x2 appears on the stack. After that the throw code n1 is placed on the stack.

NB: in the stack notation, i*x and j*x are data type symbols. They do not identify any particular value.

ruv commented 3 weeks ago
echo -e " ' quit catch \n abort" | ./post4
# output:
# -45 thrown: floating-point stack underflow

How quit is called — directly, via another definition, via execute, via catch — shall not change anything.

The output of the above command shall be the same as the output of:

echo -e " ' quit execute \n abort" | ./post4
SirWumpus commented 3 weeks ago

Thanks. I figured that out, but your explanation expresses that more clearly.

-1 ' throw ( n1 xt )
catch ( x2 n1 )

So is this, or some variant, worth adding to the test suite?

ruv commented 3 weeks ago

So is this, or some variant, worth adding to the test suite?

There is no such thing as too many tests 😉

t{ -1 ' throw catch nip -> -1 }t
t{  1 2 :noname ( x x -- ⊥ ) 2drop abort ; catch nip nip -> -1 }t
SirWumpus commented 3 weeks ago

Progress Notes

These are more for myself in order to keep track of things. One thing you (@ruv) should like is the refactoring of SETJMP() out of p4Repl() above.

Passes but some how breaks the test suite ...

Exceptions 
..
CATCH basic 
..
CATCH THROW 
.............
-56 THROW 
.
>> test_group_end
>>               ^
make[1]: *** [makefile:64: test] Segmentation fault (core dumped)
make[1]: Leaving directory '/home/achowe/git/post4/test'
make: *** [makefile:95: test] Error 2
roche$ 

Without the new test added, Valgrind reports no leaks.

SirWumpus commented 3 weeks ago

Could you provide an example of incorrect reading/writing the return stack?

With the current _evaluate code ...

_evaluate:  w = P4_POP(ctx->ds);
        x = P4_POP(ctx->ds);
        rc = p4EvalString(ctx, x.s, w.u);
        NEXT;

rs_stack.0.log

rs_stack.1.log

So part of the issue with above is that the inner p4Repl() does not pop at end-of-string, but continues processing.

So my current efforts (beyond adding TRACE for this) been try jumping to _repl to remain in the same C frame. It almost works, but handling of piped vs interactive input and the second case fails.

So the solution looks to be a change in how p4Repl() transitions from C to Forth with an eye towards something more like QUIT in Forth as discussed in #20 and avoid nested calls to p4Repl(). This would address concerns with p4EvalFile(), p4EvalFp, and INCLUDE-FILE.

SirWumpus commented 2 weeks ago

@ruv the most recent commit is rather large and covers several issues. I hope this resolves the CATCH THROW issues. Note the internal refactoring of p4Repl() into a more Forth like QUIT, _interpret,REFILL loop (please comment in #20 for separation of issues). If you are happy with the CATCH THROW behaviour, see the extra tests in test/exceptions.p4 and the test/makefile.in then I would like to close this particular issue (and maybe #20 too).