6.1.1310 ELSE CORE

Interpretation:

Interpretation semantics for this word are undefined.

Compilation:

( C: orig1 -- orig2 )

Put the location of a new unresolved forward reference orig2 onto the control flow stack. Append the run-time semantics given below to the current definition. The semantics will be incomplete until orig2 is resolved (e.g., by THEN). Resolve the forward reference orig1 using the location following the appended run-time semantics.

Run-time:

( -- )

Continue execution at the location given by the resolution of orig2.

See:

Rationale:

Typical use: : X ... test IF ... ELSE ... THEN ;

Testing:

ContributeContributions

mykesxavatar of mykesx [357] Resolve the forward reference orig1 using the location following the appended run-time semantics.Comment2024-08-10 21:49:59

Seems to me that ELSE wastes a BRANCH and its target/offset. ELSE compiles this branch so THEN can have a branch target to patch at compile time.

This branch and offset might be 16 bytes wasted for a 64 bit Forth. Wasted for each ELSE,

If you keep an 8 byte stack of if/else/then state the extra branch can be eliminated.

IF pushes a token representing IF on this stack. ELSE will patch the branch added to the dictionary by IF. ELSE pops the IF token and pushes an ELSE token. THEN pops the token and if it’s an ELSE token, it doesn’t need to patch anything at run time.

ruvavatar of ruv

Seems to me that ELSE wastes a BRANCH and its target/offset.

The specification does not dictate any particular implementation, only the behavior that a standard program can observe (or depends on).

You can even resolve references in run-time (and/or use something like a computed goto under the hood), as long as it does not affect the behavior of a standard program.

AntonErtlavatar of AntonErtl

The specification of else is in terms of two origs. These can be cs-rolled during compilation (and sometimes the cs-roll is implicit). An example is

begin ... while ... while ... repeat ... else ... then

Maybe it's possible to still implement something along the lines of mykesx' idea by having one stack item on the additional stack per control-flow stack item and then perform a roll of that stack for every cs-roll during compilation. One would also have to care for cleaning up this run-time stack on exit and throw, and doing the manipulations of the additional stack even when jumping across some control-flow word. Overall this looks complicated and fraught with pitfalls. If you want to save program memory on a 64-bit machine (why?), use native-code compilation; native-code jumps typically cost 2-5 bytes on AMD64.

mykesxavatar of mykesx

Am I not reading the paragraph for compilation correctly?

It talks about then patching the branch added by else…

ruvavatar of ruv

It talks about then patching the branch added by else

The spec does not use the term "patching". Some Forth systems employ two-pass compilation of a definition (it is allowed), without any patching. If you patch a branch — it's your implementation detail.

Anyway, we can forget about else for a moment. The following shall work without errors:

: bool-to-n ( x -- 1|0 ) if 1 ahead [ 1 cs-roll ] then 0 then ;

t{ false bool-to-n -> 0 }t
t{ true  bool-to-n -> 1 }t

And the Forth system is allowed to compile the above definition to a code that does not contain any branches at all, for example to a code like this:

: bool-to-n ( x -- 1|0 ) [: 1 ;] [: 0 ;] ifelse ;

Where ifelse does not perform any jump/branch, but only an indirect call:

: ifelse ( i*x  x.bool  xt.on-true xt.on-false -- j*x )
  rot 0= tuck and ( xt.on-true flag xt.on-false|0 )
  -rot 0= and ( xt.on-false|0 xt.on-true|0 )
  or execute
;
Reply New Version

ruvavatar of ruv [358] Example implementation for `ELSE`Suggested reference implementation2024-08-11 10:13:40

: ELSE ( C: orig1 -- orig2 )
  POSTPONE AHEAD  1 CS-ROLL  POSTPONE THEN
; IMMEDIATE
Reply New Version