• More thoughts on SET-OPTIMIZER and Forth systems

    From Anton Ertl@21:1/5 to All on Fri Nov 25 16:19:02 2022
    For those who have not followed recent discussions: If you define a
    word FOO and then invoke SET-OPTIMIZER ( xt -- ), then COMPILE,ing FOO
    will invoke the xt; i.e. SET-OPTIMIZER changes what COMPILE, executes.
    The only correct way to use SET-OPTIMIZER is to pass an xt that does
    not change the behaviour, only the implementation of the prescribed
    behaviour of COMPILE,. One point of discussion has been that the
    behaviour of a word is now described twice, once for EXECUTE and once
    for COMPILE,; this redundancy leads to a potential for bugs, so we
    discussed how to eliminate it.

    While thinking about this issue, I realized that the whole issue is
    quite different for different Forth systems:

    * Gforth compiles all primitives with PEEPHOLE-COMPILE, (which has a
    lot of machinery behind it) and uses SET-OPTIMIZER primarily for
    dealing with different word types; there are 18 occurrences of
    SET-OPTIMIZER in the Gforth image, and most of them are for handling
    different word types: 2CONSTANTs, SYNONYMs, DOES>-defined words,
    FCONSTANTs, etc. And they typically create inline code instead of
    CALLing the DOES>/SET-DOES> code. In addition, there are some uses
    of SET-OPTIMIZER for better code in connection with earlier literals
    in the code, but that's not the main point here.

    * If Gforth had an automatic inliner, like VFX, an alternative would
    be to define, e.g., CONSTANTs as colon definitions, e.g.,

    5 constant five

    would be equivalent to

    : five 5 ;

    With the automatic inliner, SET-OPTIMIZER would not be necessary to
    generate inlined code for constants. Problem solved! Well, except
    that constants as inlinable colon definitions could be quite large:
    There is the native code (typically not large), but there is
    typically also some intermediate representation that is used for
    inlining. Depending on how you balance size against the costs of
    adding SET-OPTIMIZER calls, you might still prefer to go for the
    latter.

    There would still be the optimizations involving constants, which
    could be solved by making the deeper levels of the code generator
    (below PEEPHOLE-COMPILE,) aware of constants, but that just shifts
    redundancy from one place to another. In the end you have to live
    with the fact that any optimization that is optional introduces
    redundancy, and the optional generated code needs to have the same
    semantics as the default code.

    * But a Forth system can also use SET-OPTIMIZER for dealing with each
    primitive separately. I.e., to have stuff like

    : compile,-+ ( xt -- ) drop ... ( generate code for + ) ;
    : + [ 0 compile,-+ ] ; ' compile,-+ set-optimizer

    And of course, with 50 or more primitives, you can factor out the
    commonalities between them. I think that VFX works that way.
    Lxf/ntf has a similar approach, but it works through the mechanism
    for changing the compilation semantics, not through COMPILE,.

    When having a separate COMPILE, implementation for each primitive,
    the xt passed to COMPILE, does not need to be passed to the COMPILE,
    implementation; but you still need it for words (such as constants)
    where the same COMPILE, implementation is used for the whole class.

    - anton
    --
    M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
    comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
    New standard: https://forth-standard.org/
    EuroForth 2022: https://euro.theforth.net

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Marcel Hendrix@21:1/5 to minf...@arcor.de on Fri Nov 25 10:29:47 2022
    On Friday, November 25, 2022 at 7:22:15 PM UTC+1, minf...@arcor.de wrote:
    [..]
    What would all those SET-OPTIMIZERs do to compile "2 CELLS ALLOT"
    to "add 8 to [address-of-dp]" ?

    Something like this:

    FORTH> 4 CONSTANT CELL
    FORTH> : CELLS cell * ;
    FORTH> VARIABLE dp ok
    FORTH> : ALLOT dp +! ;
    FORTH> : test 6 CELLS ALLOT ; see test
    Flags: TOKENIZE, ANSI
    : test 6 [trashed] [trashed] ; ok
    FORTH> ' test idis
    $0133E600 : test
    $0133E60A add $0133E140 qword-offset, #24 b#
    $0133E612 ;

    -marcel

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From minforth@arcor.de@21:1/5 to Anton Ertl on Fri Nov 25 10:22:13 2022
    Anton Ertl schrieb am Freitag, 25. November 2022 um 18:38:03 UTC+1:
    For those who have not followed recent discussions: If you define a
    word FOO and then invoke SET-OPTIMIZER ( xt -- ), then COMPILE,ing FOO
    will invoke the xt; i.e. SET-OPTIMIZER changes what COMPILE, executes.
    The only correct way to use SET-OPTIMIZER is to pass an xt that does
    not change the behaviour, only the implementation of the prescribed
    behaviour of COMPILE,. One point of discussion has been that the
    behaviour of a word is now described twice, once for EXECUTE and once
    for COMPILE,; this redundancy leads to a potential for bugs, so we
    discussed how to eliminate it.

    While thinking about this issue, I realized that the whole issue is
    quite different for different Forth systems:

    * Gforth compiles all primitives with PEEPHOLE-COMPILE, (which has a
    lot of machinery behind it) and uses SET-OPTIMIZER primarily for
    dealing with different word types; there are 18 occurrences of
    SET-OPTIMIZER in the Gforth image, and most of them are for handling different word types: 2CONSTANTs, SYNONYMs, DOES>-defined words,
    FCONSTANTs, etc. And they typically create inline code instead of
    CALLing the DOES>/SET-DOES> code. In addition, there are some uses
    of SET-OPTIMIZER for better code in connection with earlier literals
    in the code, but that's not the main point here.

    * If Gforth had an automatic inliner, like VFX, an alternative would
    be to define, e.g., CONSTANTs as colon definitions, e.g.,

    5 constant five

    would be equivalent to

    : five 5 ;

    With the automatic inliner, SET-OPTIMIZER would not be necessary to
    generate inlined code for constants. Problem solved! Well, except
    that constants as inlinable colon definitions could be quite large:
    There is the native code (typically not large), but there is
    typically also some intermediate representation that is used for
    inlining. Depending on how you balance size against the costs of
    adding SET-OPTIMIZER calls, you might still prefer to go for the
    latter.

    There would still be the optimizations involving constants, which
    could be solved by making the deeper levels of the code generator
    (below PEEPHOLE-COMPILE,) aware of constants, but that just shifts
    redundancy from one place to another. In the end you have to live
    with the fact that any optimization that is optional introduces
    redundancy, and the optional generated code needs to have the same
    semantics as the default code.

    * But a Forth system can also use SET-OPTIMIZER for dealing with each primitive separately. I.e., to have stuff like

    : compile,-+ ( xt -- ) drop ... ( generate code for + ) ;
    : + [ 0 compile,-+ ] ; ' compile,-+ set-optimizer

    And of course, with 50 or more primitives, you can factor out the commonalities between them. I think that VFX works that way.
    Lxf/ntf has a similar approach, but it works through the mechanism
    for changing the compilation semantics, not through COMPILE,.

    When having a separate COMPILE, implementation for each primitive,
    the xt passed to COMPILE, does not need to be passed to the COMPILE, implementation; but you still need it for words (such as constants)
    where the same COMPILE, implementation is used for the whole class.

    Assume ALLOT and CELLS are no primitives but defined as
    4 CONSTANT CELL
    : CELLS cell * ;
    VARIABLE dp
    : ALLOT dp +! ;

    What would all those SET-OPTIMIZERs do to compile "2 CELLS ALLOT"
    to "add 8 to [address-of-dp]" ?

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Anton Ertl@21:1/5 to minf...@arcor.de on Fri Nov 25 22:29:27 2022
    "minf...@arcor.de" <minforth@arcor.de> writes:
    Anton Ertl schrieb am Freitag, 25. November 2022 um 18:38:03 UTC+1:
    Assume ALLOT and CELLS are no primitives but defined as
    4 CONSTANT CELL
    : CELLS cell * ;
    VARIABLE dp
    : ALLOT dp +! ;

    What would all those SET-OPTIMIZERs do to compile "2 CELLS ALLOT"
    to "add 8 to [address-of-dp]" ?

    With these definitions, it does not work in Gforth, because Gforth
    does not inline. If you add two inlinings manually (a practice I
    don't recommend, but for the sake of the example):

    4 CONSTANT CELL
    : inline-cells ( xt -- ) drop ]] cell * [[ ;
    : CELLS cell * ; ' inline-cells set-optimizer
    VARIABLE dp
    : inline-allot ( xt -- ) drop ]] dp +! [[ ;
    : ALLOT dp +! ; ' inline-allot set-optimizer
    \ and now the test case
    : foo 2 cells allot ;

    Now SEE FOO outputs:

    : foo #8 dp +! ;

    This works as follows:

    "2" pushes 2 on the literal stack (this is for the constant folding optimization invented by Matthias Koch).

    "Cells" compiles CELL (which pushes 4 on the literal stack) and *; The optimizer for * sees two values on the literal stack, multiplies them
    to 8, and pushes that on the literal stack.

    "allot" compiles DP, and the COMPILE, for the variable is to push the
    address of DP as literal, which pushes the address on the literal
    stack. And it also compiles +!; before compiling a primitive, all two
    literals are popped from the literal stack and realized as primitive
    LIT followed by the value; this produces the #8 and DP literals. Then
    the +! is compiled.

    - anton
    --
    M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
    comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
    New standard: https://forth-standard.org/
    EuroForth 2022: https://euro.theforth.net

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Ruvim@21:1/5 to Anton Ertl on Sat Nov 26 16:17:08 2022
    On 2022-11-25 16:19, Anton Ertl wrote:
    For those who have not followed recent discussions: If you define a
    word FOO and then invoke SET-OPTIMIZER ( xt -- ), then COMPILE,ing FOO
    will invoke the xt; i.e. SET-OPTIMIZER changes what COMPILE, executes.
    The only correct way to use SET-OPTIMIZER is to pass an xt that does
    not change the behaviour, only the implementation of the prescribed
    behaviour of COMPILE,. One point of discussion has been that the
    behaviour of a word is now described twice, once for EXECUTE and once
    for COMPILE,; this redundancy leads to a potential for bugs, so we
    discussed how to eliminate it.

    While thinking about this issue, I realized that the whole issue is
    quite different for different Forth systems:

    * Gforth compiles all primitives with PEEPHOLE-COMPILE, (which has a
    lot of machinery behind it) and uses SET-OPTIMIZER primarily for
    dealing with different word types; there are 18 occurrences of
    SET-OPTIMIZER in the Gforth image, and most of them are for handling
    different word types: 2CONSTANTs, SYNONYMs, DOES>-defined words,
    FCONSTANTs, etc. And they typically create inline code instead of
    CALLing the DOES>/SET-DOES> code. In addition, there are some uses
    of SET-OPTIMIZER for better code in connection with earlier literals
    in the code, but that's not the main point here.

    * If Gforth had an automatic inliner, like VFX, an alternative would
    be to define, e.g., CONSTANTs as colon definitions, e.g.,

    5 constant five

    would be equivalent to

    : five 5 ;

    With the automatic inliner, SET-OPTIMIZER would not be necessary to
    generate inlined code for constants. Problem solved! Well, except
    that constants as inlinable colon definitions could be quite large:
    There is the native code (typically not large), but there is
    typically also some intermediate representation that is used for
    inlining. Depending on how you balance size against the costs of
    adding SET-OPTIMIZER calls, you might still prefer to go for the
    latter.

    There would still be the optimizations involving constants, which
    could be solved by making the deeper levels of the code generator
    (below PEEPHOLE-COMPILE,) aware of constants, but that just shifts
    redundancy from one place to another. In the end you have to live
    with the fact that any optimization that is optional introduces
    redundancy, and the optional generated code needs to have the same
    semantics as the default code.


    My initial point was against redundancy on the source code level,
    especially in user code.


    If a system employs inlining, it can use it even for "does>" parts, and
    then "set-optimizer" is not needed at all.

    So, as I can see, it's a too system-specific tool, which should not be
    used in standard programs.





    * But a Forth system can also use SET-OPTIMIZER for dealing with each
    primitive separately. I.e., to have stuff like

    : compile,-+ ( xt -- ) drop ... ( generate code for + ) ;
    : + [ 0 compile,-+ ] ; ' compile,-+ set-optimizer

    And of course, with 50 or more primitives, you can factor out the
    commonalities between them.

    I think that VFX works that way.
    Lxf/ntf has a similar approach, but it works through the mechanism
    for changing the compilation semantics, not through COMPILE,.

    Like it was in cmForth. And A.6.2.0945 says about that too:
    | It is not always possible to obtain the compilation token
    | from the execution token. In these implementations,
    | "COMPILE," might not generate code that is as efficient
    | as normally compiled code.

    So it generates better code when the word is encountered by the Forth
    text interpreter, than when "compile," is applied to the xt of the word
    (when it's allowed).



    When having a separate COMPILE, implementation for each primitive,
    the xt passed to COMPILE, does not need to be passed to the COMPILE,
    implementation; but you still need it for words (such as constants)
    where the same COMPILE, implementation is used for the whole class.


    I would prefer to have different setters for these cases, and don't pass
    xt when it's not needed.


    --
    Ruvim

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From P Falth@21:1/5 to Ruvim on Sun Nov 27 12:10:36 2022
    On Saturday, 26 November 2022 at 17:17:12 UTC+1, Ruvim wrote:
    On 2022-11-25 16:19, Anton Ertl wrote:
    For those who have not followed recent discussions: If you define a
    word FOO and then invoke SET-OPTIMIZER ( xt -- ), then COMPILE,ing FOO
    will invoke the xt; i.e. SET-OPTIMIZER changes what COMPILE, executes.
    The only correct way to use SET-OPTIMIZER is to pass an xt that does
    not change the behaviour, only the implementation of the prescribed behaviour of COMPILE,. One point of discussion has been that the
    behaviour of a word is now described twice, once for EXECUTE and once
    for COMPILE,; this redundancy leads to a potential for bugs, so we discussed how to eliminate it.

    While thinking about this issue, I realized that the whole issue is
    quite different for different Forth systems:

    * Gforth compiles all primitives with PEEPHOLE-COMPILE, (which has a
    lot of machinery behind it) and uses SET-OPTIMIZER primarily for
    dealing with different word types; there are 18 occurrences of SET-OPTIMIZER in the Gforth image, and most of them are for handling different word types: 2CONSTANTs, SYNONYMs, DOES>-defined words, FCONSTANTs, etc. And they typically create inline code instead of
    CALLing the DOES>/SET-DOES> code. In addition, there are some uses
    of SET-OPTIMIZER for better code in connection with earlier literals
    in the code, but that's not the main point here.

    * If Gforth had an automatic inliner, like VFX, an alternative would
    be to define, e.g., CONSTANTs as colon definitions, e.g.,

    5 constant five

    would be equivalent to

    : five 5 ;

    With the automatic inliner, SET-OPTIMIZER would not be necessary to generate inlined code for constants. Problem solved! Well, except
    that constants as inlinable colon definitions could be quite large:
    There is the native code (typically not large), but there is
    typically also some intermediate representation that is used for
    inlining. Depending on how you balance size against the costs of
    adding SET-OPTIMIZER calls, you might still prefer to go for the
    latter.

    There would still be the optimizations involving constants, which
    could be solved by making the deeper levels of the code generator
    (below PEEPHOLE-COMPILE,) aware of constants, but that just shifts redundancy from one place to another. In the end you have to live
    with the fact that any optimization that is optional introduces
    redundancy, and the optional generated code needs to have the same semantics as the default code.
    My initial point was against redundancy on the source code level,
    especially in user code.


    If a system employs inlining, it can use it even for "does>" parts, and
    then "set-optimizer" is not needed at all.

    So, as I can see, it's a too system-specific tool, which should not be
    used in standard programs.

    I agree to this. in LXF I have ;macro that inlines the does part. Used like

    : array create cells allot ;macro swap cells + ; ok
    10 array a1 ok
    123 2 a1 ! ok
    2 a1 @ . 123 ok
    : t1 2 a1 @ ; ok
    t1 . 123 ok
    see t1
    A4A614 409CE7 14 C80000 5 normal T1

    409CE7 A11C337400 mov eax , [0074331C]
    409CEC 895DFC mov [ebp-4h] , ebx
    409CEF 8BD8 mov ebx , eax
    409CF1 8D6DFC lea ebp , [ebp-4h]
    409CF4 C3 ret near
    ok

    In lxf this is just an evaluate macro so has all its faults.
    I also implemented DOES-COMP> and ;DOES-RUN to avoid this.
    But honestly it is not used anywhere in my code!


    * But a Forth system can also use SET-OPTIMIZER for dealing with each primitive separately. I.e., to have stuff like

    : compile,-+ ( xt -- ) drop ... ( generate code for + ) ;
    : + [ 0 compile,-+ ] ; ' compile,-+ set-optimizer

    And of course, with 50 or more primitives, you can factor out the commonalities between them.

    I think that VFX works that way.
    Lxf/ntf has a similar approach, but it works through the mechanism
    for changing the compilation semantics, not through COMPILE,.
    Like it was in cmForth. And A.6.2.0945 says about that too:
    | It is not always possible to obtain the compilation token
    | from the execution token. In these implementations,
    | "COMPILE," might not generate code that is as efficient
    | as normally compiled code.

    So it generates better code when the word is encountered by the Forth
    text interpreter, than when "compile," is applied to the xt of the word
    (when it's allowed).

    Exactly. In the beginning lxf used the cmForth way of a separate compilation wordlist. When I needed also wordlists for its intended purpose. I implemented the dual XT header instead.

    Yes xt COMPILE, will just produce a call to xt and will not use the code generator.
    I do not see a problem with this.

    When having a separate COMPILE, implementation for each primitive,
    the xt passed to COMPILE, does not need to be passed to the COMPILE, implementation; but you still need it for words (such as constants)
    where the same COMPILE, implementation is used for the whole class.
    I would prefer to have different setters for these cases, and don't pass
    xt when it's not needed.

    LXF has a flag in the header indicating type of word so the xt will only be supplied
    when actually needed.

    BR
    Peter


    --
    Ruvim

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Ruvim@21:1/5 to Anton Ertl on Sun Nov 27 21:14:42 2022
    On 2022-11-25 22:29, Anton Ertl wrote:
    "minf...@arcor.de" <minforth@arcor.de> writes:
    Anton Ertl schrieb am Freitag, 25. November 2022 um 18:38:03 UTC+1:
    Assume ALLOT and CELLS are no primitives but defined as
    4 CONSTANT CELL
    : CELLS cell * ;
    VARIABLE dp
    : ALLOT dp +! ;

    What would all those SET-OPTIMIZERs do to compile "2 CELLS ALLOT"
    to "add 8 to [address-of-dp]" ?

    With these definitions, it does not work in Gforth, because Gforth
    does not inline. If you add two inlinings manually (a practice I
    don't recommend, but for the sake of the example):

    4 CONSTANT CELL
    : inline-cells ( xt -- ) drop ]] cell * [[ ;
    : CELLS cell * ; ' inline-cells set-optimizer
    VARIABLE dp
    : inline-allot ( xt -- ) drop ]] dp +! [[ ;
    : ALLOT dp +! ; ' inline-allot set-optimizer
    \ and now the test case
    : foo 2 cells allot ;

    Now SEE FOO outputs:

    : foo #8 dp +! ;

    This works as follows:

    "2" pushes 2 on the literal stack (this is for the constant folding optimization invented by Matthias Koch).

    "Cells" compiles CELL (which pushes 4 on the literal stack) and *; The optimizer for * sees two values on the literal stack, multiplies them
    to 8, and pushes that on the literal stack.

    "allot" compiles DP, and the COMPILE, for the variable is to push the
    address of DP as literal, which pushes the address on the literal
    stack. And it also compiles +!; before compiling a primitive, all two literals are popped from the literal stack and realized as primitive
    LIT followed by the value; this produces the #8 and DP literals. Then
    the +! is compiled.



    It seems the literal stack is not needed if an intermediate
    representation (e.g., an abstract semantic graph) is used for a whole definition for optimization purposes.

    Then a term rewrite system (term graph rewriting) can be employed to
    make optimization and code generation.

    Was such an approach used in Forth systems?


    --
    Ruvim

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From minforth@arcor.de@21:1/5 to Ruvim on Sun Nov 27 14:01:45 2022
    Ruvim schrieb am Sonntag, 27. November 2022 um 22:14:47 UTC+1:
    On 2022-11-25 22:29, Anton Ertl wrote:
    "minf...@arcor.de" <minf...@arcor.de> writes:
    Anton Ertl schrieb am Freitag, 25. November 2022 um 18:38:03 UTC+1:
    Assume ALLOT and CELLS are no primitives but defined as
    4 CONSTANT CELL
    : CELLS cell * ;
    VARIABLE dp
    : ALLOT dp +! ;

    What would all those SET-OPTIMIZERs do to compile "2 CELLS ALLOT"
    to "add 8 to [address-of-dp]" ?

    With these definitions, it does not work in Gforth, because Gforth
    does not inline. If you add two inlinings manually (a practice I
    don't recommend, but for the sake of the example):

    4 CONSTANT CELL
    : inline-cells ( xt -- ) drop ]] cell * [[ ;
    : CELLS cell * ; ' inline-cells set-optimizer
    VARIABLE dp
    : inline-allot ( xt -- ) drop ]] dp +! [[ ;
    : ALLOT dp +! ; ' inline-allot set-optimizer
    \ and now the test case
    : foo 2 cells allot ;

    Now SEE FOO outputs:

    : foo #8 dp +! ;

    This works as follows:

    "2" pushes 2 on the literal stack (this is for the constant folding optimization invented by Matthias Koch).

    "Cells" compiles CELL (which pushes 4 on the literal stack) and *; The optimizer for * sees two values on the literal stack, multiplies them
    to 8, and pushes that on the literal stack.

    "allot" compiles DP, and the COMPILE, for the variable is to push the address of DP as literal, which pushes the address on the literal
    stack. And it also compiles +!; before compiling a primitive, all two literals are popped from the literal stack and realized as primitive
    LIT followed by the value; this produces the #8 and DP literals. Then
    the +! is compiled.
    It seems the literal stack is not needed if an intermediate
    representation (e.g., an abstract semantic graph) is used for a whole definition for optimization purposes.

    Then a term rewrite system (term graph rewriting) can be employed to
    make optimization and code generation.

    Was such an approach used in Forth systems?


    About 30y ago I used a Forth compiler written in Prolog. High-level definitions were translated to list terms. So a Forth program became a database of list terms.
    Forth words and literals became compound Prolog terms including type information.

    Now the Forth program = list database could be submitted to repeated rule-based rewritings (e.g. peephole optimizations incl. inlining, constant folding, and translation
    to machine code) until the rule database was exhausted and no more rewritings could be applied. A task for which a Prolog engine is well suited.

    Unfortunately our Prolog guru had to leave this world too soon...
    But I am still convinced of the benefits of the concept to use lists as intermediate
    language for Forth definitions, and to apply multi-pass optimizations over groups
    of terms and groups of lists. Only today one would probably rather use Ocaml instead
    of Prolog. I wouldn't know how to do this with Forth on-board resources.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Anton Ertl@21:1/5 to Ruvim on Mon Nov 28 18:03:14 2022
    Ruvim <ruvim.pinka@gmail.com> writes:
    On 2022-11-25 16:19, Anton Ertl wrote:
    There would still be the optimizations involving constants, which
    could be solved by making the deeper levels of the code generator
    (below PEEPHOLE-COMPILE,) aware of constants, but that just shifts
    redundancy from one place to another. In the end you have to live
    with the fact that any optimization that is optional introduces
    redundancy, and the optional generated code needs to have the same
    semantics as the default code.


    My initial point was against redundancy on the source code level,

    Such optimizations have to be expressed at the source code level. As
    a simple example, take a look at

    :noname ( xt -- )
    lits# 1 u>= if
    lits> case
    0 of postpone dup drop exit endof
    1 of postpone over drop exit endof
    [defined] fourth [if]
    2 of postpone third drop exit endof
    3 of postpone fourth drop exit endof
    [then]
    dup >lits
    endcase
    then
    peephole-compile, ;
    optimizes pick

    If you don't have this, PICK will just PEEPHOLE-COMPILE,. But with
    this, it will compile

    0 PICK into DUP
    1 PICK into OVER
    2 PICK into THIRD
    3 PICK into FOURTH

    and only other cases as before; and there you have a path with no
    lits, and a path with 1 or more lits. Each of the various paths
    through this word except the last is redundant, and each can
    potentially be buggy.

    especially in user code.

    What is user code? Looking at recent talks by Nick Nelson, in Forth
    there is not necessarily a difference between a user and a system
    implementor.

    If a system employs inlining, it can use it even for "does>" parts, and
    then "set-optimizer" is not needed at all.

    CREATE-DOES> has the problem that the data is in memory, and can be
    changed through >BODY. Consider the code

    : constant ( n "name" -- )
    create ,
    does> ( -- n)
    @ ;

    5 constant five
    : foo five ;

    Even the best compiler would have to compile FOO into code equivalent
    to

    : foo [ ' five >body ] literal @ ;

    while one would want

    : foo 5 ;

    There are the following solutions to this problem:

    * The solution that uses SET-OPTIMIZER. The disadvantage is, as
    mentioned the redundancy of expressing the same thing twice, with
    the potential for bugs.

    * Defining words such as FIVE as colon definitions, e.g.,

    : constant ( n "name" -- )
    {: n :} : ]] n ; [[ ;

    Elegant! But the disadvantage, as mentioned, is the larger memory
    consumption.

    * Using the closure mechanism; something like:

    : constant ( n "name" -- )
    [n:d ;] alias ;

    Very short and to the point; it creates a closure and then an alias
    for it, which costs extra space in Gforth; this could be alleviated
    with a named-closure mechanism. Also, in Gforth the closure is not
    inlined (and others don't have closures AFAIK), so for now it's just
    a potential solution.

    * Using the DOES> approach, but telling the compiler that the memory
    is read-only. Something like

    : constant ( n "name" -- )
    create , here cell- 1 cells read-only
    does> ( -- n)
    @ ;

    Now, when the compiler sees a @ from that address range (as it does
    when compiling FIVE, it can perform constant-folding on the @,
    resulting in compiling 5.

    This requires some memory for remembering the information about
    read-only data, but otherwise it would work and be very traditional.
    Supposedly VFX and iForth have such features. It could be added to
    Gforth relatively straightforwardly, but it currently is not there.

    So, as I can see, it's a too system-specific tool, which should not be
    used in standard programs.

    SET-OPTIMIZER certainly is. The colon definition approach can be used
    on any system, but the example code above is Gforth-specific. The
    closure approach is also Gforth-specific. The READ-ONLY approach is
    specific to the systems that support this feature. So, apart from the
    colon definition approach, they are all system-specific and
    non-standard.

    When having a separate COMPILE, implementation for each primitive,
    the xt passed to COMPILE, does not need to be passed to the COMPILE,
    implementation; but you still need it for words (such as constants)
    where the same COMPILE, implementation is used for the whole class.


    I would prefer to have different setters for these cases, and don't pass
    xt when it's not needed.

    That's easy to achieve. E.g.:

    : set-singleton-optimizer ( xt -- )
    [n:d nip execute ;] set-optimizer ;

    - anton
    --
    M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
    comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
    New standard: https://forth-standard.org/
    EuroForth 2022: https://euro.theforth.net

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Ruvim@21:1/5 to Anton Ertl on Tue Nov 29 10:26:24 2022
    On 2022-11-28 18:03, Anton Ertl wrote:
    Ruvim <ruvim.pinka@gmail.com> writes:
    On 2022-11-25 16:19, Anton Ertl wrote:
    There would still be the optimizations involving constants, which
    could be solved by making the deeper levels of the code generator
    (below PEEPHOLE-COMPILE,) aware of constants, but that just shifts
    redundancy from one place to another. In the end you have to live
    with the fact that any optimization that is optional introduces
    redundancy, and the optional generated code needs to have the same
    semantics as the default code.


    My initial point was against redundancy on the source code level,

    Such optimizations have to be expressed at the source code level. As
    a simple example, take a look at

    :noname ( xt -- )
    lits# 1 u>= if
    lits> case
    0 of postpone dup drop exit endof
    1 of postpone over drop exit endof
    [defined] fourth [if]
    2 of postpone third drop exit endof
    3 of postpone fourth drop exit endof
    [then]
    dup >lits
    endcase
    then
    peephole-compile, ;
    optimizes pick

    If you don't have this, PICK will just PEEPHOLE-COMPILE,. But with
    this, it will compile

    0 PICK into DUP
    1 PICK into OVER
    2 PICK into THIRD
    3 PICK into FOURTH

    and only other cases as before; and there you have a path with no
    lits, and a path with 1 or more lits. Each of the various paths
    through this word except the last is redundant, and each can
    potentially be buggy.


    It's a good example.


    I initially considered the cases when one fragment of code can be *automatically* generated from another fragment of code, and both are
    manually defined in source code. Hence, one of them is excessive.

    But this case is different.

    The first part can be considered as redundant only semantically, since
    it produces semantically the same code as the second part ("peephole-compile,").

    But the first part cannot be automatically created from the second part
    or from the initial definition itself. So it contains new information,
    and so it's essential for optimization.



    One more point.
    This optimizer is expressed in such a way that its first part doesn't
    require xt, and the second part requires it.

    But actually this optimizer is for "pick" only. So, the second part can
    be expressed as:

    ['] pick peephole-compile, ;

    or as something like:

    [peephole-compile] ;
    which knows at compile-time for what word it's used and produces a
    correct variant.

    Now this optimizer does not require xt at all, and its first part
    becomes simpler (if xt is not passed to it).



    And yet another point.
    This optimizer can be expressed in a more concise form as a set of
    independent term rewrite rules. Something like:

    :opt 0 pick =>= dup ;
    :opt 1 pick =>= over ;
    [defined] fourth [if]
    :opt 2 pick =>= third ;
    :opt 3 pick =>= fourth ;
    [then]


    As a possible option, such rules (their left parts) can be compiled into
    a prefix tree ("trie") to efficiently match the compiled code (right on
    the fly, or after the definition is compiled into intermediate code).

    What also is required for such rules is a kind of templates. Since
    sometimes we need to match any number, or any word of a given class.

    A huge advantages is that a significant part of such rules is system independent. They can be reused in different Forth systems.



    [...]

    --
    Ruvim

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From minforth@arcor.de@21:1/5 to Ruvim on Tue Nov 29 08:30:51 2022
    Ruvim schrieb am Dienstag, 29. November 2022 um 11:26:31 UTC+1:
    As a possible option, such rules (their left parts) can be compiled into
    a prefix tree ("trie") to efficiently match the compiled code (right on
    the fly, or after the definition is compiled into intermediate code).

    What also is required for such rules is a kind of templates. Since
    sometimes we need to match any number, or any word of a given class.

    A huge advantages is that a significant part of such rules is system independent. They can be reused in different Forth systems.

    For a "possible option" one would need syntax definitions for intermediate
    code and for rules, before the rules database can be applied recursively
    on the intermediate code.

    A "minor" thing: special care must be given to not peephole-optimize
    jump targets away, OTOH it is rather easy to eliminate tail-calls.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Anton Ertl@21:1/5 to Ruvim on Wed Nov 30 17:20:46 2022
    Ruvim <ruvim.pinka@gmail.com> writes:
    On 2022-11-28 18:03, Anton Ertl wrote:
    Ruvim <ruvim.pinka@gmail.com> writes:
    On 2022-11-25 16:19, Anton Ertl wrote:
    There would still be the optimizations involving constants, which
    could be solved by making the deeper levels of the code generator
    (below PEEPHOLE-COMPILE,) aware of constants, but that just shifts >>>> redundancy from one place to another. In the end you have to live >>>> with the fact that any optimization that is optional introduces
    redundancy, and the optional generated code needs to have the same >>>> semantics as the default code.


    My initial point was against redundancy on the source code level,

    Such optimizations have to be expressed at the source code level. As
    a simple example, take a look at

    :noname ( xt -- )
    lits# 1 u>= if
    lits> case
    0 of postpone dup drop exit endof
    1 of postpone over drop exit endof
    [defined] fourth [if]
    2 of postpone third drop exit endof
    3 of postpone fourth drop exit endof
    [then]
    dup >lits
    endcase
    then
    peephole-compile, ;
    optimizes pick

    If you don't have this, PICK will just PEEPHOLE-COMPILE,. But with
    this, it will compile

    0 PICK into DUP
    1 PICK into OVER
    2 PICK into THIRD
    3 PICK into FOURTH

    and only other cases as before; and there you have a path with no
    lits, and a path with 1 or more lits. Each of the various paths
    through this word except the last is redundant, and each can
    potentially be buggy.


    It's a good example.


    I initially considered the cases when one fragment of code can be >*automatically* generated from another fragment of code, and both are >manually defined in source code. Hence, one of them is excessive.

    It's unclear what you mean. What mechanism is behind the "automatic" generation? Isn't the source code for this mechanism also redundant?

    But this case is different.

    The first part can be considered as redundant only semantically, since
    it produces semantically the same code as the second part >("peephole-compile,").

    But the first part cannot be automatically created from the second part
    or from the initial definition itself.

    I think it can, with enough effort (consider stuff like the
    superoptimizer).

    Now this optimizer does not require xt at all, and its first part
    becomes simpler (if xt is not passed to it).

    Or one could rewrite it as:

    :noname ( xt -- )
    drop lits# 1 u>= if
    lits> case
    0 of postpone dup exit endof
    1 of postpone over exit endof
    [defined] fourth [if]
    2 of postpone third exit endof
    3 of postpone fourth exit endof
    [then]
    dup >lits
    endcase
    then
    ['] pick peephole-compile, ;
    optimizes pick

    And then delete the DROP, and instead have

    :noname ... ;
    optimizes-only pick

    or somesuch.

    And yet another point.
    This optimizer can be expressed in a more concise form as a set of >independent term rewrite rules. Something like:

    :opt 0 pick =>= dup ;
    :opt 1 pick =>= over ;
    [defined] fourth [if]
    :opt 2 pick =>= third ;
    :opt 3 pick =>= fourth ;
    [then]


    As a possible option, such rules (their left parts) can be compiled into
    a prefix tree ("trie") to efficiently match the compiled code (right on
    the fly, or after the definition is compiled into intermediate code).

    What also is required for such rules is a kind of templates. Since
    sometimes we need to match any number, or any word of a given class.

    But are there enough such cases to justify the machinery necessary for
    these ideas? You find the current uses of SET-OPTIMIZER (and derived
    words like OPTIMIZES) in

    http://git.savannah.gnu.org/cgit/gforth.git/tree/fold.fs http://git.savannah.gnu.org/cgit/gforth.git/tree/stagediv.fs

    There is some schematic code in there, so it probably can be
    simplified in some way. But finding a way with a result that is easy
    to understand is not so obvious.

    A huge advantages is that a significant part of such rules is system >independent. They can be reused in different Forth systems.

    If the systems provide all the mechanisms you have in mind. It may be
    easier to convince them of a simpler mechanism like SET-OPTIMIZER
    (which Gforth and VFX already have under different names), although
    even that is probably quite an effort.

    - anton
    --
    M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
    comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
    New standard: https://forth-standard.org/
    EuroForth 2022: https://euro.theforth.net

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Ruvim@21:1/5 to Anton Ertl on Thu Dec 1 15:59:11 2022
    On 2022-11-28 18:03, Anton Ertl wrote:
    Ruvim <ruvim.pinka@gmail.com> writes:
    On 2022-11-25 16:19, Anton Ertl wrote:

    There would still be the optimizations involving constants, which
    could be solved by making the deeper levels of the code generator
    (below PEEPHOLE-COMPILE,) aware of constants, but that just shifts
    redundancy from one place to another. In the end you have to live
    with the fact that any optimization that is optional introduces
    redundancy, and the optional generated code needs to have the same
    semantics as the default code.


    My initial point was against redundancy on the source code level,
    especially in user code.

    What is user code? Looking at recent talks by Nick Nelson, in Forth
    there is not necessarily a difference between a user and a system implementor.


    By user code I mean code (source code) that is system independent
    (portable) and unrelated to any system, regardless who created this code (although, usually such code is created by users, or implementors in the
    role of user).




    If a system employs inlining, it can use it even for "does>" parts, and
    then "set-optimizer" is not needed at all.

    CREATE-DOES> has the problem that the data is in memory, and can be
    changed through >BODY.

    Yes, but it's a problem for immutable data only.
    But I would not use create-does for immutable data at all.

    Moreover, in my practice, I use create-does only to implement (or
    re-implement) standard words in a portable way, or for some extensions.
    Usually I just directly use partial application (NB: "does>" is
    effectively just partial application with some bells and whistles).


    Consider the code

    : constant ( n "name" -- )
    create ,
    does> ( -- n)
    @ ;

    5 constant five
    : foo five ;

    Even the best compiler would have to compile FOO into code equivalent
    to

    : foo [ ' five >body ] literal @ ;

    while one would want

    : foo 5 ;

    There are the following solutions to this problem:

    * The solution that uses SET-OPTIMIZER. The disadvantage is, as
    mentioned the redundancy of expressing the same thing twice, with
    the potential for bugs.

    * Defining words such as FIVE as colon definitions, e.g.,

    : constant ( n "name" -- )
    {: n :} : ]] n ; [[ ;

    Elegant! But the disadvantage, as mentioned, is the larger memory
    consumption.

    * Using the closure mechanism; something like:

    : constant ( n "name" -- )
    [n:d ;] alias ;

    Very short and to the point; it creates a closure and then an alias
    for it, which costs extra space in Gforth; this could be alleviated
    with a named-closure mechanism. Also, in Gforth the closure is not
    inlined (and others don't have closures AFAIK), so for now it's just
    a potential solution.

    * Using the DOES> approach, but telling the compiler that the memory
    is read-only. Something like

    : constant ( n "name" -- )
    create , here cell- 1 cells read-only
    does> ( -- n)
    @ ;

    Now, when the compiler sees a @ from that address range (as it does
    when compiling FIVE, it can perform constant-folding on the @,
    resulting in compiling 5.

    This requires some memory for remembering the information about
    read-only data, but otherwise it would work and be very traditional.
    Supposedly VFX and iForth have such features. It could be added to
    Gforth relatively straightforwardly, but it currently is not there.

    So, as I can see, it's a too system-specific tool, which should not be
    used in standard programs.

    SET-OPTIMIZER certainly is. The colon definition approach can be used
    on any system, but the example code above is Gforth-specific. The
    closure approach is also Gforth-specific. The READ-ONLY approach is
    specific to the systems that support this feature. So, apart from the
    colon definition approach, they are all system-specific and
    non-standard.



    Actually, I consider the option whether a tool like SET-OPTIMIZER can be standardized (and so employable in user code). And I think, the fact
    that this tool implies source code duplication (not literally, but that
    the same information is specified twice) plays against this idea.

    And my assumption is that such duplication is acceptable in a system implementation (and some times it's too expensive to avoid it), but
    should not be encourage in user code.


    --
    Ruvim

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Anton Ertl@21:1/5 to Ruvim on Thu Dec 1 18:32:22 2022
    Ruvim <ruvim.pinka@gmail.com> writes:
    It seems the literal stack is not needed if an intermediate
    representation (e.g., an abstract semantic graph) is used for a whole >definition for optimization purposes.

    The usual way to get a data-flow graph from sequential stack-based
    code is to use a stack of subgraphs [ertl92]. The literal stack can
    be seen as the variant of this applied to literals only. If you build
    the full graph, you may prefer to perform the constant folding right
    when building the graph (and avoid the cost of building the additional
    nodes), or later (and avoid the cost of testing for the
    constant-folding case when building the graph).

    Then a term rewrite system (term graph rewriting) can be employed to
    make optimization and code generation.

    Was such an approach used in Forth systems?

    Unless you count the tree-parsing used by RAFTS as term graph
    rewriting, not to my knowledge. Albert v. d. Horst has posted
    something that looked like rewriting, but on the sequence level rather
    than the graph level IIRC.

    - anton

    @InProceedings{ertl92,
    author = "M. Anton Ertl",
    title = "A New Approach to {Forth} Native Code Generation",
    crossref = "euroforth92",
    pages = "73--78",
    url = "http://www.complang.tuwien.ac.at/papers/ertl92.ps.gz",
    abstract = "RAFTS is a framework for applying state of the art
    compiler technology to the compilation of Forth. The
    heart of RAFTS is a simple method for transforming
    Forth programs into data flow graphs and static
    single assignment form. Standard code generation and
    optimization techniques can be applied to programs in
    these forms. Specifically, RAFTS uses interprocedural
    register allocation to eliminate nearly all stack
    accesses. It also removes nearly all stack pointer
    updates. Inlining and tail call optimization reduce
    the call overhead. RAFTS compiles all of Forth,
    including difficult cases like unknown stack heights,
    {\tt PICK}, {\tt ROLL} and {\tt EXECUTE}. And last,
    but not least, RAFTS is designed for interactive
    Forth systems; it is not restricted to batch
    compilers"
    }

    @Proceedings{euroforth92,
    key = "EuroForth~'92",
    title = "EuroForth~'92",
    booktitle = "EuroForth~'92",
    year = "1992",
    organization = "MicroProcessor Engineering",
    address = "Southampton, England"
    }

    - anton
    --
    M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
    comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
    New standard: https://forth-standard.org/
    EuroForth 2022: https://euro.theforth.net

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Anton Ertl@21:1/5 to Ruvim on Thu Dec 1 18:19:40 2022
    Ruvim <ruvim.pinka@gmail.com> writes:
    Actually, I consider the option whether a tool like SET-OPTIMIZER can be >standardized (and so employable in user code). And I think, the fact
    that this tool implies source code duplication (not literally, but that
    the same information is specified twice) plays against this idea.

    Yes, although Forth is not a nanny language, I prefer standardizing
    features that have not had a history of seducing people to incorrect
    usage.

    My favourite is the closure mechanism, maybe combined with some more
    direct way to produce a named word than ALIAS.

    - anton
    --
    M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
    comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
    New standard: https://forth-standard.org/
    EuroForth 2022: https://euro.theforth.net

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From none) (albert@21:1/5 to Anton Ertl on Thu Dec 1 21:12:20 2022
    In article <2022Dec1.193222@mips.complang.tuwien.ac.at>,
    Anton Ertl <anton@mips.complang.tuwien.ac.at> wrote:
    Ruvim <ruvim.pinka@gmail.com> writes:
    It seems the literal stack is not needed if an intermediate
    representation (e.g., an abstract semantic graph) is used for a whole >>definition for optimization purposes.

    The usual way to get a data-flow graph from sequential stack-based
    code is to use a stack of subgraphs [ertl92]. The literal stack can
    be seen as the variant of this applied to literals only. If you build
    the full graph, you may prefer to perform the constant folding right
    when building the graph (and avoid the cost of building the additional >nodes), or later (and avoid the cost of testing for the
    constant-folding case when building the graph).

    Then a term rewrite system (term graph rewriting) can be employed to
    make optimization and code generation.

    Was such an approach used in Forth systems?

    Unless you count the tree-parsing used by RAFTS as term graph
    rewriting, not to my knowledge. Albert v. d. Horst has posted
    something that looked like rewriting, but on the sequence level rather
    than the graph level IIRC.

    My rewriting is on the level of constant folding.
    I'm working on a system where you can decide to optimise a
    choosen Forth word, that is recursively inlined, eliminating next
    for a total low level word.
    The result is a set of basic blocks that jump to each other.
    That works more or less.

    I'm still working on transforming the resulting assembler code,
    totally i86 specific, based on my modular assembler (ciasdis).


    - anton

    Groetjes Albert
    --
    "in our communism country Viet Nam, people are forced to be
    alive and in the western country like US, people are free to
    die from Covid 19 lol" duc ha
    albert@spe&ar&c.xs4all.nl &=n http://home.hccnet.nl/a.w.m.van.der.horst

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Anton Ertl@21:1/5 to P Falth on Sun Dec 4 11:03:49 2022
    P Falth <peter.m.falth@gmail.com> writes:
    I agree to this. in LXF I have ;macro that inlines the does part. Used like

    : array create cells allot ;macro swap cells + ; ok
    10 array a1 ok
    123 2 a1 ! ok
    2 a1 @ . 123 ok
    : t1 2 a1 @ ; ok
    t1 . 123 ok
    see t1
    A4A614 409CE7 14 C80000 5 normal T1

    409CE7 A11C337400 mov eax , [0074331C]
    409CEC 895DFC mov [ebp-4h] , ebx
    409CEF 8BD8 mov ebx , eax
    409CF1 8D6DFC lea ebp , [ebp-4h]
    409CF4 C3 ret near
    ok

    : constant create , ;macro @ ;
    5 constant five
    : foo five + ;
    : bar 5 + ;

    the code for FOO is:

    804FC10 A1F89C3808 mov eax , [08389CF8]
    804FC15 01C3 add ebx , eax
    804FC17 C3 ret near

    while the code for BAR is:

    804FC18 83C305 add ebx , # 5h
    804FC1B C3 ret near

    So CREATE...;MACRO has the usual disadvantage that CREATE..DOES> has
    on a system that inlines the DOES>-code.

    - anton
    --
    M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
    comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
    New standard: https://forth-standard.org/
    EuroForth 2022: https://euro.theforth.net

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From P Falth@21:1/5 to Anton Ertl on Sun Dec 4 05:37:09 2022
    On Sunday, 4 December 2022 at 12:12:35 UTC+1, Anton Ertl wrote:
    P Falth <peter....@gmail.com> writes:
    I agree to this. in LXF I have ;macro that inlines the does part. Used like

    : array create cells allot ;macro swap cells + ; ok
    10 array a1 ok
    123 2 a1 ! ok
    2 a1 @ . 123 ok
    : t1 2 a1 @ ; ok
    t1 . 123 ok
    see t1
    A4A614 409CE7 14 C80000 5 normal T1

    409CE7 A11C337400 mov eax , [0074331C]
    409CEC 895DFC mov [ebp-4h] , ebx
    409CEF 8BD8 mov ebx , eax
    409CF1 8D6DFC lea ebp , [ebp-4h]
    409CF4 C3 ret near
    ok
    : constant create , ;macro @ ;
    5 constant five
    : foo five + ;
    : bar 5 + ;

    You call it constant but what you have created is a value
    and lxf will treat it accordingly. To use a macro to define a constant do

    macro five " 5"
    or
    :m five 5 ;

    the code for FOO is:

    804FC10 A1F89C3808 mov eax , [08389CF8]
    804FC15 01C3 add ebx , eax
    804FC17 C3 ret near

    while the code for BAR is:

    804FC18 83C305 add ebx , # 5h
    804FC1B C3 ret near

    So CREATE...;MACRO has the usual disadvantage that CREATE..DOES> has
    on a system that inlines the DOES>-code.

    Of course it is inlining on the cheap using evaluate so will also have the evaluate problems

    BR
    Peter


    - anton
    --
    M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
    New standard: https://forth-standard.org/
    EuroForth 2022: https://euro.theforth.net

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Anton Ertl@21:1/5 to P Falth on Mon Dec 5 12:34:57 2022
    P Falth <peter.m.falth@gmail.com> writes:
    On Sunday, 4 December 2022 at 12:12:35 UTC+1, Anton Ertl wrote:
    : constant create , ;macro @ ;
    5 constant five
    : foo five + ;
    : bar 5 + ;

    You call it constant but what you have created is a value

    Yes, that's what you get with CREATE..DOES> and also with
    CREATE..;MACRO.

    and lxf will treat it accordingly. To use a macro to define a constant do

    macro five " 5"
    or
    :m five 5 ;

    Fortunately lxf does not define constants like this. You see problems
    with

    \ 10 constant ten
    macro ten " 10"
    : foo ten ;
    hex : bar ten ;
    decimal
    see foo
    see bar
    bar . \ prints "16"

    If I define TEN as constant rather than this kind of macro, it works
    correctly, and lxf compiles it efficiently (not like a value, and not
    like a colon definition). So apparently you have a special mechanism
    for that.

    - anton
    --
    M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
    comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
    New standard: https://forth-standard.org/
    EuroForth 2022: https://euro.theforth.net

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Hans Bezemer@21:1/5 to Anton Ertl on Mon Dec 5 06:34:03 2022
    On Monday, December 5, 2022 at 1:56:17 PM UTC+1, Anton Ertl wrote:
    If I define TEN as constant rather than this kind of macro, it works correctly, and lxf compiles it efficiently (not like a value, and not
    like a colon definition). So apparently you have a special mechanism
    for that.
    Correct. Forth has true constants - contrary to C, where even a "static const" does not equal a true constant, since it (traditionally - I'm not talking about ugly modern kludges) cannot be used to size e.g. arrays.

    Furthermore, the radix in C is exclusively by using prefixes - since there is no other way to set the radix. Traditional Forth however, does not have radix control outside BASE and friends.

    If I take this to 4tH - it does NOT feature prefixes, it uses compilation directives
    as [DECIMAL], [HEX], [OCTAL] and [BINARY] to determine the radix at compile time. All constants are inlined automatically. So although the 4tH preprocessor keeps track of the current radix, I wouldn't do a :MACRO FIVE 5 ; for obvious reasons.

    Hans Bezemer

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)