LEAVE

Interpretation:

Interpretation semantics for this word are undefined.

Execution:

( -- ) ( R: loop-sys -- )

Discard the current loop control parameters. An ambiguous condition exists if they are unavailable. Continue execution immediately following the innermost syntactically enclosing DO...LOOP or DO...+LOOP.

See:

Rationale:

Note that LEAVE immediately exits the loop. No words following LEAVE within the loop will be executed. Typical use:

   : X ... DO ... IF ... LEAVE THEN ... LOOP ... ;

Testing:

T{ : GD5 123 SWAP 0 DO 
     I 4 > IF DROP 234 LEAVE THEN 
   LOOP ; -> }T

T{ 1 GD5 -> 123 }T
T{ 5 GD5 -> 123 }T
T{ 6 GD5 -> 234 }T

ContributeContributions

ruvavatar of ruv Stack effect of LEAVE during compilationRequest for clarification2021-04-04 10:35:03

May LEAVE be implemented in such a way that its compilation semantics have stack effect ( C: do-sys1 i*x -- do-sys2 i*x )?

For illustration, LEAVE is implemented in this way in my DO LOOP over BEGIN UNTIL proof of concept. This PoC also relies on variable size of data objects of do-sys data type, but LEAVE can be also implemented in such a way that the size of do-sys1 is equal to the size of do-sys2.

AntonErtlavatar of AntonErtl

Consider

: leave1 save-data-stack POSTPONE leave is-data-stack-same-as-saved ; immediate
: test ?do leave1 loop ;

So if your control-flow stack is on the data stack, a standard program can see if LEAVE changes control-flow stack items, so such a system would not conform to the standard. I don't see how to do such a check in a standard program for a separate control-flow stack, so you may be able to use such an approach there.

Alternative approaches: A classic technique (probably used by many systems) is to use the space for the target address of each leave branch to store a link to the previous LEAVE. Gforth stores more than fits there, so it uses a separate LEAVE stack.

ruvavatar of ruv

Yes, a program can see if LEAVE changes the control-flow stack items on they are on the data stack. But I don't sure that a standard program is allowed to rely on this change due to 3.1.5.1 System-compilation types:

These data types denote zero or more items on the control-flow stack (see 3.2.3.2). The possible presence of such items on the data stack means that any items already there shall be unavailable to a program until the control-flow-stack items are consumed.

Then your save-data-stack is allowed to access neither the data objects of system data types nor the items that were on the data stack before the system data types were placed on the control-flow stack.

It's obvious that when the compilation semantics for LEAVE are performed, the control flow stack should contain at least one do-sys: ( C: do-sys i*x ). Since LEAVE is connected with the innermost syntactically enclosing DO/?DOLOOP/+LOOP. And so it's connected with the topmost do-sys among several do-sys in the control-flow stack.

Then we can claim that in a standard system LEAVE compilation should have stack effect either ( C: do-sys i*x -- do-sys i*x ) or ( C: do-sys1 i*x -- do-sys2 i*x ).

By the current wording for LEAVE, it looks like if a system uses the second variant, then it should either use the separate control-flow stack, or the same size of do-sys1 and do-sys2. These limitations look irrelevant, superfluous, and actually they don't exist.

Another argument is that if the standard allows ( C: do-sys1 i*x -- do-sys2 i*x ) when the separate control flow stack is used, then it should allow the same when the control-flow stack is united with the data stack.

AntonErtlavatar of AntonErtl

This sentence in 3.1.5.1 is somewhat self-contradictory. The "means that" indicates that it describes a consequence of an earlier normative statement, while the "shall" indicates that the sentence is intended to be normative itself. However, I don't see a reason why the Forth-94 committee should have made such a normative restriction, so I lean towards the interpretation that it is intended as a description of a consequence of the fact that the size of system-compilation types is not known to standard programs. Of course, you can lean towards the normative interpretation.

But even with the normative interpretation, I don't think you can keep the information about all LEAVEs on the data stack. Consider:

: test ?do [ depth ] leave leave leave leave [ depth - . ] loop ;

Compiling this has to print -1.

StephenPelcavatar of StephenPelc

For use in a standard program is there any reason at all for LEAVE to modify a stack at compile time? And if you do so, do you change any entitlements? I think that you do, so my answer is that the behaviour is not permitted.

For use in a non-standard program, we do not need to care.

ruvavatar of ruv

Well, it's arguable question

For use in a standard program is there any reason at all for LEAVE to modify a stack at compile time?

I talk not abut a program, but about a standard system.

The reason for LEAVE to modify the control-flow stack (in compatible manner) is to simplify the implementation. For example, Gforth uses a separate stack solely for LEAVE. If LEAVE is allowed to modify the control-flow stack then some implementations can be simpler since no need for a separate stack.

Well, I would suggest a proposal to have ( C: do-sys1 i*x -- do-sys2 i*x ) for LEAVE compilation. Do you think such a system can break some programs? (except artificial examples like Anton's example above).

Having this stack effect in the specification, a system is allowed to throw an exception if the control-flow stack doesn't contain do-sys during compilation of LEAVE. At the moment, we don't have such ambiguous condition explicitly, but having this stack effect a system can rely on the clause "An ambiguous condition exists if an incorrectly typed data object is encountered" (from 3.1 Data types).

AntonErtlavatar of AntonErtl

I don't expect that there are production programs that would break on a system like you envision, but I could be wrong.

OTOH, the benefit to systems of such a change would be close to zero. All existing systems manage to implement LEAVE without this restriction. And if this restriction was standardized, we would not make use of it in Gforth, because it's simpler to implement a separate stack than to keep track of the latest do-sys in the data stack, make room for additional information by moving the closer-to-top stack iterms, and storing the leave information that.

LEAVE outside of a DO...LOOP is not a common problem, although I remember one user who intentionally did it because he thought that LEAVE would leave the dynamically enclosing DO...LOOP. One way to deal with that would be to let an unresolved LEAVE branch to an appropriate error throw (maybe "LEAVE unresolved (?DO ... LOOP missing)").

ruvavatar of ruv

it's simpler to implement a separate stack than to keep track of the latest do-sys in the data stack, make room for additional information by moving the closer-to-top stack iterms, and storing the leave information that.

Don't sure that a separate stack is simpler. The actual code is even less than your description:

: leave postpone 2r> cs-cnt n>r postpone ahead nr> drop ac+ ; immediate

And it's far less than the separate LEAVE-stack in cond.fs of Gforth.

A default branch for LEAVE — is an interesting solution. But it's a run-time error (and it leaks an item of the LEAVE-stack). Whereas a system could also raise a compilation-time error.

AntonErtlavatar of AntonErtl

On behalf of the committee:

Is such a LEAVE implementation standard-compliant? No.

Will this break any production programs? Most likely not.

Closed
Reply New Version