Post by Anton ErtlPost by RuvimSET-OPTIMIZER sets the implementation of COMPILE, ( xt -- ) for
the current word. A correct implementation of COMPILE, does not
change the semantics in any way, only the implementation of the
semantics.
It seems, "set-optimizer" as a basis for such an API is suboptimal,
since you have to describe the same semantics *twice*, and you have a
chance to do it incorrectly.
Yes.
Post by RuvimActually, if we have a definition that compiles some behavior, the
definition that performs this behavior can be created automatically.
Yes, but ... [see below].
Post by Ruvim: compile-foo ( -- ) ... ;
that appends behavior "foo" to the current definition,
then a word "foo" can be defined as
: foo [ compile-foo ] ;
Actually it's
: compile-foo ( xt -- ) ... ;
: foo recursive [ ' foo compile-foo ] ;
If COMPILE-FOO just drops the xt, you can just pass 0 to COMPILE-FOO.
Post by RuvimThen, why do we need to define both "foo" and "compile-foo" by hands?
Having one of them, another can be created automatically.
The usual usage of SET-COMPILER is in defining words, e.g.
: constant1 ( n "name" -- )
create ,
To me, "lit," looks far more comprehensible than "]] literal [["
Post by Anton ErtlHere you have the advantage that the constant needs only one cell in
addition to the header. Yes, you have the disadvantage that the
SET-DOES> and SET-OPTIMIZER actions might disagree, leading to
incorrect behaviour. An additional aspect here is that this
definition assumes that the value of the constant is not changed.
Could we avoid the redundancy and the potential disagreement? You
suggest creating a colon definition for "name". How could this work?
: lit, postpone literal ;
: constant2 ( n "name" -- )
Post by Ruvimr :noname r> ]] drop literal lit, ; [[ >r
The definition
5 constant1 five1
takes 6 cells (on a 64-bit machine) in the dictionary, while
5 constant2 five2
takes 16 cells in the dictionary plus 146 Bytes of native code with
the debugging engine on AMD64.
The code is lager since create-does in Gforth avoids duplication of some
code parts (i.e., it utilizes one instance for many definitions). And
since anonymous definition are too heavy in Gforth. For example,
":noname ;" takes 24 bytes (3 cells) in Gforth, 3 bytes (3/4 cells) in
SwiftForth 3.11.6, and 1 byte (1/4 cells) in SP-Forth/4.
For colon definitions this difference should not be so drastic.
OTOH, if you provide an optimizer that generates longer code instead of
a definition call, it's probably not a problem that the definition
itself takes more space.
Post by Anton ErtlMoreover, I had several bugs in CONSTANT2 until I got it right, but
that could get better with more practice. But will it get better than
the alternative? The code is larger, so that's far from clear.
Having proper tools, it should not be more difficult.
Post by Anton Ertlr :noname r> ]] drop literal lit, ; [[ ( xt )
They can be expressed far simpler as following.
In "constant1":
[: >body @ lit, ;]
In "constant2":
['] lit, partial1
Post by Anton ErtlIn any case, it seems to me that the size advantage alone makes the
CONSTANT1 approach preferable. Yes, you describe the same thing
twice, and you may get it wrong in one description while getting it
right in the other, so you have test both implementations separately
(e.g., interpret the word once, and include it in a colon definition,
and use the same tests on it; maybe we could automate that), but such
bugs are rare.
Post by RuvimA better API for per-word optimization should require the user to define
only the compiler for a word, and the word itself will be created
automatically.
[: postpone over postpone over ;] "2dup" define-by-compiler
compiler: 2dup ]] over over [[ ;
: value
create ,
;
The first two are alternatives, the third one addresses a different
need. For the VALUE example, how does the implementation work; I can
imagine how it works for the 2DUP examples.
Ideally, an implementation for such "does-by-compiler" should be
supported by the corresponding implementations for "create" and "does>".
But for the purpose of PoC we can do it less efficiently. So a Gforth
specific PoC is following.
In Gforth, ":" and ":noname" affect "latestxt" (which is used by
"set-does>" and "set-compiler"), but "[: ... ;]" doesn't affect it.
So I use the latter construct to create intermediate helper definitions.
The intermediate definitions are needed to adapt the interface of
"does-by-compiler" to the interface of "set-does>" and "set-optimizer"
in Gforth.
: begin-quot ( C: -- quotation-sys colon-sys ) ['] [: execute ;
: end-quot ( C: quotation-sys colon-sys -- xt ) postpone ;] ;
: does-by-compiler ( xt.compiler -- ) \ xt.compiler ( addr.body -- )
latestxt >body >r >r ( R: addr.body xt.compiler )
begin-quot
postpone drop \ the passed addr.body is not needed
2r@ execute
end-quot set-does>
begin-quot
postpone drop \ the passed xt is not needed
r> r> lit, compile,
end-quot set-optimizer
;
A usage example:
: val ( x "name" -- )
create , [: lit, postpone @ ;] does-by-compiler
;
123 val x
x . \ prints 123
: foo x . ; foo \ prints 123
456 ' x >body !
x . \ prints 456
see foo \ should show an optimized variant
Post by Anton ErtlPost by RuvimBTW, I don't see why xt should be passed to a compiler (as it's done in
"set-compiler"). In what cases it's useful?
It's useful for getting the value of the constant in CONSTANT1.
As I can see, what is actually needed in this case is not an xt but a
data field address.
Do we have an example when an xt itself is needed?
Post by Anton ErtlIt's also the interface of COMPILE,. SET-OPTIMIZER only defines
what COMPILE, does for the word that SET-OPTIMIZER is applied to. If
COMPILE, instead DROPped the xt and only then called the word that we
pass with SET-OPTIMIZER, that works nicely for the 2DUP example, but
how would DOES-BY-COMPILER produce the ADDR that is passed to the xt?
From the formal point of view, "does>" in run-time makes partial
application. It partially applies the part "X" in "does> X ;" to the
ADDR, producing a new definition, and replaces the execution semantics
of the most recent definition by the execution semantics of this new
definition.
In the case of "does-by-compiler", this new definition is created by
means of the passed xt.compiler, and then the execution semantics of the
most recent definition is replaced by this new definition.
But it still have to partially apply the xt.compiler to create the full
optimizer. A possible more concise definition for "does-by-compiler":
: does-by-compiler ( xt.compiler -- ) \ xt.compiler ( addr.body -- )
latest-name> >body swap 2>r ( R: addr.body xt.compiler )
begin-quot 2r@ execute end-quot latest-name> replace-behavior
2r> partial1 latest-name> advise-compiler
;
where
partial1 ( x xt1 -- xt2 )
\ xt2 is partially applied xt1 to x
\ This word may use data space.
latest-name> ( -- xt )
\ xt is the execution token of the most recently appended
\ definition in the compilation word list.
\ An ambiguous condition exists if such a definition is absent.
advise-compiler ( xt.compiler xt -- )
\ It makes "compiler," to only perform xt.compiler
\ when it's applied to xt. It may use data space.
\ An ambiguous condition exists if the execution semantics
\ identified by xt.compiler are distinct from appending
\ the execution semantics identified by xt to the current definition.
replace-behavior ( xt.new xt -- )
\ It makes xt to identify the execution semantics identified
\ by xt.new. It may use data space.
\ An ambiguous condition exists if xt is not for a definition
\ that is created by "create".
--
Ruvim