Closed ruv closed 1 week 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$
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.
.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.
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
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?
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
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.
When testing manually it appears all is good, except for possible leaks, ...
roche$ valgrind ./post4
==5615== Memcheck, a memory error detector
==5615== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==5615== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==5615== Command: ./post4
==5615==
ok s" -1 throw " ' evaluate catch .s
ds
top-02 $0000000004b6aa80 $ffffffffffffffff $ffffffffffffffff
ok bye
==5615==
==5615== HEAP SUMMARY:
==5615== in use at exit: 155,373 bytes in 689 blocks
==5615== total heap usage: 694 allocs, 5 frees, 162,046 bytes allocated
==5615==
==5615== LEAK SUMMARY:
==5615== definitely lost: 0 bytes in 0 blocks
==5615== indirectly lost: 0 bytes in 0 blocks
==5615== possibly lost: 1,168 bytes in 3 blocks
==5615== still reachable: 154,205 bytes in 686 blocks
==5615== suppressed: 0 bytes in 0 blocks
==5615== Rerun with --leak-check=full to see details of leaked memory
==5615==
==5615== For lists of detected and suppressed errors, rerun with: -s
==5615== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
roche$
Yet the above fixes fail when piped ...
roche$ printf 's" -1 throw " '' evaluate catch .s ' | ./post4
>>
>> ^
-6 thrown: return stack underflow
double free or corruption (out)
Aborted (core dumped)
roche$ printf 's" -1 throw " '' evaluate catch . ' | valgrind ./post4
==5587== Memcheck, a memory error detector
==5587== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==5587== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==5587== Command: ./post4
==5587==
==5587== Invalid read of size 8
==5587== at 0x4061BC: p4Repl (post4.c:2091)
==5587== by 0x4092A7: main (post4.c:3062)
==5587== Address 0x4b6a7f8 is 8 bytes before a block of size 544 alloc'd
==5587== at 0x484BF70: calloc (vg_replace_malloc.c:1595)
==5587== by 0x404476: p4CreateStack (post4.c:1166)
==5587== by 0x40468C: p4Create (post4.c:1211)
==5587== by 0x409191: main (post4.c:3047)
==5587==
==5587== Invalid write of size 8
==5587== at 0x404EC5: p4Repl (post4.c:1640)
==5587== by 0x4092A7: main (post4.c:3062)
==5587== Address 0x4b6a7f8 is 8 bytes before a block of size 544 alloc'd
==5587== at 0x484BF70: calloc (vg_replace_malloc.c:1595)
==5587== by 0x404476: p4CreateStack (post4.c:1166)
==5587== by 0x40468C: p4Create (post4.c:1211)
==5587== by 0x409191: main (post4.c:3047)
==5587==
>>
>> ^
-6 thrown: return stack underflow
==5587==
==5587== HEAP SUMMARY:
==5587== in use at exit: 0 bytes in 0 blocks
==5587== total heap usage: 694 allocs, 694 frees, 165,118 bytes allocated
==5587==
==5587== All heap blocks were freed -- no leaks are possible
==5587==
==5587== For lists of detected and suppressed errors, rerun with: -s
==5587== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
roche$
Adding the test to ../test/exceptions.p4
T{ S" -1 THROW " ' EVALUATE CATCH NIP NIP -> -1 }T
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.
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;
This command fails (also fails manually (input words then ^D to send EOF)). Observe how the return stack goes negative. This appears to be an artifact of the p4EvalString()/p4Repl()
nesting since p4Repl()
maintains its own local ip
. When the inner p4Repl()
pops at EOF, the outer one continues from an out-of-date ip
.
printf 'S" -1 THROW" '\'' EVALUATE CATCH .s' | ./post4
This works as expected.
printf 'S" 0 THROW" '\'' EVALUATE CATCH .s' | ./post4
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
.
@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).
Commands in shell:
Commands in Forth: