Krishna Myneni <
krishna...@ccreweb.org> writes:
>On 10/13/22 10:30, Anton Ertl wrote:
>> If SET-OPTIMIZER would not change COMPILE,, it would not work (it
>> would fail to set the optimizer). For Gforth's current
>> implementation, if SET-COMPSEM did not change NAME>COMPILE, it would
>> not work (it would fail to change the compilation semantics).
>>
>
>In the case of standard "COMPILE," the problem is that on a dual-xt
>system, "COMPILE," operating on an xt is a fundamentally incorrect way
>of compiling a named definition (word).
If you mean that, e.g.,
: [compile,] compile, ; immediate
: foo [ ' s" ] [compile,] bla" ;
is not (and must not be) equivalent to
: foo s" bla" ;
i.e., performing the compilation semantics of S", you are correct.
It's also incorrect for performing the compilation semantics of
: bar ." bar" ; immediate
But
: foo1 [ ' bar ] [compile,] ;
appends the execution=interpretation=compilation semantics to foo1, so
it is equivalent to
: foo1 postpone bar ;
Ticking named words is unambiguous for most named words, and
COMPILE,ing the resulting xt is just as standard as EXECUTEing it.
>I understand
>your "hack" in Gforth is for the purpose of maintaining backwards
>compatibility.
I don't know what you mean with '"hack"'. Forth has COMPILE, for
appending semantics to the current definition, and NAME>COMPILE for
getting the compilation semantics of a named word. These are two
different jobs, and Gforth provides SET-OPTIMIZER and SET->COMP,
respectively, to provide implementations for these words for the
current definition. And that's the right way to do it.
Because one often follows the other, there have been attempts to use
one mechanism for both purposes:
* It is incorrect to use SET-OPTIMIZER for changing the compilation
semantics.
* If you use SET->COMP for changing the code generation, you don't
cover the cases that COMPILE, xts directly instead of going through
NAME>COMPILE, and you have to provide a fallback mechanism for
COMPILE,ing that; and if you implement [COMPILE], you also have to
tell it which uses of SET->COMP change the default compilation
semantics and which don't. You may think that you are saving
complexity by eliminating SET-OPTIMIZER, but the complexity pops up
elsewhere.
I think that providing both mechanisms is a good solution, because it
also provides a nice separation of concerns: If you want to change the
generated code without changing the semantics (i.e., if you want to
optimize), use SET-OPTIMIZER; if you want to change the compilation
semantics, use SET->COMP (or, by extension, SET-COMPSEM).
It also gives different results for your dependency-tracking (which is
outside semantics, so you can use either mechanism). Consider:
: bar ." bar" ; track-deps
: foo postpone bar postpone bar ; immediate
: bla foo foo ;
: blub [ ' bar compile, ] ;
If you track dependencies throug NAME>COMPILE as mechanism, you will
see that BAR is used twice in FOO. If you track dependencies using
"COMPILE,", you will see that BAR is used four times in BLA and one
time in BLUB.
>> You just have to break the cycle, in the
>> present case by explicitly performing the old action of NAME>COMPILE
>> instead of just calling NAME>COMPILE.
>>
>
>Does SET-COMPSEM erase all memory of the original compilation semantics,
>or is the old xt-comp still available? If not, we should be able to fix
>this.
The compilation semantics is specified by the >HM>COMP field (which
contains the xt performing NAME>COMPILE for the word), but may access
other parts of the word. SET-COMPSEM changes the >HM>COMP field, but
not the other parts of the word. Since you do not change the other
parts of the word, you could just remember the old contents of
>HM>COMP and execute that; that's what the untested version I posted
earlier does:
: track-deps ( -- )
latestnt dup track-dependency ( nt-track )
dup >namehm @ >hm>comp @ ( nt-track old-name>comp )
[d:d swap >r execute r> latestnt add-dependency ;] set->comp ;
Here I directly called SET->COMP instead of going through SET-COMPSEM.
Benefits: No additional closure from SET-COMPSEM (saving memory), and
it works correctly with [COMPILE].
>Ok, I see that there is a problem with using NAME>COMPILE within the
>the closure's xt, passed to SET-COMPSEM.
>
>NAME>COMPILE should be used outside the closure to obtain the
>compilation semantics at the time that TRACK-DEPS was invoked. Thus a
>permanent data structure, 3 cells long, may be passed into the closure.
>The data structure contains the nt of the word, and the original
>compilation semantics, which consists of two cells of data: x xt. Then
>the closure can invoke both the original compilation semantics and
>append the extra actions needed to add information to the dependency table.
>
>Here's my new version of TRACK-DEPS. It invokes NAME>COMPILE outside of
>the closure to obtain and store the current compilation semantics for
>the word before SET-COMPSEM is invoked. It should satisfy all cases,
>including preserving any extensions to the compilation semantics prior
>to invoking TRACK-DEPS. I've tested it on the single-xt+immediate flag
>cases. So it appears that the previous xt-comp is still valid.
>
>: track-deps ( -- )
> latestnt dup track-dependency ( -- nt-track)
> dup name>compile ( -- nt-track x xt )
> 3 cells allocate drop \ reserve memory for old compsem
> dup >r 2! r@ 2 cells + ! r>
> [n:d dup 2@ execute 2 cells + a@ latestnt add-dependency ;]
> set-compsem ;
Another way to do that is to simply pass the three values to the
closure. Gforth does not provide pure-stack closure construction
words for that, but unless you are a locals-hater, you may prefer the
following (untested):
: track-deps ( -- )
latestnt dup track-dependency ( nt-track)
dup name>compile ( nt-track x xt )
[{: nt-track x xt :}d x xt execute nt-track latestnt add-dependency ;]
set-compsem ;
or alternatively, turning xt into a defer-flavoured local:
: track-deps ( -- )
latestnt dup track-dependency ( nt-track)
dup name>compile ( nt-track x xt )
[{: nt-track x xt: xt :}d x xt nt-track latestnt add-dependency ;]
set-compsem ;