Discussion:
Another example of factoring and locals
(too old to reply)
Anton Ertl
2022-10-26 20:51:53 UTC
Permalink
Today I rewrote SEE-CODE (actually it's factor SEE-CODE-RANGE) to
display, e.g.

this instead of this
... ...
$7F7E43202948 r> 1->1 $7F7E43202948 r> 1->1
7F7E42ECADE4: mov [r15],r8 $7F7E43202950 fill
7F7E42ECADE7: sub r15,$08 7F7E42ECADE4: mov [r15],r8
7F7E42ECADEB: mov r8,[rbx] 7F7E42ECADE7: sub r15,$08
7F7E42ECADEE: add r13,$08 7F7E42ECADEB: mov r8,[rbx]
7F7E42ECADF2: add rbx,$08 7F7E42ECADEE: add r13,$08
7F7E42ECADF6: mov rcx,-$08[r13] 7F7E42ECADF2: add rbx,$08
7F7E42ECADFA: jmp ecx 7F7E42ECADF6: mov rcx,-$08[r13]
7F7E42ECADFC: nop 7F7E42ECADFA: jmp ecx
$7F7E43202950 fill 7F7E42ECADFC: nop
$7F7E43202958 ;s 1->1 $7F7E43202958 ;s 1->1
... ...

I had to get some additional information from the decompiler
primitive, so we now have

decompile-prim2 ( a_code -- useqlen ustart uend c_addr u ulen )

instead of

decompile-prim ( a_code -- ustart uend c_addr u ulen )

but the existing factoring also was not very amenable to the change,
so I rewrote SEE-CODE-RANGE from scratch, with the following result:

: see-code-range { addr1 addr2 -- } \ gforth
addr1 0 0 0 ['] noop case { addr nseqlen d: codeblock xt: cr? }
addr addr2 u>= ?of endof
addr @ decompile-prim2 { ulen } ulen 0< ?of
drop 2drop 2drop
cr? addr simple-see-word
addr cell+ nseqlen codeblock ['] cr contof
nseqlen 0= if codeblock discode 0 0 to codeblock ['] noop to cr? then
cr? addr see-word.addr type { nseqlen1 ustart uend } ulen if
ustart 4 spaces 0 .r ." ->" uend .
assert( codeblock nip 0= )
addr @ ulen to codeblock then
addr cell+ nseqlen nseqlen1 max 1- codeblock ['] cr
next-case
codeblock discode ;

As you can see, it uses locals heavily (the word I have written with
the most locals by far). If you want to follow what's going on on the
stack, it uses the following additional words:

simple-see-word ( addr -- )
discode ( c-addr u -- )
see-word.addr ( addr -- )

This code took me a while to write, but apart from the handling of the
CRs, it worked on the first try. The handling of the CRs is
complicated, because DISCODE outputs a CR at the start and at the end
(one might consider this a factoring mistake); we could get rid of
everything to do with CR? if DISCODE did not have this property. I
find that the flexible enhanced CASE control structure is a very
natural way for me to consider the different cases and to keep track
of what has been covered already.

For comparison, below you find the old code; it uses fewer locals, and
is factored into three words, but is it better (apart from the
functionality difference)? IMO no.

\ also uses decompile-prim ( addr1 -- addr2 )

: see-code-word { addr -- len }
addr see-word.addr addr @ decompile-prim1 dup >r -1 = if
2drop 2drop addr cell+ addr @ .word drop
else
type 4 spaces swap r@ if
0 .r ." ->" .
else
2drop then
then
r> ;

: see-code-next-inline { addr1 addr2 -- addr3 }
\ decompile starting at addr1 until an inlined primitive is found,
\ or addr2 is reached; addr3 is addr2 or the next inlined
\ primitive
addr1 begin { addr }
addr addr2 u< while
addr @ dup decompile-prim = while
addr cr simple-see-word
addr cell+
repeat then
addr ;

: see-code-range { addr1 addr2 -- } \ gforth
cr addr1 begin { a }
a see-code-word { restlen }
a cell+ addr2 see-code-next-inline { b }
a @ b addr2 u< while
( a @ ) b @ over - discode
b
repeat
\ now disassemble the remaining a @; we derive the length from
\ it's primitive
restlen discode ;
\ dup decompile-prim dup next-prim swap - discode ;

- 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
dxforth
2022-10-27 03:38:37 UTC
Permalink
Post by Anton Ertl
For comparison, below you find the old code; it uses fewer locals, and
is factored into three words, but is it better (apart from the
functionality difference)? IMO no.
If factoring simplifies code and removes the need for strange Case words,
IMO that's a good thing.

: foo ( addr1 addr2 -- )
Post by Anton Ertl
r 0 swap 0 0 ['] noop begin { nseqlen addr d: codeblock xt: cr? }
addr r@ u>= not while
addr @ decompile-prim2 { ulen } ulen 0< if
drop 2drop 2drop
cr? addr simple-see-word
nseqlen
else
nseqlen 0= if codeblock discode 0 0 to codeblock ['] noop to cr? then
cr? addr see-word.addr type { nseqlen1 ustart uend } ulen if
ustart 4 spaces 0 .r ." ->" uend .
assert( codeblock nip 0= )
addr @ ulen to codeblock then
nseqlen nseqlen1 max 1-
then
addr cell+ codeblock ['] cr
repeat r> drop ;

: see-code-range ( addr1 addr2 -- ) \ gforth
foo codeblock discode ;
Anton Ertl
2022-10-27 10:32:14 UTC
Permalink
Post by dxforth
If factoring simplifies code and removes the need for strange Case words,
IMO that's a good thing.
: foo ( addr1 addr2 -- )
Post by Anton Ertl
r 0 swap 0 0 ['] noop begin { nseqlen addr d: codeblock xt: cr? }
drop 2drop 2drop
cr? addr simple-see-word
nseqlen
else
nseqlen 0= if codeblock discode 0 0 to codeblock ['] noop to cr? then
cr? addr see-word.addr type { nseqlen1 ustart uend } ulen if
ustart 4 spaces 0 .r ." ->" uend .
assert( codeblock nip 0= )
nseqlen nseqlen1 max 1-
then
addr cell+ codeblock ['] cr
repeat r> drop ;
: see-code-range ( addr1 addr2 -- ) \ gforth
foo codeblock discode ;
Yes, once one has a working version, one can reorganize it, and in
this case it's relatively straightforward to replace the
CASE...NEXT-CASE with a BEGIN ... REPEAT through the introduction of
an ELSE. But it's helpful to have the additional possibilities while
creating the code, and then one can switch to the more traditional
control structures if desired.

The splitting into two words does not work that way, because CODEBLOCK
is a local. It's also unnecessary, because you can just append
"CODEBLOCK DISCODE" to FOO. And the name you chose for FOO also shows
that FOO is not a useful factor for use by other words.

Concerning the partial replacement of locals by keeping the values on
the return stack or data stack, that also can be done more easily in a
second step. But given that we use locals here anyway, I don't find
the result in any way preferable.

If performance was an issue (it is not for this word), on some systems
that might be a reason to use more data stack (not so sure about
return stack) items rather than locals; one data stack item (as used
above) is helpful on most systems, because they keep one data stack
item in a register across basic block boundaries; it's less clearcut
for return stack items or more data stack items.

- 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
dxforth
2022-10-27 12:19:50 UTC
Permalink
Post by Anton Ertl
Concerning the partial replacement of locals by keeping the values on
the return stack or data stack, that also can be done more easily in a
second step. But given that we use locals here anyway, I don't find
the result in any way preferable.
There it is. I don't find locals preferable in any way. It pushes
me to find whether there's another, potentially better, solution.
Hans Bezemer
2022-10-27 16:11:07 UTC
Permalink
There it is. I don't find locals preferable in any way. It pushes
me to find whether there's another, potentially better, solution.
Hear, hear. I also don't like long definitions. They're a horror to maintain.
Short words make the logic comprehensible - at every level. Both the lower
level words as well as the higher level words.

Hans Bezemer
Anton Ertl
2022-10-27 16:24:41 UTC
Permalink
Post by Hans Bezemer
I also don't like long definitions. They're a horror to maintain.
Short words make the logic comprehensible - at every level. Both the lower
level words as well as the higher level words.
I am awaiting you posting code for this problem to support your
claims.

- 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
dxforth
2022-10-28 08:11:01 UTC
Permalink
Post by Anton Ertl
Post by Hans Bezemer
I also don't like long definitions. They're a horror to maintain.
Short words make the logic comprehensible - at every level. Both the lower
level words as well as the higher level words.
I am awaiting you posting code for this problem to support your
claims.
It's not enough he doesn't use locals in his own code - he should do
your coding as well? :)

The appeal of locals in forth is the idea that somehow they're free.
Try this test. Replace the locals in your code with regular variables/
values. Now ask yourself whether you still think it's good code; for
it should make no difference to 'good code' where the variables are
located. If one isn't happy with variables external to forth words,
one shouldn't be happy with those internal to them.
Paul Rubin
2022-10-28 08:33:28 UTC
Permalink
If one isn't happy with variables external to forth words, one
shouldn't be happy with those internal to them.
1. The externals make the code harder to understand / maintain since they
persist across function calls and can be shared between functions. You can
use some naming conventions to indicate that this isn't happening, but
ugh. And you still have to re-initialize the global inside the function
on every call.

2. If the function is recursive you can't use global variables. You have to
set up a separate stack for them or something. Ugh.

3. The globals keep occupying storage even when the function is not running.
I thought Forthers liked stack allocation so that the memory would keep
getting re-used. Locals give that.
minf...@arcor.de
2022-10-28 15:36:27 UTC
Permalink
Post by Paul Rubin
If one isn't happy with variables external to forth words, one
shouldn't be happy with those internal to them.
1. The externals make the code harder to understand / maintain since they
persist across function calls and can be shared between functions. You can
use some naming conventions to indicate that this isn't happening, but
ugh. And you still have to re-initialize the global inside the function
on every call.
2. If the function is recursive you can't use global variables. You have to
set up a separate stack for them or something. Ugh.
3. The globals keep occupying storage even when the function is not running.
I thought Forthers liked stack allocation so that the memory would keep
getting re-used. Locals give that.
+1 Technical aspects and personal tastes put aside, IMO the most important
benefits of using locals is instantaneous readability of code.

The human brain is no stack machine. We think in associations IOW by tagging
names to material and non-material things and concepts.

Forth is a strange animal: by factoring we try to chop long algorithms into smallest
pieces and then try to give them meaningful names to make the code understandable.
Within those pieces we allow masochistic stack juggling and some guys even claim
to be happy.

But sometimes factorization does not come easily, see Anton's example. It is
good that we have the choice to use locals where they are appropriate.

For quick testing and prototyping I sometimes even use extended locals, in their form:
: TEST { a b .. | d e .. == f g .. } .. ;
with
a b .. standard initialized locals
d e .. standard uninitialized locals
== instead of standard --
f g output locals, non-standard
All locals can be integers, doubles, floats, complex numbers (plus arrays and structs)

So in principle FROT could be defined as
: FROT { f: a b c == b c a } ; \ no code required
with complex numbers:
: ZROT { z: a b c == b c a } ; \ no code required

While this example is close to triviality (the locals compiler is not) real-world code
with extended locals and good short locals names is VERY readable, and mostly bug
free from start.
Hans Bezemer
2022-10-28 16:28:43 UTC
Permalink
Post by Paul Rubin
If one isn't happy with variables external to forth words, one
shouldn't be happy with those internal to them.
1. The externals make the code harder to understand / maintain since they
persist across function calls and can be shared between functions. You can
use some naming conventions to indicate that this isn't happening, but
ugh. And you still have to re-initialize the global inside the function
on every call.
If you need a carload of variables, you've forgotten (or completely disregarded)
the first rule of "Thinking Forth", which is "STAMP OUT THE VARIABLES"!
Post by Paul Rubin
2. If the function is recursive you can't use global variables. You have to
set up a separate stack for them or something. Ugh.
See rule number one.
Post by Paul Rubin
3. The globals keep occupying storage even when the function is not running.
I thought Forthers liked stack allocation so that the memory would keep
getting re-used. Locals give that.
So does C. It also introduces the penalty of stack frame creation and destruction.
+1 Technical aspects and personal tastes put aside, IMO the most important
benefits of using locals is instantaneous readability of code.
If you need locals for that, you're not doing it correctly. Short definitions allow for
readability - not locals.
The human brain is no stack machine. We think in associations IOW by tagging
names to material and non-material things and concepts.
We were spoiled by Fortran, which allowed us to translate mathematical formulas
almost directly to working code. Blame mathematics for that notation.
Forth is a strange animal: by factoring we try to chop long algorithms into smallest
pieces and then try to give them meaningful names to make the code understandable.
Within those pieces we allow masochistic stack juggling and some guys even claim
to be happy.
If there is excessive stack juggling, it might be because you're (a) a totally incompetent
Forth programmer, (b) a lazy Forth programmer, who feels good translating C code
directly to Forth without studying the algorithm and (c) in some circumstances it's
not worth, needed or even desired to reduce the amount of stack juggling. My rule of
the thumb is - if I can't understand it I rewrite it.
But sometimes factorization does not come easily, see Anton's example. It is
good that we have the choice to use locals where they are appropriate.
Nobody says it is or should be easy! It requires thought and some tinkering. If
you just want to churn out code quickly, you should do Forth - but Python! However,
often library code is the hardest to get right IMHO. Application code - not so much.
I was able to create a 64K Forth source in 5 days. In a professional setting. It never
missed a beat in its entire lifetime - despite the excessive diagnostic code.

Hans Bezemer
Paul Rubin
2022-10-29 02:25:08 UTC
Permalink
Post by Hans Bezemer
Post by ***@arcor.de
Post by Paul Rubin
I thought Forthers liked stack allocation so that the memory would keep
getting re-used. Locals give that.
So does C. It also introduces the penalty of stack frame creation and destruction.
If you mean the frame pointer, you don't really need that, it just makes
debugging easier. You can pass -ffomit-frame-pointer to gcc to omit it.
Post by Hans Bezemer
Post by ***@arcor.de
But sometimes factorization does not come easily, see Anton's
example. It is good that we have the choice to use locals where they
are appropriate.
Nobody says it is or should be easy! It requires thought and some
tinkering. If you just want to churn out code quickly, you should do
Forth - but Python!
Is there some benefit gained from that thought and tinkering and loss of
productivity? Does it make the code faster, more reliable, or anything
like that? Otherwise it sounds like masochism or some misplaced
ideological extremism, like "the stack operators cannot fail--they can
only be failed".

Here is the simplest way I can see to multiply of two complex numbers
(x=a+bi, y=c+di, z=x*y):

: z* { F: a F: b F: c F: d -- F: ac-bd F: ad+bc }
a c f* b d f* f- a d f* b c f* f+ ;

That example could use FVARIABLEs instead of locals but it would be a
lot messier imho. There are no FVALUEs in standard Forth iirc, so you
have to use @ everywhere, and then your auditing process has to make
sure nothing else uses those variables, including by having anything
aliased to the pointers in the variables, and so on. There is no
equivalent of the return stack to stash intermediate FP values, so stack
juggling becomes harder. Maybe it is still possible, but who wants to
spend the debugging effort? I can't believe it helps readability.

I have another example that uses FP locals and is recursive, so moving
them to FVARIABLEs isn't even workable. I don't see a general approach
other than an auxiliary FP stack implement in user code. Ugh.
dxforth
2022-10-29 05:04:14 UTC
Permalink
Post by Paul Rubin
...
Here is the simplest way I can see to multiply of two complex numbers
: z* { F: a F: b F: c F: d -- F: ac-bd F: ad+bc }
a c f* b d f* f- a d f* b c f* f+ ;
That example could use FVARIABLEs instead of locals but it would be a
lot messier imho. There are no FVALUEs in standard Forth iirc, so you
sure nothing else uses those variables, including by having anything
aliased to the pointers in the variables, and so on. There is no
equivalent of the return stack to stash intermediate FP values, so stack
juggling becomes harder. Maybe it is still possible, but who wants to
spend the debugging effort? I can't believe it helps readability.
How many times will you code Z* ? FWIW FSLIB already has it.
Post by Paul Rubin
I have another example that uses FP locals and is recursive, so moving
them to FVARIABLEs isn't even workable. I don't see a general approach
other than an auxiliary FP stack implement in user code. Ugh.
FP locals isn't supported by any standard. Some folks have suggested
there should be a F>R R>F which would allow recursive f/p variables.
IIRC Wil Baden had a scheme that used memory at HERE as f/p locals.
The situations you present as requiring locals are obscure enough that
there is almost always another way. But you have to want to find it.
Paul Rubin
2022-10-29 21:27:41 UTC
Permalink
Post by dxforth
How many times will you code Z* ? FWIW FSLIB already has it.
Z* is a simple example. The issue comes up with some regularity.
Post by dxforth
FP locals isn't supported by any standard.
Hmm, I didn't realize that, I mostly use gforth. It is unfortunate.
Post by dxforth
Some folks have suggested there should be a F>R R>F which would allow
recursive f/p variables.
That could help, but might as well support FP locals directly.
Post by dxforth
IIRC Wil Baden had a scheme that used memory at HERE as f/p locals.
That sounds pretty messy. Was it hooked into (LOCAL) somehow? What
happens if your function ALLOTs something after making the locals? Does
the space for the locals not get reclaimed?
Post by dxforth
The situations you present as requiring locals are obscure enough that
there is almost always another way. But you have to want to find it.
The examples I gave were real. I don't claim anything "requires" locals
in the sense that finding another way is impossible. But finding and
implementing another way obviously imposes costs, and I don't currently
see an offsetting benefit. Before wanting to find the other way, I'd
first want to know what I'd gain from finding it. If I have a method
that works, why search for ways to make more pain for myself instead
of just getting on with it?
Post by dxforth
In Forth, locals are all about style... Unfortunately Forth was never
designed for it. Compared to data stack, the return stack is the
smaller - intended only to hold return addresses and the occasional
temp. Nested definitions using locals will quickly eat it up.
The return stack being smaller depends on the implementation ofc. Some
implementations (gforth) use a separate locals stack. An optimizing
compiler can in some cases put the locals in registers which is cheaper
than putting them on any stack. They can also be in the heap, which
unlike even the data stack, can have non-contiguous locals areas without
much hassle.

Admittedly I've been using gforth on machines with gigabytes of memory
but I don't see a big issue even on small MCU's. Of course you have to
maintain some awareness of stack usage from locals, but you have to
maintain that awareness for everything else too.
dxforth
2022-10-30 01:59:43 UTC
Permalink
Post by Paul Rubin
Post by dxforth
How many times will you code Z* ? FWIW FSLIB already has it.
Z* is a simple example. The issue comes up with some regularity.
Post by dxforth
FP locals isn't supported by any standard.
Hmm, I didn't realize that, I mostly use gforth. It is unfortunate.
Unfortunate has been the mixed messaging. Moore couldn't be more explicit
locals had no place in Forth. Still, better one can find that out for
oneself. Locals in Forth can be likened to chocolate. Irresistible to
the sweet-toothed.
Paul Rubin
2022-10-30 05:37:43 UTC
Permalink
Post by dxforth
Unfortunate has been the mixed messaging. Moore couldn't be more explicit
locals had no place in Forth.
Moore, I've heard of that guy. Isn't he the one who invented PICK and
ROLL because his Forth didn't have locals? Once you've turned the stack
into an indexible array, you might as well be able to give names to the
slots.
dxforth
2022-10-30 06:16:21 UTC
Permalink
Post by Paul Rubin
Post by dxforth
Unfortunate has been the mixed messaging. Moore couldn't be more explicit
locals had no place in Forth.
Moore, I've heard of that guy. Isn't he the one who invented PICK and
ROLL because his Forth didn't have locals? Once you've turned the stack
into an indexible array, you might as well be able to give names to the
slots.
It appears he found them superfluous:

"The words that manipulate that stack are DUP, DROP and OVER period. There's
no ..., well SWAP is very convenient and you want it, but it isn't a machine
instruction. But no PICK no ROLL, none of the complex operators to let you
index down into the stack."

"It is necessary to have variables."

http://www.ultratechnology.com/1xforth.htm
Marcel Hendrix
2022-10-30 07:25:48 UTC
Permalink
On Sunday, October 30, 2022 at 7:16:24 AM UTC+1, dxforth wrote:
[..]
Post by dxforth
"The words that manipulate that stack are DUP, DROP and OVER period. There's
no ..., well SWAP is very convenient and you want it, but it isn't a machine
instruction. But no PICK no ROLL, none of the complex operators to let you
index down into the stack."
"It is necessary to have variables."
That is simply describing what 'a machine' has available. The final statement
is a logical conclusion.
Post by dxforth
http://www.ultratechnology.com/1xforth.htm
Somebody should restore these tapes. My hunch is that there will
be surprising nuance.

-marcel
Paul Rubin
2022-10-30 07:44:14 UTC
Permalink
Post by Marcel Hendrix
Post by dxforth
http://www.ultratechnology.com/1xforth.htm
Somebody should restore these tapes. My hunch is that there will
be surprising nuance.
I'm sure I watched that video on youtube years ago. It looks like it
has been re-uploaded more recently, by Gavino, a surprising and nice
contribution.


dxforth
2022-10-30 09:20:30 UTC
Permalink
Post by Paul Rubin
Post by Marcel Hendrix
Post by dxforth
http://www.ultratechnology.com/1xforth.htm
Somebody should restore these tapes. My hunch is that there will
be surprising nuance.
I'm sure I watched that video on youtube years ago. It looks like it
has been re-uploaded more recently, by Gavino, a surprising and nice
contribution.
http://youtu.be/pSnNy7IpVMg
No - the unexpurgated version ...


Marcel Hendrix
2022-10-30 10:38:39 UTC
Permalink
On Sunday, October 30, 2022 at 10:20:33 AM UTC+1, dxforth wrote:
[..]
Post by dxforth
http://youtu.be/ZYlOV7K-xOU
I admit it is dead-on with respect to the underlying sentiments
in this discussion :--)

-marcel
S Jack
2022-10-30 12:56:33 UTC
Permalink
Post by dxforth
http://www.ultratechnology.com/1xforth.htm
ColorForth and Moore's vision are intriguing; however, I'm out of time
for transforming into a "moti" and beginning new adventure. If I were
younger, I would eagerly start with ColorForth and begin some endeavor of
UXN like "Past proofing".
--
me
dxforth
2022-10-30 23:05:37 UTC
Permalink
Post by S Jack
Post by dxforth
http://www.ultratechnology.com/1xforth.htm
ColorForth and Moore's vision are intriguing; however, I'm out of time
for transforming into a "moti" and beginning new adventure. If I were
younger, I would eagerly start with ColorForth and begin some endeavor of
UXN like "Past proofing".
Chasing visions is likely to bring one back to where one started.

https://en.wikisource.org/wiki/Time_and_the_Gods#The_Sorrow_of_Search
Paul Rubin
2022-10-30 23:48:55 UTC
Permalink
Post by dxforth
"The words that manipulate that stack are DUP, DROP and OVER
period. There's no ..., well SWAP is very convenient and you want it,
but it isn't a machine instruction. But no PICK no ROLL, none of the
complex operators to let you index down into the stack."
PICK and ROLL have to have come from somewhere: if it wasn't Moore, then
who? And what about ROT, -ROT, >R, and R>?

I get that Moore used VARIABLEs for everything that I'd use locals for.
He also used CODE when Forth got in the way too much.

Back in the 1970s, computers were limited, programs were necessarily
small, and Moore espoused rewriting programs from the ground up instead
of maintaining them. The main problem facing programmers was how to
make their program implement behaviour X, for whatever X happened to be
that day. They'd put a lot of effort into coding and testing X, and
then they were done.

Today, the main problem facing programmers is ensuring the absence of
behaviours rather than their presence. Writing code is way easier than
it used to be, because tools are better and computers are faster. But
maintaining is harder since programs are bigger and have more people
hacking at them. If you use a VARIABLE to save a temporary value used
by a word, then anyone modifying that word, when they see that variable,
now has to check that no other word uses the variable and depends on it
for communication. Or worse, communicates through the memory cell
occupied by that variable but through some sneaky alias. VALUE avoids
aliasing (no idea whether Moore used them), but locals make both issues
go away.

Finally, while I can't doubt Moore's genius, I've never understood the
vision that says the pure stack VM fits every programming problem.
Register allocation and spilling is one of the messier parts of
traditional compilers even when there are plenty of registers that don't
have weird special purposes. In Forth, there are only 3 or so
"registers" (T, N, and R), and each is special. Why be so vehement that
a human rather than a machine should be juggling them? I can understand
taht locals (if supported) complicate the traditional threaded Forth
interpreter, but I don't know how much Moore valued that. I know he did
like stack hardware (his many Forth chips) but that came later.

So the pure "stack plus VARIABLEs" seems "not even wrong" to me. I
don't understand the motivation behind it well enough to disagree per
se. And I've never seen an explanation that made sense. They have all
seemed like grasping at straws.
dxforth
2022-10-31 01:27:49 UTC
Permalink
Post by Paul Rubin
Post by dxforth
"The words that manipulate that stack are DUP, DROP and OVER
period. There's no ..., well SWAP is very convenient and you want it,
but it isn't a machine instruction. But no PICK no ROLL, none of the
complex operators to let you index down into the stack."
PICK and ROLL have to have come from somewhere: if it wasn't Moore, then
who? And what about ROT, -ROT, >R, and R>?
I get that Moore used VARIABLEs for everything that I'd use locals for.
You use locals because it's easier and more-or-less idiot-proof. Moore
claims it leads to inefficiency. Can't speak for his use but if I have
to use a variable as a temp, I do so sparingly - unlike locals which are
largely about 'readability' and therefore quantity.
Post by Paul Rubin
He also used CODE when Forth got in the way too much.
Don't know but when it comes to apps there's has to be a good enough
reason before I'll use CODE.
Post by Paul Rubin
Today, the main problem facing programmers is ensuring the absence of
behaviours rather than their presence. Writing code is way easier than
it used to be, because tools are better and computers are faster. But
maintaining is harder since programs are bigger and have more people
hacking at them. If you use a VARIABLE to save a temporary value used
by a word, then anyone modifying that word, when they see that variable,
now has to check that no other word uses the variable and depends on it
for communication. Or worse, communicates through the memory cell
occupied by that variable but through some sneaky alias. VALUE avoids
aliasing (no idea whether Moore used them), but locals make both issues
go away.
I think you overstate such use of variables, but how are they a problem
when defined local to the routine which uses it?
Post by Paul Rubin
Finally, while I can't doubt Moore's genius, I've never understood the
vision that says the pure stack VM fits every programming problem.
Saving the world is tough - even for the newer programming systems you
talk about. For me, it's enough that I've been able to create a tool
I'm both comfortable using and is challenging at the same time.
Paul Rubin
2022-10-31 02:04:49 UTC
Permalink
Post by dxforth
You use locals because it's easier and more-or-less idiot-proof. Moore
claims it leads to inefficiency.
Yes, idiot proofing is always a good goal when programming ;). The
inefficiency depends on the implementation, I'd expect. It may also be
that code execution profiles have changed since Moore's heyday. DJB
made that claim in an article called "Demise Of The Optimizing Compiler"
or some such. Basically the execution time of programs nowadays is
concentrated almost entirely in a few hot spots. Those spots may have
to be tuned for efficiency, but the rest of the program's efficiency
almost doesn't matter. It was different in the 1970s, where efficiency
mattered everywhere in the program.
Post by dxforth
if I have to use a variable as a temp, I do so sparingly - unlike
locals which are largely about 'readability' and therefore quantity.
Not sure what you mean about quantity, but I look at the gforth manual's
locals version of MAX compared with a traditional one (both untested):

: max1 ( a b -- max ) 2dup > if drop else nip then ;
: max2 { a b -- max } a b > if a else b then ;

the locals version has less source code and (if compiled) maybe less
object code as well.

Similarly you recently posted elsewhere (158 source chars):

: concat ( a1 u1 ... an un n dest -- dest len )
0 0 begin 2>r over while swap 1- swap 2swap repeat
swap begin 2r> over while 2swap +string repeat 2drop ;

A locals version (untested) is 165 chars mostly due to extra whitespace
that could be squished out:

: concat2 ( a1 u1 ... an un n dest -- dest len )
{ n dest } 0 { len }
n 0 ?do
{ a u } a u dest len +string
u len + to len
loop
dest len ;

I can't begin to understand the stack juggling version so can't be sure
the two do the same thing. I also don't see how to implement 2SWAP with
just DUP, DROP, OVER. I guess it could be done with variables.
Post by dxforth
I think you overstate such use of variables, but how are they a problem
when defined local to the routine which uses it?
How do you do that? Variables are always globals, I thought. Another
issue with them that I forgot to mention is non-reentrancy. Like if you
implement 2SWAP with variables and an interrupt handler uses it while
another call to 2SWAP is in progress, you get a Heisenbug. Oops.
Post by dxforth
Saving the world is tough - even for the newer programming systems you
talk about. For me, it's enough that I've been able to create a tool
I'm both comfortable using and is challenging at the same time.
I like my tools to be comfortable and powerful. Challenge should come
from using them on bigger and tougher problems, not from making even
easy problems a pain to solve.
Doug Hoffman
2022-10-31 02:44:01 UTC
Permalink
Post by Paul Rubin
I also don't see how to implement 2SWAP with
just DUP, DROP, OVER.
Use the return stack as a "local variable holder"
and use ROT and SWAP.

: 2swap ( x1 x2 x3 x4 -- x3 x4 x1 x2 )
Post by Paul Rubin
r rot rot r> swap rot swap ;
Truthfully, I don't see much difference between conventional locals
and using the return stack for same. With the latter you don't get names
though it is likely more efficient. But IMO is just as "impure" as locals.
Locals are much easier to use/debug/maintain-changes. If you are
not resource limited and the locals version is fast enough then I
leave it at that.

-Doug
Paul Rubin
2022-10-31 02:54:27 UTC
Permalink
Post by Doug Hoffman
I also don't see how to implement 2SWAP with just DUP, DROP, OVER.
Use the return stack as a "local variable holder"
and use ROT and SWAP.
I'm doing by the quote from Chuck Moore about DUP, DROP, and OVER (and
maybe SWAP) being the only legit stack primitives, so no ROT. He does
use the return stack in his own code though.
Post by Doug Hoffman
Truthfully, I don't see much difference between conventional locals
and using the return stack for same.
There is no RPICK in standard Forth, so you can't get at the internal
slots. Moore even frowns on regular PICK. Also, you have to be able to
pop the locals if the word exits through an exception. (LOCAL) had to
be added to the standard to allow that, since the VM has to handle the
unwinding in that situation.
none) (albert
2022-10-31 10:25:59 UTC
Permalink
Post by Paul Rubin
Post by Doug Hoffman
I also don't see how to implement 2SWAP with just DUP, DROP, OVER.
Use the return stack as a "local variable holder"
and use ROT and SWAP.
I'm doing by the quote from Chuck Moore about DUP, DROP, and OVER (and
maybe SWAP) being the only legit stack primitives, so no ROT. He does
use the return stack in his own code though.
Once strings are introduced as a proper pair (not zero-ended)
you need SPSWAP and PSSWAP for swapping a single and a double.
I can't see Chuck Moore object to that.
I use these preferably, because I (like Chuck) have difficulty
remembering the stack diagram of ROT.
Post by Paul Rubin
Post by Doug Hoffman
Truthfully, I don't see much difference between conventional locals
and using the return stack for same.
There is no RPICK in standard Forth, so you can't get at the internal
slots. Moore even frowns on regular PICK. Also, you have to be able to
pop the locals if the word exits through an exception. (LOCAL) had to
be added to the standard to allow that, since the VM has to handle the
unwinding in that situation.
PICK is no use. It does something right though, it leaves parameters
on the data stack instead of copying to un undisclosed location.
I experimented with a frame pointer that marks
a position on the stack and names data items from there with an offset.
This works but I was not convinced to show it here on comp.lang.forth.
(but a one-screener and not invasive.)

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
***@spe&ar&c.xs4all.nl &=n http://home.hccnet.nl/a.w.m.van.der.horst
Doug Hoffman
2022-10-31 15:30:31 UTC
Permalink
Post by Paul Rubin
Post by Doug Hoffman
I also don't see how to implement 2SWAP with just DUP, DROP, OVER.
Use the return stack as a "local variable holder"
and use ROT and SWAP.
I'm doing by the quote from Chuck Moore about DUP, DROP, and OVER (and
maybe SWAP) being the only legit stack primitives, so no ROT. He does
use the return stack in his own code though.
Post by Doug Hoffman
Truthfully, I don't see much difference between conventional locals
and using the return stack for same.
There is no RPICK in standard Forth, so you can't get at the internal
slots. Moore even frowns on regular PICK. Also, you have to be able to
pop the locals if the word exits through an exception. (LOCAL) had to
be added to the standard to allow that, since the VM has to handle the
unwinding in that situation.
Meh. What Moore has stated in recent decades also has little to do with
standard Forth.

My point, which I should have stated explicitly, is that detractors of locals
generally say that their use impedes factoring because items are
not on the data stack. But often these same people have no issue
with putting items on the return stack, which must also impede factoring
for the same reason. Though I really don't subscribe to the impede
factoring issue in either case.

Frequently-used core-type words like 2swap should not use locals.
In higher level problem specific words locals have a place in the code I write.

That was fun. What next, a debate about blocks vs text files?

-Doug
minf...@arcor.de
2022-10-31 16:10:55 UTC
Permalink
Post by Doug Hoffman
Post by Paul Rubin
Post by Doug Hoffman
I also don't see how to implement 2SWAP with just DUP, DROP, OVER.
Use the return stack as a "local variable holder"
and use ROT and SWAP.
I'm doing by the quote from Chuck Moore about DUP, DROP, and OVER (and
maybe SWAP) being the only legit stack primitives, so no ROT. He does
use the return stack in his own code though.
Post by Doug Hoffman
Truthfully, I don't see much difference between conventional locals
and using the return stack for same.
There is no RPICK in standard Forth, so you can't get at the internal
slots. Moore even frowns on regular PICK. Also, you have to be able to
pop the locals if the word exits through an exception. (LOCAL) had to
be added to the standard to allow that, since the VM has to handle the
unwinding in that situation.
Meh. What Moore has stated in recent decades also has little to do with
standard Forth.
My point, which I should have stated explicitly, is that detractors of locals
generally say that their use impedes factoring because items are
not on the data stack. But often these same people have no issue
with putting items on the return stack, which must also impede factoring
for the same reason. Though I really don't subscribe to the impede
factoring issue in either case.
Frequently-used core-type words like 2swap should not use locals.
In higher level problem specific words locals have a place in the code I write.
That was fun. What next, a debate about blocks vs text files?
I'd suggest state-smartness. Seems to attract flies too.
dxforth
2022-11-01 02:18:36 UTC
Permalink
Post by Doug Hoffman
What Moore has stated in recent decades also has little to do with
standard Forth.
He did say the latter "did not describe Forth, but a language with the
same name"! Not sure 'language' is even appropriate - 'unrealized ideal'
would be closer. One would be hard pressed to find a language as drawn-out
and divisive as 'Standard Forth' (any incarnation). If anyone is writing
applications 'in Forth', it's because they've stopped pursuing 'the vision
splendid', accepted what they have, and the short time in which they have
to do it.
dxforth
2022-11-01 03:46:52 UTC
Permalink
Post by Doug Hoffman
My point, which I should have stated explicitly, is that detractors of locals
generally say that their use impedes factoring because items are
not on the data stack. But often these same people have no issue
with putting items on the return stack, which must also impede factoring
for the same reason.
'These same people' are judicious in their use of return stack. They don't
have so many items that they need to name them, or require the system to
manage it for them :)
Doug Hoffman
2022-11-01 11:44:33 UTC
Permalink
'These same people' are judicious in their use of return stack. They don't
have so many items that they need to name them, or require the system to
manage it for them :)
Yes. As it should be. I'm a habitual user of the return stack when it is all
I need (as opposed to resorting to locals). Very handy and efficient.

-Doug
dxforth
2022-11-01 14:23:02 UTC
Permalink
Post by Doug Hoffman
'These same people' are judicious in their use of return stack. They don't
have so many items that they need to name them, or require the system to
manage it for them :)
Yes. As it should be. I'm a habitual user of the return stack when it is all
I need (as opposed to resorting to locals). Very handy and efficient.
Are you saying Forth is inadequate - or that it's your choice to extend it
with locals? If Forth is inadequate it would mean I've wasted 40 years on
a bum steer. Locals users too.
Hans Bezemer
2022-11-02 11:04:56 UTC
Permalink
Post by dxforth
Are you saying Forth is inadequate - or that it's your choice to extend it
with locals? If Forth is inadequate it would mean I've wasted 40 years on
a bum steer. Locals users too.
True. Thinking about programming in a Forth way has significantly influenced my
C style. Now I need to go the opposite way: translating C to Forth, so I can
have loads of local variables - and write functions (not words) of five pages
without ever posing the question: "Do I really need those - or can I do something
clever to eradicate them?"

See: https://sourceforge.net/p/forth-4th/wiki/This%20is%20Forth/ and
https://sourceforge.net/p/forth-4th/wiki/Understand%20your%20algorithm/

Hans Bezemer
Brian Fox
2022-11-02 13:53:34 UTC
Permalink
Post by Hans Bezemer
Post by dxforth
Are you saying Forth is inadequate - or that it's your choice to extend it
with locals? If Forth is inadequate it would mean I've wasted 40 years on
a bum steer. Locals users too.
True. Thinking about programming in a Forth way has significantly influenced my
C style. Now I need to go the opposite way: translating C to Forth, so I can
have loads of local variables - and write functions (not words) of five pages
without ever posing the question: "Do I really need those - or can I do something
clever to eradicate them?"
See: https://sourceforge.net/p/forth-4th/wiki/This%20is%20Forth/ and
https://sourceforge.net/p/forth-4th/wiki/Understand%20your%20algorithm/
Hans Bezemer
Off topic but ever since you started making videos I can't read your
written stuff without hearing your voice in my head. :-)))
(with the interesting "Nederlandse" accent of course)
Doug Hoffman
2022-11-03 10:40:09 UTC
Permalink
Post by Doug Hoffman
Post by dxforth
'These same people' are judicious in their use of return stack.
As am I and I believe everyone else I've seen use the return stack
to stash data stack items.
Post by Doug Hoffman
I'm a habitual user of the return stack when it is all
I need (as opposed to resorting to locals).
As am I.
Did I say that all colon definitions I write use locals?
Are you saying ... that it's your choice to extend [Forth]
with locals?
You give me too much credit. I have never extended a Forth
to have locals. The experts that created VFXForth, SwiftForth,
iForth, Gforth, iMops, and other Forths have already done it.
If Forth is inadequate it would mean I've wasted 40 years on
a bum steer. Locals users too.
I don't follow that logic. How could the way I, or anyone else,
use Forth impact whether or not you have wasted your time?

-Doug
Hans Bezemer
2022-11-03 18:41:53 UTC
Permalink
Post by Doug Hoffman
As am I and I believe everyone else I've seen use the return stack
to stash data stack items.
That's only half the story. I learned this trick from the guy who wrote the FIG
editor - you don't randomly place stuff there, you place the stuff that is either
constant or almost constant.

So - you triage your parameters. Those who change a LOT go on the
data stack and those who (barely) change go on the return stack.
In 4tH, you have R@, R'@ and R"@. And then it starts to make even MORE
sense. You can also map those with I, I' and J - with NO overhead.

The TORS contains the stuff that barely changes., while R'@ and R"@ are
pure constants.
Post by Doug Hoffman
Did I say that all colon definitions I write use locals?
CONCAT didn't - I loved it. You see, you are perfectly capable to do better ;-)

I understand the sentiment about "throwing away the experience". Forth
not only made me a better programmer, it also forced me to delve deeper
in an algorithm in order to REALLY understand it. LOCALs allow a much
shallower way of implementing it. It also allows me to waste resources -
simply because I can away with it.

If you'd asked me 10 - 20 years ago to make a preprocessor that exceeds
M4 I wouldn't have known where to begin. In Forth, it grew organically. And
at some moment I thought "Wow, did I really make this?"

I also wrote what must be one of the smallest DBMSes (400 lines or so). Again,
a task so daunting in any other languages I probably never would have attempted.

And all this because every single element is forced to be so small, it seems
almost trivial. And all there is is the flow of data through the stack. Every tiny
mistake is punished by a crash. That will teach you discipline.

There is no such mechanism at work when using locals. All words are brute
connected by an interface that is the stack frame. And instead of flowing
almost invisibly through the program there is this point where data becomes
painfully explicit - like C.

It is not a matter of passing parameters in a different way, it's a way of thinking
that is radically different. It is that that newbies fail to comprehend - and often
even fail to master. That's why they drop out.

IMHO it isn't a different way to pass and handle data, it is a different and way of
thinking and designing. And LOCALs aren't about "making things easier", but
a concession to make things workable for lesser gods and sloppy programmers.

I have a lib that does LOCALs - but I never use it. I have embraced this other
way of approaching the problem - and it works for me. I don't see any use in
dropping it (and yes, at times I wrestle with the stack as well) and trade it for
an IMHO inferior way of converting an idea into working code.

Hans Bezemer
Marcel Hendrix
2022-11-03 22:34:22 UTC
Permalink
On Thursday, November 3, 2022 at 7:41:55 PM UTC+1, ***@gmail.com wrote:
[..]
and trade it for an IMHO inferior way of converting an idea into working code.
It was demonstrated to you (e.g., by Peter Fälth) that it doesn't matter
how you write the Forth source, it converts to the same code, given a
reasonable compiler.

-marcel
Hans Bezemer
2022-11-03 23:42:15 UTC
Permalink
Post by Marcel Hendrix
[..]
If you're talking about a mechanical proces like code generation
You're missing the entire point I'm trying to make.

Hans Bezemer
Post by Marcel Hendrix
and trade it for an IMHO inferior way of converting an idea into working code.
It was demonstrated to you (e.g., by Peter Fälth) that it doesn't matter
how you write the Forth source, it converts to the same code, given a
reasonable compiler.
-marcel
dxforth
2022-11-04 00:15:18 UTC
Permalink
Post by Marcel Hendrix
[..]
and trade it for an IMHO inferior way of converting an idea into working code.
It was demonstrated to you (e.g., by Peter Fälth) that it doesn't matter
how you write the Forth source, it converts to the same code, given a
reasonable compiler.
So now one is dependent on "a reasonable compiler". GCC here we come.
Paul Rubin
2022-11-04 00:47:21 UTC
Permalink
Post by dxforth
So now one is dependent on "a reasonable compiler". GCC here we come.
You don't have to depend on a compiler at all, but without one, you're
running interpreted Forth which is maybe 10x slower. That 10x is not
too bad, compared to the 50x or more slowdown you get from Python. With
a rudimentary compiler you might get 3x instead of 10x.

Maybe coding style (locals or whatever) can change the 10x to 15x or
vice versa. If you care about that, using a compiler sounds like a
better way to recover the missed speed than warping your mind trying to
optimize interpreted code at the source level. And as usual, if you
have to optimize at all, it is likely to just be in a few hot spots of
the program.

Remember too that even an almost minimal Forth target these days
(Raspberry Pi Pico) is probably 100x faster than the 16 bit
minicomputers where Forth originated. That gives you a lot of breathing
space. There is no need for 3+length dictionaries any more, and we
can also use more relaxed and expressive coding styles.
P Falth
2022-11-04 06:46:34 UTC
Permalink
Post by Marcel Hendrix
[..]
and trade it for an IMHO inferior way of converting an idea into working code.
It was demonstrated to you (e.g., by Peter Fälth) that it doesn't matter
how you write the Forth source, it converts to the same code, given a
reasonable compiler.
-marcel
But only if you have the right example code to compile!
In this case my code generator produces the same result with locals or without.
When you have a break in a basic block ( a call, a controlflow point) the locals
needs to be placed in a known place, in the case of lxf the return stack.
This generates extra code.
The problem is the way locals are specified, they have a scope of the whole
word, they are released just at the end of the definition.
Stack parameters have instead a dynamic scope, they are duped, or droped or
consumed as arguments to a word as decided by the programmer.

Of course a multipass compiler could determine the scope of each local and
improve the code generation. Lxf is just a simple 1 pass compiler. The reason
you can make code generation for locals look good with lxf is that they are
implementedto rest on the return stack. I put in some effort to get a good
code generation for the R-stack use. The locals was for free after that.

In my own Forth programming I do not use locals. I have not seen the need
for them and I do not like the way they are specified to look like a comment!
In lxf I have instead another feature n LBUFFER: <name> that reserves n
byte on the return stack that can be accessed with ! @ at <name>. Just like a
variable. This is useful to pass structures to systems call for example.

Best Regards
Peter
minf...@arcor.de
2022-11-04 08:27:13 UTC
Permalink
Post by P Falth
In my own Forth programming I do not use locals. I have not seen the need
for them and I do not like the way they are specified to look like a comment!
In lxf I have instead another feature n LBUFFER: <name> that reserves n
variable. This is useful to pass structures to systems call for example.
These are good points:
a) when there is no need, then there is no use case
b) standard Forth locals syntax looks strange
c) standard Forth locals are too limited, practical applications often have to deal
with data objects like structures and (dynamic) strings/arrays; at least fp-locals
should be there

As long as one does only "scratch on the ground" i.e. can solve one's task with
some standard words and only integer addresses and numbers as arguments,
locals wouldn't pay their rent.

I am old school myseld, and by myself I use a lot of paper and pencil to understand/tune
data structures and algorithms before I fire up the computer. Then I am rather sure
to have got the thinking right and am happy to leave as much bit&byte lifting as possible
to the compiler. It is a waste of programmer's time to burden him with syntax mistakes.

Whether manual deep factoring is good programming or not, is a matter of personal
likes and experience. Moore and his acolytes live with their experience. There
are good cases where deep factoring works, there are many cases where deep
factoring would break data flows and algorithms apart. Always keep in mind that
code needs to stay maintainable, particularly when you work in a team, and it
must not require solving jigsaw code puzzles.
dxforth
2022-11-04 05:05:51 UTC
Permalink
Post by Doug Hoffman
Post by dxforth
...
If Forth is inadequate it would mean I've wasted 40 years on
a bum steer. Locals users too.
I don't follow that logic. How could the way I, or anyone else,
use Forth impact whether or not you have wasted your time?
How else to describe a language that folks say they can't use unless
locals are added.
Paul Rubin
2022-11-04 05:38:49 UTC
Permalink
Post by dxforth
How else to describe a language that folks say they can't use unless
locals are added.
No software is ever finished until its last user is dead. So all
software is inadequate in one way or another. We do what we can in
order to get by anyway.
dxforth
2022-11-04 07:42:27 UTC
Permalink
Post by Paul Rubin
Post by dxforth
How else to describe a language that folks say they can't use unless
locals are added.
No software is ever finished until its last user is dead. So all
software is inadequate in one way or another. We do what we can in
order to get by anyway.
Oh, I'd say organized Forth is absolutely dead. I agree with your
last line.
Paul Rubin
2022-11-05 03:51:38 UTC
Permalink
Post by dxforth
Oh, I'd say organized Forth is absolutely dead. I agree with your
last line.
Well, what was organized Forth trying to do when it was alive? Was it
trying to crank out working code by hook or by crook, or was it trying
to pursue a vision of stack combinator purity? If it was merely trying
to crank out code, I still think locals help with that, and haven't seen
persuasive reasoning to the contrary. If it was trying to pursue purity
then obviously locals are a no-no, ok fine. It may make me a philistine
but I don't think the vision has held up that well. Lambda calculus has
held up a lot better.

In Ting's writings I see both approaches. In Jeff Fox's, I can't always
tell what he is getting at. And Moore sometimes seems to be operating
in a different world than the rest of us.

Hans Bezemer
2022-10-31 08:40:08 UTC
Permalink
Post by Doug Hoffman
Post by Paul Rubin
I also don't see how to implement 2SWAP with
just DUP, DROP, OVER.
Use the return stack as a "local variable holder"
and use ROT and SWAP.
: 2swap ( x1 x2 x3 x4 -- x3 x4 x1 x2 )
Post by Paul Rubin
r rot rot r> swap rot swap ;
Really?! Even my computer can do better!

$ pp4th -x stackopt.4th abcd cdab
- Trying a 1 word solution..
No solutions.
- Trying a 2 word solution..
No solutions.
- Trying a 3 word solution..
No solutions.
- Trying a 4 word solution..
rot >r rot r>

Hans Bezemer
Doug Hoffman
2022-10-31 15:10:34 UTC
Permalink
Post by Hans Bezemer
Post by Doug Hoffman
: 2swap ( x1 x2 x3 x4 -- x3 x4 x1 x2 )
Post by Paul Rubin
r rot rot r> swap rot swap ;
Really?! Even my computer can do better!
rot >r rot r>
Thanks for pointing that out Hans.
VFX's optimizer lulled me into thinking that
what I showed was efficient. My bad!

-Doug
Anton Ertl
2022-10-31 17:09:40 UTC
Permalink
Post by Doug Hoffman
Post by Hans Bezemer
Post by Doug Hoffman
: 2swap ( x1 x2 x3 x4 -- x3 x4 x1 x2 )
Post by Paul Rubin
r rot rot r> swap rot swap ;
Really?! Even my computer can do better!
rot >r rot r>
Thanks for pointing that out Hans.
VFX's optimizer lulled me into thinking that
what I showed was efficient. My bad!
r rot rot r> swap rot swap rot >r rot r> 2swap
PUSH RBX PUSH QWORD [RBP+08] MOV RDX, RBX
POP RDX POP RDX MOV RBX, [RBP+08]
MOV RBX, [RBP+08] MOV RCX, [RBP+10] MOV [RBP+08], RDX
MOV [RBP+08], RDX MOV RAX, [RBP] MOV RDX, [RBP+10]
MOV RDX, [RBP+10] MOV [RBP], RCX MOV RCX, [RBP]
MOV RCX, [RBP] MOV [RBP+10], RAX MOV [RBP], RDX
MOV [RBP], RDX MOV [RBP+08], RBX MOV [RBP+10], RCX
MOV [RBP+10], RCX MOV RBX, RDX RET/NEXT
RET/NEXT RET/NEXT 28 Bytes
27 bytes 29 Bytes

For comparison, lxf is analytical about the return stack and compiles
them all to the same code (21 bytes, but 32-bit code rather than
Post by Doug Hoffman
r rot rot r> swap rot swap rot >r rot r> 2swap
mov eax , [ebp+4h] mov eax , [ebp+4h] mov eax , [ebp+4h]
mov [ebp+4h] , ebx mov [ebp+4h] , ebx mov [ebp+4h] , ebx
mov ebx , eax mov ebx , eax mov ebx , eax
mov eax , [ebp+8h] mov eax , [ebp+8h] mov eax , [ebp+8h]
mov ecx , [ebp] mov ecx , [ebp] mov ecx , [ebp]
mov [ebp+8h] , ecx mov [ebp+8h] , ecx mov [ebp+8h] , ecx
mov [ebp] , eax mov [ebp] , eax mov [ebp] , eax
ret near ret near ret near

- 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
Marcel Hendrix
2022-10-31 17:35:07 UTC
Permalink
On Monday, October 31, 2022 at 6:20:55 PM UTC+1, Anton Ertl wrote:
[..]
Post by Anton Ertl
r rot rot r> swap rot swap rot >r rot r> 2swap
[..]
Post by Anton Ertl
For comparison, lxf is analytical about the return stack and compiles
them all to the same code (21 bytes, but 32-bit code rather than
[..]
FORTH> : tt >r rot rot r> swap rot swap rot >r rot r> 2swap ; ok
FORTH> ' tt idis
$0133DC40 : tt
$0133DC4A pop rbx
$0133DC4B pop rdi
$0133DC4C pop rax
$0133DC4D pop rdx
$0133DC4E push rdi
$0133DC4F push rbx
$0133DC50 push rdx
$0133DC51 push rax
$0133DC52 ;
FORTH> : tt2 tt 2swap 2swap 2swap .S ; ok
FORTH> see tt2
Flags: ANSI
$0133DCC0 : tt2
$0133DCCA pop rbx
$0133DCCB pop rdi
$0133DCCC pop rax
$0133DCCD mov rdx, [rsp] qword
$0133DCD1 push rax
$0133DCD2 push rdi
$0133DCD3 push rbx
$0133DCD4 jmp .S+10 ( $012BEA0A ) offset NEAR
$0133DCD9 ;
FORTH> : tt3 1. 2. tt tt tt .S ; ok
FORTH> see tt3
Flags: ANSI
$0133DCC0 : tt3
$0133DCCA push 2 b#
$0133DCCC push 0 b#
$0133DCCE push 1 b#
$0133DCD0 push 0 b#
$0133DCD2 jmp .S+10 ( $012BEA0A ) offset NEAR
$0133DCD7 ;

-marcel
P Falth
2022-10-31 17:45:23 UTC
Permalink
Post by Doug Hoffman
Post by Hans Bezemer
Post by Doug Hoffman
: 2swap ( x1 x2 x3 x4 -- x3 x4 x1 x2 )
Post by Paul Rubin
r rot rot r> swap rot swap ;
Really?! Even my computer can do better!
rot >r rot r>
Thanks for pointing that out Hans.
VFX's optimizer lulled me into thinking that
what I showed was efficient. My bad!
r rot rot r> swap rot swap rot >r rot r> 2swap
PUSH RBX PUSH QWORD [RBP+08] MOV RDX, RBX
POP RDX POP RDX MOV RBX, [RBP+08]
MOV RBX, [RBP+08] MOV RCX, [RBP+10] MOV [RBP+08], RDX
MOV [RBP+08], RDX MOV RAX, [RBP] MOV RDX, [RBP+10]
MOV RDX, [RBP+10] MOV [RBP], RCX MOV RCX, [RBP]
MOV RCX, [RBP] MOV [RBP+10], RAX MOV [RBP], RDX
MOV [RBP], RDX MOV [RBP+08], RBX MOV [RBP+10], RCX
MOV [RBP+10], RCX MOV RBX, RDX RET/NEXT
RET/NEXT RET/NEXT 28 Bytes
27 bytes 29 Bytes
For comparison, lxf is analytical about the return stack and compiles
them all to the same code (21 bytes, but 32-bit code rather than
Post by Doug Hoffman
r rot rot r> swap rot swap rot >r rot r> 2swap
mov eax , [ebp+4h] mov eax , [ebp+4h] mov eax , [ebp+4h]
mov [ebp+4h] , ebx mov [ebp+4h] , ebx mov [ebp+4h] , ebx
mov ebx , eax mov ebx , eax mov ebx , eax
mov eax , [ebp+8h] mov eax , [ebp+8h] mov eax , [ebp+8h]
mov ecx , [ebp] mov ecx , [ebp] mov ecx , [ebp]
mov [ebp+8h] , ecx mov [ebp+8h] , ecx mov [ebp+8h] , ecx
mov [ebp] , eax mov [ebp] , eax mov [ebp] , eax
ret near ret near ret near
Also the locals version will produce the same code!

: s2 {: a b c d -- c d a b :} c d a b ; ok
see s2
A4A5D4 409CB2 21 C80000 5 normal S2

409CB2 8B4504 mov eax , [ebp+4h]
409CB5 895D04 mov [ebp+4h] , ebx
409CB8 8BD8 mov ebx , eax
409CBA 8B4508 mov eax , [ebp+8h]
409CBD 8B4D00 mov ecx , [ebp]
409CC0 894D08 mov [ebp+8h] , ecx
409CC3 894500 mov [ebp] , eax
409CC6 C3 ret near
ok

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
Marcel Hendrix
2022-10-31 20:18:17 UTC
Permalink
On Monday, October 31, 2022 at 6:45:25 PM UTC+1, P Falth wrote:
[..]
Post by P Falth
Also the locals version will produce the same code!
: s2 {: a b c d -- c d a b :} c d a b ; ok
see s2
A4A5D4 409CB2 21 C80000 5 normal S2
409CB2 8B4504 mov eax , [ebp+4h]
409CB5 895D04 mov [ebp+4h] , ebx
409CB8 8BD8 mov ebx , eax
409CBA 8B4508 mov eax , [ebp+8h]
409CBD 8B4D00 mov ecx , [ebp]
409CC0 894D08 mov [ebp+8h] , ecx
409CC3 894500 mov [ebp] , eax
409CC6 C3 ret near
In this particular case I can match that (but not in general).

FORTH> : s2 params| a b c d | c d a b ; ok
FORTH> see s2
Flags: TOKENIZE, ANSI
: s2 (4)PARAMS %c %d %a %b ; ok
FORTH> ' s2 idis
$0133E080 : s2
$0133E08A pop rbx
$0133E08B pop rdi
$0133E08C pop rax
$0133E08D pop rdx
$0133E08E push rdi
$0133E08F push rbx
$0133E090 push rdx
$0133E091 push rax
$0133E092 ;
FORTH> : test 1 2 3 4 s2 .S ; ok
FORTH> see test
Flags: ANSI
$0133E100 : test
$0133E10A push 3 b#
$0133E10C push 4 b#
$0133E10E push 1 b#
$0133E110 push 2 b#
$0133E112 jmp .S+10 ( $012BEA0A ) offset NEAR

-marcel
dxforth
2022-10-31 03:18:07 UTC
Permalink
Post by Paul Rubin
Post by dxforth
if I have to use a variable as a temp, I do so sparingly - unlike
locals which are largely about 'readability' and therefore quantity.
Not sure what you mean about quantity, but I look at the gforth manual's
: max1 ( a b -- max ) 2dup > if drop else nip then ;
: max2 { a b -- max } a b > if a else b then ;
the locals version has less source code and (if compiled) maybe less
object code as well.
I'd have written:

: max1 ( a b -- max ) 2dup < if swap then drop ;

Compare them under VFX and see what you get.
Post by Paul Rubin
: concat ( a1 u1 ... an un n dest -- dest len )
0 0 begin 2>r over while swap 1- swap 2swap repeat
swap begin 2r> over while 2swap +string repeat 2drop ;
A locals version (untested) is 165 chars mostly due to extra whitespace
: concat2 ( a1 u1 ... an un n dest -- dest len )
{ n dest } 0 { len }
n 0 ?do
{ a u } a u dest len +string
u len + to len
loop
dest len ;
I can't begin to understand the stack juggling version so can't be sure
the two do the same thing.
Ditto for me for the locals version. Perhaps it's a matter of what one
is used to? If one's goal is to jump between languages using more or
less the same code, then maybe you'll want to use locals in Forth.
Personally I don't and think it a terrible idea.
Post by Paul Rubin
Post by dxforth
I think you overstate such use of variables, but how are they a problem
when defined local to the routine which uses it?
How do you do that? Variables are always globals, I thought.
Give an example how it's a problem.
Post by Paul Rubin
Another
issue with them that I forgot to mention is non-reentrancy. Like if you
implement 2SWAP with variables and an interrupt handler uses it while
another call to 2SWAP is in progress, you get a Heisenbug. Oops.
You could make it a USER variable :)
Hans Bezemer
2022-10-31 08:41:20 UTC
Permalink
Post by Paul Rubin
Yes, idiot proofing is always a good goal when programming ;).
According to both Moore and Dijkstra, idiots shouldn't be programming.

Hans Bezemer
dxforth
2022-10-31 11:03:12 UTC
Permalink
Post by Hans Bezemer
Post by Paul Rubin
Yes, idiot proofing is always a good goal when programming ;).
According to both Moore and Dijkstra, idiots shouldn't be programming.
I felt the same way after quashing a bug that randomly deleted files :)
none) (albert
2022-10-31 10:15:26 UTC
Permalink
Post by dxforth
Post by Paul Rubin
Post by dxforth
"The words that manipulate that stack are DUP, DROP and OVER
period. There's no ..., well SWAP is very convenient and you want it,
but it isn't a machine instruction. But no PICK no ROLL, none of the
complex operators to let you index down into the stack."
PICK and ROLL have to have come from somewhere: if it wasn't Moore, then
who? And what about ROT, -ROT, >R, and R>?
I get that Moore used VARIABLEs for everything that I'd use locals for.
You use locals because it's easier and more-or-less idiot-proof. Moore
claims it leads to inefficiency. Can't speak for his use but if I have
to use a variable as a temp, I do so sparingly - unlike locals which are
largely about 'readability' and therefore quantity.
locals is not enough, you need dlocals,flocals
You are actually brains washed by c.
"
#include whatever

int gcd( int a, int b)
{ float hesp;
double x;
long int x ;
hesp inteact(a, x);
return items(a, hesp);
}
"
You can have this type behaviour. with

VOCABULARY gcdlist ALSO gcdlist

int a int b
float hesp
double x
long_int x
: gcd
b ! a !
a @ x 2@ hesp F@ inteact hesp F!
a @ ,hesp F@ items
;

previous

It is good that you are not looking at Pascal, then
we would have local definitions adding to the
abominations of the local type zoo (LOCAL FLOCAL DLOCAL DFLOCAL ..)

In the above picture we could easily add after
long_int x
the line
: inteact do this do that mysterious F@ ;

proving the superiority of classical approach of forth.

I can't stand the idea that so,eome duplicates VARIABLE
into VALUES (it is sooo much works to fetch them..)
and then LOCAL VALUES (duplicating the scope restriction
that are present in Forth).

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
***@spe&ar&c.xs4all.nl &=n http://home.hccnet.nl/a.w.m.van.der.horst
Stephen Pelc
2022-10-31 11:49:08 UTC
Permalink
Post by Paul Rubin
In Forth, there are only 3 or so
"registers" (T, N, and R), and each is special. Why be so vehement that
a human rather than a machine should be juggling them?
If you consider the whole Forth VM there are rather more:
TOS, NOS, PSP \ T, N, data stack pointer
TOR, RSP \ R, return stack pointer
TOF, FSP \ top of floats, float stack pointer
LP \ local pointer
UP \ user area pointer
For stack underflow checking you need three more. On a modern optimising
Forth.
it's easy to find oneself using six to eight CPU registers for the VM and the
same
again for code generation, and a couple of special purpose registers.

Stephen
--
Stephen Pelc, ***@vfxforth.com
MicroProcessor Engineering, Ltd. - More Real, Less Time
133 Hill Lane, Southampton SO15 5AF, England
tel: +44 (0)23 8063 1441, +44 (0)78 0390 3612,
+34 649 662 974
http://www.mpeforth.com - free VFX Forth downloads
a***@math.uni.wroc.pl
2022-11-01 13:19:16 UTC
Permalink
Post by Paul Rubin
Post by dxforth
"The words that manipulate that stack are DUP, DROP and OVER
period. There's no ..., well SWAP is very convenient and you want it,
but it isn't a machine instruction. But no PICK no ROLL, none of the
complex operators to let you index down into the stack."
PICK and ROLL have to have come from somewhere: if it wasn't Moore, then
who? And what about ROT, -ROT, >R, and R>?
I get that Moore used VARIABLEs for everything that I'd use locals for.
He also used CODE when Forth got in the way too much.
Back in the 1970s, computers were limited, programs were necessarily
small, and Moore espoused rewriting programs from the ground up instead
of maintaining them. The main problem facing programmers was how to
make their program implement behaviour X, for whatever X happened to be
that day. They'd put a lot of effort into coding and testing X, and
then they were done.
Today, the main problem facing programmers is ensuring the absence of
behaviours rather than their presence. Writing code is way easier than
it used to be, because tools are better and computers are faster. But
maintaining is harder since programs are bigger and have more people
hacking at them. If you use a VARIABLE to save a temporary value used
by a word, then anyone modifying that word, when they see that variable,
now has to check that no other word uses the variable and depends on it
for communication. Or worse, communicates through the memory cell
occupied by that variable but through some sneaky alias. VALUE avoids
aliasing (no idea whether Moore used them), but locals make both issues
go away.
Finally, while I can't doubt Moore's genius, I've never understood the
vision that says the pure stack VM fits every programming problem.
Register allocation and spilling is one of the messier parts of
traditional compilers even when there are plenty of registers that don't
have weird special purposes. In Forth, there are only 3 or so
"registers" (T, N, and R), and each is special. Why be so vehement that
a human rather than a machine should be juggling them? I can understand
taht locals (if supported) complicate the traditional threaded Forth
interpreter,
I think thatsome voices here vastly overestimate extra complexity.
AFAICS at runtime it is enough to have bunch of extra primitives:
add and subtract from return stack pointer (could be single primitive)
and two primitives fpr each local, one for reading, the other for
storing. If one sticks to standard limit of 16 locals that is 33
primitives. And they are very simple, one or two machine instructions
for real work (plus whatever boilerplate is needed for dispatch).

At compile time main effort is to add new transient part to dictionary.
One needs to decide where to put it. With standard restriction
of local declarations fitting on single line one could use a fixed
size area of order 120-200 bytes. One needs to modify search for
words to look there before main dictionary. And of course one needs
parsing word to recognize locals.

Together that adds some bulk, probably around 500-1000 bytes to
code space and say extra 200 bytes during compile, but I do not
think it is really complication.
Post by Paul Rubin
but I don't know how much Moore valued that. I know he did
like stack hardware (his many Forth chips) but that came later.
So the pure "stack plus VARIABLEs" seems "not even wrong" to me. I
don't understand the motivation behind it well enough to disagree per
se. And I've never seen an explanation that made sense. They have all
seemed like grasping at straws.
--
Waldek Hebisch
Marcel Hendrix
2022-11-01 16:34:48 UTC
Permalink
On Tuesday, November 1, 2022 at 2:19:21 PM UTC+1, ***@math.uni.wroc.pl wrote:
[..]
Post by a***@math.uni.wroc.pl
I think thatsome voices here vastly overestimate extra complexity.
add and subtract from return stack pointer (could be single primitive)
and two primitives fpr each local, one for reading, the other for
storing. If one sticks to standard limit of 16 locals that is 33
primitives. And they are very simple, one or two machine instructions
for real work (plus whatever boilerplate is needed for dispatch).
Aren't you forgetting what happens when you call a word that also
uses locals? What about recursion? What happens when bot locals
and the R-stack are used?
Post by a***@math.uni.wroc.pl
At compile time main effort is to add new transient part to dictionary.
[..]

Complexity depends on simply *implementing* locals versus *optimizing* them.
The former is indeed trivially simple.

Another problem: what to do with SEE ?

-marcel
Anton Ertl
2022-11-01 17:49:32 UTC
Permalink
Post by Marcel Hendrix
[..]
Post by a***@math.uni.wroc.pl
I think thatsome voices here vastly overestimate extra complexity.
add and subtract from return stack pointer (could be single primitive)
and two primitives fpr each local, one for reading, the other for
storing. If one sticks to standard limit of 16 locals that is 33
primitives. And they are very simple, one or two machine instructions
for real work (plus whatever boilerplate is needed for dispatch).
Actually, you need only one primitive with an immediate argument N for
reading local N and one primitive with an immediate argument M for
writing local M.
Post by Marcel Hendrix
Aren't you forgetting what happens when you call a word that also
uses locals? What about recursion?
The locals are on the return stack in his model (like in many
systems), so calls to other words with locals are covered, including
recursion.
Post by Marcel Hendrix
What happens when bot locals
and the R-stack are used?
The standard has restrictions on mixing that. I think if he wants
minimal complexity, he will also impose these restrictions in programs
on his system (like many systems with locals on the return stack do).
Post by Marcel Hendrix
Another problem: what to do with SEE ?
Most native-code systems show the native code in disassembled form, so
apparently it's not that important that the result of SEE is similar
to the source code. If you want to see that source code, use LOCATE.
In Gforth, SEE shows you the primitives for code generated for locals.
E.g.:

: my2swap {: a b c d :} c d a b ; ok
see my2swap
: my2swap
- 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
a***@math.uni.wroc.pl
2022-11-03 02:56:06 UTC
Permalink
Post by Marcel Hendrix
[..]
Post by a***@math.uni.wroc.pl
I think thatsome voices here vastly overestimate extra complexity.
add and subtract from return stack pointer (could be single primitive)
and two primitives fpr each local, one for reading, the other for
storing. If one sticks to standard limit of 16 locals that is 33
primitives. And they are very simple, one or two machine instructions
for real work (plus whatever boilerplate is needed for dispatch).
Aren't you forgetting what happens when you call a word that also
uses locals? What about recursion? What happens when bot locals
and the R-stack are used?
Anton answered this.
Post by Marcel Hendrix
Post by a***@math.uni.wroc.pl
At compile time main effort is to add new transient part to dictionary.
[..]
Complexity depends on simply *implementing* locals versus *optimizing* them.
The former is indeed trivially simple.
I have spent some time working on "optimizing" codegen for two stack
VM with locals. I mean input was sequence of Forth-like operations
and codegen produced machine code. AFAICS result was worse than
your Forth. But offender were not locals. In fact, careful
use of local gave best code. One trouble was insufficient stack
tracking, sometimes items were forced on data stack not because
they had to be there, but only because codegen was unable to
track them. Another trouble was too naive register allocator, which
did not do well with temporaries. But if you had small number
of locals it would put them all in registers. And if all temporaries
were immediately put into locals, then computation could do work
in registers and you would get resonably good code. In other
words, one could use locals to tell compiler which things should
go into registers.

If you say that optimizing locals in _your_ Forth takes substantial
effort, then I belive. I know in principle how to write better
register allocator and do not think it is very hard. But existing
code was micro-optimized in many places and is hard to modify.

So, this is not absolute difficulty of solving problem, but rather
trouble with modifying existing program (plus limited time that
I could spent on this). But with different compiler structure
or when starting anew I could do better. And I _know_ that one
can generate pretty efficient code for locals in rather simple
compiler.
--
Waldek Hebisch
minf...@arcor.de
2022-11-02 06:37:53 UTC
Permalink
Post by a***@math.uni.wroc.pl
Post by Paul Rubin
Post by dxforth
"The words that manipulate that stack are DUP, DROP and OVER
period. There's no ..., well SWAP is very convenient and you want it,
but it isn't a machine instruction. But no PICK no ROLL, none of the
complex operators to let you index down into the stack."
PICK and ROLL have to have come from somewhere: if it wasn't Moore, then
who? And what about ROT, -ROT, >R, and R>?
I get that Moore used VARIABLEs for everything that I'd use locals for.
He also used CODE when Forth got in the way too much.
Back in the 1970s, computers were limited, programs were necessarily
small, and Moore espoused rewriting programs from the ground up instead
of maintaining them. The main problem facing programmers was how to
make their program implement behaviour X, for whatever X happened to be
that day. They'd put a lot of effort into coding and testing X, and
then they were done.
Today, the main problem facing programmers is ensuring the absence of
behaviours rather than their presence. Writing code is way easier than
it used to be, because tools are better and computers are faster. But
maintaining is harder since programs are bigger and have more people
hacking at them. If you use a VARIABLE to save a temporary value used
by a word, then anyone modifying that word, when they see that variable,
now has to check that no other word uses the variable and depends on it
for communication. Or worse, communicates through the memory cell
occupied by that variable but through some sneaky alias. VALUE avoids
aliasing (no idea whether Moore used them), but locals make both issues
go away.
Finally, while I can't doubt Moore's genius, I've never understood the
vision that says the pure stack VM fits every programming problem.
Register allocation and spilling is one of the messier parts of
traditional compilers even when there are plenty of registers that don't
have weird special purposes. In Forth, there are only 3 or so
"registers" (T, N, and R), and each is special. Why be so vehement that
a human rather than a machine should be juggling them? I can understand
taht locals (if supported) complicate the traditional threaded Forth
interpreter,
I think thatsome voices here vastly overestimate extra complexity.
add and subtract from return stack pointer (could be single primitive)
and two primitives fpr each local, one for reading, the other for
storing. If one sticks to standard limit of 16 locals that is 33
primitives. And they are very simple, one or two machine instructions
for real work (plus whatever boilerplate is needed for dispatch).
At compile time main effort is to add new transient part to dictionary.
One needs to decide where to put it. With standard restriction
of local declarations fitting on single line one could use a fixed
size area of order 120-200 bytes.
With locals declarations in one line you can parse the declarations twice
by resetting >in to {: after first encounter of :}. It makes the locals compiler
only slightly more complex.

Use the the first parsing stage to calculate the required locals frame size
in the return stack. Advantage: the next step for fp, buffer/strings, structure
locals is well prepared.

Locals are seldom worth the effort when one deals only with integers.

FWIW I found string locals very helpful in the past. Very often they can
release you from explicit buffer management or setting up a string stack.
Hans Bezemer
2022-11-02 10:28:11 UTC
Permalink
I think that some voices here vastly overestimate extra complexity.
I did locals in my uBASIC interpreter - because it's BASIC - a Fortran
derivative - it's fitting. But it requires setting up and releasing a stack frame,
copying values there - that's a whole lot of overhead. For some ugly C-ism.
As if we didn't have enough of those in ANS Forth.
add and subtract from return stack pointer (could be single primitive)
and two primitives fpr each local, one for reading, the other for
storing. If one sticks to standard limit of 16 locals that is 33
primitives. And they are very simple, one or two machine instructions
for real work (plus whatever boilerplate is needed for dispatch).
33 primitives? And you call that "minor overhead"? My entire VM has about
100 primitives.

Hans Bezemer
a***@math.uni.wroc.pl
2022-11-03 03:50:41 UTC
Permalink
Post by Hans Bezemer
I think that some voices here vastly overestimate extra complexity.
I did locals in my uBASIC interpreter - because it's BASIC - a Fortran
derivative - it's fitting. But it requires setting up and releasing a stack frame,
copying values there - that's a whole lot of overhead. For some ugly C-ism.
As if we didn't have enough of those in ANS Forth.
Copying values is not really overhead. Normally values are copied
because you need to preserve them, so it is part of computation
and needs to be done one way or anouther. In fact, bulk copy
in many cases is most officient way to do this.
Post by Hans Bezemer
add and subtract from return stack pointer (could be single primitive)
and two primitives fpr each local, one for reading, the other for
storing. If one sticks to standard limit of 16 locals that is 33
primitives. And they are very simple, one or two machine instructions
for real work (plus whatever boilerplate is needed for dispatch).
33 primitives? And you call that "minor overhead"? My entire VM has about
100 primitives.
I am not sure what is troubling you here. That they are called
"primitives"? Access primitives are very simple and very similar
to each other, conceptually there is almost no increase in complexity.
That they need to be coded in assembler? Albert's ciforth has about
300 words defiend in assembler, and from glance at his code I think
that most is longer than proposed primitives. So it seems that one
would get 5-7% increase in amount of assembler code and to that
matter simpler than much of the rest. Of course, you may that pride
that you need less assember code. However, I take pragmatic view
here: assembler is problematic because one can do tricky things
which are hard to understand. But the primitives are so simple that
there is not problem with understanding.

One extra remark: as Anton noted instead of 32 access primitives
I could use just 2. But AFACS in threaded code each use of
Anton version would add 2 words of code (one for primitive itself,
second for parameter). And assembly code of primitive would
be a bit more complex. My version when used needs just one
word of code. And assembly should be pretty efficient, at least
as efficient as named constant or value and possibly more efficient.
Which means that when used in good way locals should lead to
smaller and more efficient code.

Maybe you are worried about used dictionary slots? AFAICS Forthers
are not shy about defining small special purpose words that
get used once or twice. Looking at extras for Mecrisp I saw
MCU definition files that define hundreds of constants, so even
for small targets people use a lot dictionary slots. So,
I call those 33 slots minor overhead.

And you may not belive, but I have reasons to think that
using locals within Forth implementation (not for style, but
when it leads to gain!) would make code smaller, likely
compensating space taken by primitives.
--
Waldek Hebisch
none) (albert
2022-11-03 11:13:38 UTC
Permalink
Post by a***@math.uni.wroc.pl
One extra remark: as Anton noted instead of 32 access primitives
I could use just 2. But AFACS in threaded code each use of
Anton version would add 2 words of code (one for primitive itself,
second for parameter). And assembly code of primitive would
be a bit more complex. My version when used needs just one
word of code. And assembly should be pretty efficient, at least
as efficient as named constant or value and possibly more efficient.
Which means that when used in good way locals should lead to
smaller and more efficient code.
Maybe you are worried about used dictionary slots? AFAICS Forthers
are not shy about defining small special purpose words that
get used once or twice. Looking at extras for Mecrisp I saw
MCU definition files that define hundreds of constants, so even
for small targets people use a lot dictionary slots. So,
I call those 33 slots minor overhead.
<SNIP>
Post by a***@math.uni.wroc.pl
I am not sure what is troubling you here. That they are called
"primitives"? Access primitives are very simple and very similar
to each other, conceptually there is almost no increase in complexity.
That they need to be coded in assembler? Albert's ciforth has about
300 words defiend in assembler, and from glance at his code I think
that most is longer than proposed primitives. So it seems that one
would get 5-7% increase in amount of assembler code and to that
matter simpler than much of the rest. Of course, you may that pride
that you need less assember code. However, I take pragmatic view
here: assembler is problematic because one can do tricky things
which are hard to understand. But the primitives are so simple that
there is not problem with understanding.
Putting the record straight:
ciforth has 300 core words, but there are only 82+3 code words.
Please point out what is not deemed a primitive in the following list:

Code definition : NOOP
Code definition : LIT
Code definition : EXECUTE
Code definition : SKIP
Code definition : 0BRANCH
Code definition : (+LOOP)
Code definition : (DO)
Code definition : (?DO)
Code definition : I
Code definition : J
Code definition : UNLOOP
Code definition : CMOVE
Code definition : MOVE
Code definition : FARMOVE
Code definition : UM*
Code definition : UM/MOD
Code definition : AND
Code definition : OR
Code definition : XOR
Code definition : INVERT
Code definition : DSP@
Code definition : DSP!
Code definition : RSP@
Code definition : RSP!
Code definition : EXIT
Code definition : CO
Code definition : >R
Code definition : R>
Code definition : RDROP
Code definition : 0=
Code definition : 0<
Code definition : +
Code definition : D+
Code definition : NEGATE
Code definition : DNEGATE
Code definition : OVER
Code definition : DROP
Code definition : NIP
Code definition : 2DROP
Code definition : SWAP
Code definition : DUP
Code definition : 2DUP
Code definition : 2SWAP
Code definition : 2OVER
Code definition : +!
Code definition : TOGGLE
Code definition : @
Code definition : C@
Code definition : 2@
Code definition : !
Code definition : C!
Code definition : 2!
Code definition : FAR@
Code definition : FAR!
Code definition : _
Code definition : $@
Code definition : ALIGNED
Code definition : -
Code definition : =
Code definition : <
Code definition : U<
Code definition : <>
Code definition : ROT
Code definition : FILL
Code definition : CORA
Code definition : $^
Code definition : $/
Code definition : $\
Code definition : S>D
Code definition : LSHIFT
Code definition : RSHIFT
Code definition : M*
Code definition : SM/REM
Code definition : 2/
Code definition : 2*
Code definition : PC@
Code definition : PC!
Code definition : PW@
Code definition : PW!
Code definition : XOS
Code definition : XOS5
Alias : BRANCH
Alias : (;)
Alias : : R@

<SNIP>
Post by a***@math.uni.wroc.pl
And you may not belive, but I have reasons to think that
using locals within Forth implementation (not for style, but
when it leads to gain!) would make code smaller, likely
compensating space taken by primitives.
Interesting observation, but it doesn't chime with me.
A can't think of a high level word in the kernel that
would benefit from locals.

This is about as bad at it gets (decompiled, not formatted):
: FORGET-VOC 2DUP SWAP U< IF SWAP >R >WID BEGIN DUP DUP BEGIN >LFA @
DUP R@ U< UNTIL SWAP >LFA ! >LFA @ DUP 0= UNTIL DROP R> ELSE >VFA @
VOC-LINK ! ONLY FORTH DEFINITIONS THEN ;

Note that I think nothing of tucking away something on the return
stack that I need later. IMO it beats coming up with a sensible
name for a local.

Waldek Hebisch

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
***@spe&ar&c.xs4all.nl &=n http://home.hccnet.nl/a.w.m.van.der.horst
Paul Rubin
2022-11-03 16:51:13 UTC
Permalink
Post by none) (albert
VOC-LINK ! ONLY FORTH DEFINITIONS THEN ;
I had to guess at the stack effects of the internal words and have no
idea what the parameters are supposed to mean, but this looks roughly
like:

: FORGET-VOC { a b }
b a u< IF
b >WID
BEGIN
BEGIN b >LFA @ { c } c a U< UNTIL
b >LFA !
b 0= UNTIL
b c a
ELSE
a b >VFA @
VOC-LINK ! ONLY FORTH DEFINITIONS
THEN ;

Actually that is probably wrong since there is a different amount of
stuff on the stack depending on which branch of the IF is taken.
Some comments and stack diagrams would surely help.
none) (albert
2022-11-04 10:47:46 UTC
Permalink
Post by Paul Rubin
Post by none) (albert
VOC-LINK ! ONLY FORTH DEFINITIONS THEN ;
I had to guess at the stack effects of the internal words and have no
idea what the parameters are supposed to mean, but this looks roughly
: FORGET-VOC { a b }
b a u< IF
b >WID
BEGIN
b >LFA !
b 0= UNTIL
b c a
ELSE
VOC-LINK ! ONLY FORTH DEFINITIONS
THEN ;
Actually that is probably wrong since there is a different amount of
stuff on the stack depending on which branch of the IF is taken.
Some comments and stack diagrams would surely help.
It was not intended to be analysed, but to show how approximately
the most complicated words look. Probably the decompiler had
the control structures wrong hence the return stack imbalance.
In actual source code it look (first part shown).

_HEADER({FORGET-VOC},{FORGV},{DOCOL})
DC TDUP
DC SWAP
DC ULESS
_0BRANCH(FORGV1)
_C{ Forget part of contents.}
DC SWAP
DC TOR
DC TWID
FORGV0:
....

Remember the kernel is code in assembler.

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
***@spe&ar&c.xs4all.nl &=n http://home.hccnet.nl/a.w.m.van.der.horst
dxforth
2022-11-04 23:46:20 UTC
Permalink
Post by none) (albert
Post by Paul Rubin
Post by none) (albert
VOC-LINK ! ONLY FORTH DEFINITIONS THEN ;
I had to guess at the stack effects of the internal words and have no
idea what the parameters are supposed to mean, but this looks roughly
: FORGET-VOC { a b }
b a u< IF
b >WID
BEGIN
b >LFA !
b 0= UNTIL
b c a
ELSE
VOC-LINK ! ONLY FORTH DEFINITIONS
THEN ;
Actually that is probably wrong since there is a different amount of
stuff on the stack depending on which branch of the IF is taken.
Some comments and stack diagrams would surely help.
It was not intended to be analysed, but to show how approximately
the most complicated words look.
Fig-Forth's FORGET was simple but it broke once vocabs were added.
Consequently the code to trim dictionaries back to a usable state
became ever more complicated as dictionaries became more complicated.
Whether it was worth it is debatable but that's how it evolved.
This is for a single-thread, split dictionary with separated headers:

: (forget) ( nfa dps dp -- )
dp 2! >r \ starting maximums
voc-link begin \ trim vocs > nfa
@ dup cell- @ cell+ \ vocab nfa
r@ u<
until dup voc-link !
begin
dup cell- @ \ scan remaining vocs
dup h@ begin
dup r@ u< 0= \ for each word >= nfa
while
-alias if \ not an alias
dup name> \ get its xt
xdp tuck @
umin swap ! \ trim dict
then
n>name
repeat
swap h!
@ ?dup 0= \ until all vocs done
until
r> dup dph !
(idph) @ u< if \ below fence?
protect \ fix bootup values
then
prunes ; \ run prunes list

: EMPTY ( -- )
forth definitions (idph) @ (idp) 2@ (forget) ;

: FORGET ( "name" -- )
get-current context ! name? -alias 0= abort" is alias"
swap limit over u< if dp @ else dps @ swap then (forget) ;
S Jack
2022-11-05 02:38:42 UTC
Permalink
Post by dxforth
Fig-Forth's FORGET was simple but it broke once vocabs were added.
Using Fig and oldest simple vocabularies. Upgraded FORGET to update
word lists in all existing vocabularies. Didn't bother to fix any
broken VOC-LINK chain; however, I did fence vocabularies when created.
If some FORGET includes a vocabulary, fence will alert and the chain
can be fixed manually. Never had to; not a problem I expect to deal
with.
--
me
Hans Bezemer
2022-10-30 12:44:29 UTC
Permalink
Moore, I've heard of that guy. Isn't he the one who invented PICK and
ROLL because his Forth didn't have locals? Once you've turned the stack
into an indexible array, you might as well be able to give names to the
slots.
Well, in his 1993 interview he was quoted to have said: "But no PICK no
ROLL, none of the complex operators to let you index down into the stack.
This is the only part of the stack, these first two elements, that you have
any business worrying about". (http://www.ultratechnology.com/moore4th.htm)

It's also explicitly listed in "Thinking Forth" - although that one was not written
by Moore. It's just the penultimate work on Forth philosophy..

So it must have been another Moore, I guess.. There are so many Moores writing
Forth..

Hans Bezemer
Hans Bezemer
2022-10-30 13:37:15 UTC
Permalink
Moore, I've heard of that guy. Isn't he the one who invented PICK and
ROLL because his Forth didn't have locals? Once you've turned the stack
into an indexible array, you might as well be able to give names to the
slots.
BTW - PICK and ROLL do not make a stack into an indexable array. Even
when using dynamic memory (unless you reallocate it by a resize) I can
be pretty much assured that every time I get an indexed element, it points
to the very same memory address. However, if I do a "5 PICK", the next time
I do a "5 PICK" it will get me the contents of a very different address. If I
want the same element I have to issue "6 PICK".

ROLL - on the other hand - shifts the contents. You may get the same address,
but completely different content. The ontology of ROLL is more difficult, though
- I agree. It's more a question of semantics here.

So, PICK does NOT turn a stack into an "indexable array" - which makes the
next argument "being able to give names to the slots" completely untenable.

Hans Bezemer
Gerry Jackson
2022-10-29 07:41:25 UTC
Permalink
Post by Hans Bezemer
Post by Paul Rubin
If one isn't happy with variables external to forth words, one
shouldn't be happy with those internal to them.
1. The externals make the code harder to understand / maintain since they
persist across function calls and can be shared between functions. You can
use some naming conventions to indicate that this isn't happening, but
ugh. And you still have to re-initialize the global inside the function
on every call.
If you need a carload of variables, you've forgotten (or completely disregarded)
the first rule of "Thinking Forth", which is "STAMP OUT THE VARIABLES"!
You don't seem to practice what you preach e.g.

\ -----------------------------------------------------------
\ 4tH library - string CONCAT - Copyright 2012 J.L. Bezemer
\ You can redistribute this file and/or modify it under
\ the terms of the GNU General Public License

[UNDEFINED] concat [IF]
variable __concat \ destination address

: concat ( a1 n1 .. ai ni i a2 -- a2 n2)
__concat ! 1- dup \ save destination, setup counter
begin dup while rot >r rot >r 1- repeat drop >r __concat @ place r>
begin dup while r> r> __concat @ +place 1- repeat drop __concat @ count
; \ first transfer to Rstack, then append
\ ----------------------------------------------------------

Seems a bit of a dog's breakfast to me: unnecesssary use of a variable,
lengthy definition and unnecessary call to PLACE, crashes if i=0,
incorrect & misleading stack comment. Compare with:

: concat ( ca1 u1 .. can un n ca -- ca+1 u )
0 over c! swap
[: ?dup if 2swap 2>r 1- recurse dup 2r> rot +place then ;]
execute count
;
--
Gerry
Marcel Hendrix
2022-10-29 08:05:40 UTC
Permalink
On Saturday, October 29, 2022 at 9:41:27 AM UTC+2, Gerry Jackson wrote:
[..]
Post by Gerry Jackson
Seems a bit of a dog's breakfast to me: unnecesssary use of a variable,
lengthy definition and unnecessary call to PLACE, crashes if i=0,
: concat ( ca1 u1 .. can un n ca -- ca+1 u )
0 over c! swap
[: ?dup if 2swap 2>r 1- recurse dup 2r> rot +place then ;]
execute count
;
FORTH> : concats ( ca1 u1 .. can un n -- ca u ) 1- 0 max 0 ?DO $+ LOOP ;
FORTH> S" Hello" S" , " S" world" S" !" 4 concats
[2]FORTH> type Hello, world! ok

-marcel
dxforth
2022-10-29 15:57:39 UTC
Permalink
Post by Marcel Hendrix
FORTH> : concats ( ca1 u1 .. can un n -- ca u ) 1- 0 max 0 ?DO $+ LOOP ;
FORTH> S" Hello" S" , " S" world" S" !" 4 concats
[2]FORTH> type Hello, world! ok
: concats <# 0 ?do holds loop 0 0 #> ;

s" Hello" s" , " s" world" s" !" 4 concats type Hello, world! ok
Gerry Jackson
2022-10-30 15:09:21 UTC
Permalink
FORTH> : concats ( ca1 u1 .. can un n -- ca u )  1- 0 max 0 ?DO  $+
LOOP ;
FORTH> S" Hello"  S" , "  S" world"  S" !"  4 concats
[2]FORTH> type Hello, world! ok
: concats  <# 0 ?do holds loop 0 0 #> ;
s" Hello"  s" , "  s" world"  s" !"  4 concats type Hello, world! ok
Yes that's much simpler even with the inelegance of having to provide 2
cells on the stack for #> to drop.

Seeing that solution is ironic and humbling. I knew that <# ... #> and
indeed have posted something about that on c.l.f in the past. I've no
explanation about why I didn't think of that in my response to Hans
Bezemer, I suppose his solution pointed me into using +PLACE and I
didn't think beyond that.
--
Gerry
dxforth
2022-10-30 23:55:12 UTC
Permalink
FORTH> : concats ( ca1 u1 .. can un n -- ca u )  1- 0 max 0 ?DO  $+ LOOP ;
FORTH> S" Hello"  S" , "  S" world"  S" !"  4 concats
[2]FORTH> type Hello, world! ok
: concats  <# 0 ?do holds loop 0 0 #> ;
s" Hello"  s" , "  s" world"  s" !"  4 concats type Hello, world! ok
Yes that's much simpler even with the inelegance of having to provide 2 cells on the stack for #> to drop.
Seeing that solution is ironic and humbling. I knew that <# ... #> and indeed have posted something about that on c.l.f in the past. I've no explanation about why I didn't think of that in my response to Hans Bezemer, I suppose his solution pointed me into using +PLACE and I didn't think beyond that.
I had considered an iterative solution similar to the original:

\ +string ( a1 u1 a2 u2 -- a2 u3 )

: concat ( a1 u1 ... an un n dest -- dest len )
0 0 begin 2>r over while swap 1- swap 2swap repeat
swap begin 2r> over while 2swap +string repeat 2drop ;

Not sure the problem is particularly real. Akin to painting oneself into
a corner then having to find a way out. There has to be a better way...
Gerry Jackson
2022-10-30 15:00:09 UTC
Permalink
Post by Marcel Hendrix
[..]
Post by Gerry Jackson
Seems a bit of a dog's breakfast to me: unnecesssary use of a variable,
lengthy definition and unnecessary call to PLACE, crashes if i=0,
: concat ( ca1 u1 .. can un n ca -- ca+1 u )
0 over c! swap
[: ?dup if 2swap 2>r 1- recurse dup 2r> rot +place then ;]
execute count
;
FORTH> : concats ( ca1 u1 .. can un n -- ca u ) 1- 0 max 0 ?DO $+ LOOP ;
FORTH> S" Hello" S" , " S" world" S" !" 4 concats
[2]FORTH> type Hello, world! ok
So your $+ prepends a string to another into some buffer. See DX alternative
--
Gerry
Hans Bezemer
2022-10-29 12:45:50 UTC
Permalink
Post by Gerry Jackson
Post by Hans Bezemer
If you need a carload of variables, you've forgotten (or completely disregarded)
the first rule of "Thinking Forth", which is "STAMP OUT THE VARIABLES"!
You don't seem to practice what you preach e.g.
Well, the only lesson one could take from it is "there is always someone more clever
than you" - which IMHO is just a fact of life. You will probably find more examples in
the 500+ libraries I wrote - if you dig really well.

However, if this is the only example you found in 75 KLOC of 4tH, I think I'm not doing
too bad. BTW, it's a clever piece of code. Kudos!

Hans Bezemer
Hans Bezemer
2022-10-29 13:14:53 UTC
Permalink
Post by Gerry Jackson
incorrect & misleading stack comment.
I now see where your comment about "misleading stack comment" comes
from. In 4tH ASCIIZ strings are used - and a2 is actually the address that is
returned.

That your code works is because the first line effectively terminates
the target string - and the abstraction of +PLACE does the heavy lifting.

But I forgive you ;-)

Hans Bezemer
Gerry Jackson
2022-10-30 15:30:14 UTC
Permalink
Post by Hans Bezemer
Post by Gerry Jackson
incorrect & misleading stack comment.
I now see where your comment about "misleading stack comment" comes
from. In 4tH ASCIIZ strings are used - and a2 is actually the address that is
returned.
Sorry about that, I wasn't aware that 4th used zero terminated strings
and just assumed that standard counted strings were used. So presumably
your version of COUNT does it by actually counting the characters.
Post by Hans Bezemer
That your code works is because the first line effectively terminates
the target string - and the abstraction of +PLACE does the heavy lifting.
It's fortuitous that a 0 byte means an empty string for both ways of
representing a string.
Post by Hans Bezemer
But I forgive you ;-)
Thank you
--
Gerry
Hans Bezemer
2022-10-31 15:48:58 UTC
Permalink
Post by Gerry Jackson
Sorry about that, I wasn't aware that 4th used zero terminated strings
and just assumed that standard counted strings were used. So presumably
your version of COUNT does it by actually counting the characters.
Yes - found out pretty quickly that abstracting STRING words worked in most
situations - so I rarely have problems porting stuff with "cooked" strings.
Unless someone uses COUNT as C@+ equivalent :-(

Note that the overhead of "counting strings" is negligible, since 4tH promotes
the addr/count representation - just like ANS Forth. Only when getting stuff
from string variables or ," compiled strings or +PLACE counting strings is
required.
Post by Gerry Jackson
It's fortuitous that a 0 byte means an empty string for both ways of
representing a string.
Yeah - I thought so too! I'm so used to ASCIIZ strings it didn't dawn upon me
this might have been written for COUNTed strings. There I got the idea about
the stack item!

Hans Bezemer
Paul Rubin
2022-10-29 02:28:59 UTC
Permalink
Post by ***@arcor.de
: TEST { a b .. | d e .. == f g .. } .. ;
That is cool, what Forth implements that? I haven't seen it before.
Just ending the word with "f g" seems like enough though:

: swap { a b } b a ;
minf...@arcor.de
2022-10-29 06:46:15 UTC
Permalink
: TEST { a b .. | d e .. == f g .. } .. ;
That is cool, what Forth implements that? I haven't seen it before.
: swap { a b } b a ;
This would be a SWAP defined in standard Forth with single braces à la gforth.
For such extremely short definitions there is no benefit from output locals.

But consider complicated algorithms (in my case signal processing math).
There it helps when the compiler does the stack juggling for you by parsing
the stack comment COMPLETELY. Standard Forth stops halfway in the process
by ignoring everything after -- . But sometimes it is a waste of programmer's time
when he has to keep track of the machine state/stacks instead of concentrating
on the algorithm.

F.ex.
: DEARCHMEAN { tagname tstart tend f: factor | sum == rec: tagrec f: mval n: flag }
< algorithm > ;
extracts the the archived signal 'tagname' between points of time tstart to tend and
calculates the mean signal value mval using the fp-locals factor and sum. The extracted
time series record tagrec and and a flag are returned as well. The "living stack comment"
says it all. Your eyes don't have to scan the following algo code to find out where/when
final results are put on the stack, moved into the correct order, and intermediate
calculations are cleared/dropped away. Let the compiler do this for you. The algo's
code becomes uncluttered, more readable, and shorter.

Some smart guys think that this smells too much of Python. But they ignore that this is
still Forth, ultra-compact for embedding, and fast enough in 98% of all cases.

And there is a second advantage: when the compiler knows the outputs of a word, it
can raise a warning when it is connected to a word with non-matching input characteristic.
This compile-time I/O matching can be very helpful, but not always.
Hans Bezemer
2022-10-28 16:12:10 UTC
Permalink
Post by Paul Rubin
1. The externals make the code harder to understand / maintain since they
persist across function calls and can be shared between functions. You can
use some naming conventions to indicate that this isn't happening, but
ugh. And you still have to re-initialize the global inside the function
on every call.
2. If the function is recursive you can't use global variables. You have to
set up a separate stack for them or something. Ugh.
3. The globals keep occupying storage even when the function is not running.
I thought Forthers liked stack allocation so that the memory would keep
getting re-used. Locals give that.
I hope you and C become very happy. Oh dear, no! You can blow up your program
if you're incapable of balancing your malloc() - free() calls. It's much the same way
like stack balancing, you know. I hope you and Rust become very happy.

Hans Bezemer
dxforth
2022-10-29 03:16:03 UTC
Permalink
Post by Paul Rubin
If one isn't happy with variables external to forth words, one
shouldn't be happy with those internal to them.
1. The externals make the code harder to understand / maintain since they
persist across function calls and can be shared between functions. You can
use some naming conventions to indicate that this isn't happening, but
ugh. And you still have to re-initialize the global inside the function
on every call.
2. If the function is recursive you can't use global variables. You have to
set up a separate stack for them or something. Ugh.
3. The globals keep occupying storage even when the function is not running.
I thought Forthers liked stack allocation so that the memory would keep
getting re-used. Locals give that.
Oh, I'm not advocating variables - locals or otherwise. What I see is a lot
of promiscuous use of the former as if there were no cost. Here's an example
of using extra locals - not out of necessity - but 'style':

https://gforth.org/manual/Locals-programming-style.html

In C, locals are unavoidable - it's not a matter of style or choice. In Forth,
locals are all about style. The more the better so we're told. Unfortunately
Forth was never designed for it. Compared to data stack, the return stack is
the smaller - intended only to hold return addresses and the occasional temp.
Nested definitions using locals will quickly eat it up. You decide whether
style is worth the cost.
Paul Rubin
2022-11-02 22:26:03 UTC
Permalink
Post by dxforth
Oh, I'm not advocating variables - locals or otherwise. What I see is a lot
of promiscuous use of the former as if there were no cost. Here's an example
https://gforth.org/manual/Locals-programming-style.html
In that example, there is some stuff in locals, but getting rid of the
locals would mean keeping the same stuff on the data stack (and maybe
partly the return stack). The total memory used is the same. In some
systems the return (or locals) stack might be too small, but if you're
going to use locals (or anything else) at all, obviously you want to
size the stacks to accomodate your usage.
Post by dxforth
If anyone is writing applications 'in Forth', it's because they've
stopped pursuing 'the vision splendid' ...
Heh, I like that expression. I had to look up its origin, a book from
1913 about some faction in the Anglican church. I will have to look at
the book one of these days. It's the vision itself that I've never
completely understood. I ought to read more of Dr. Ting's "Zen of
Forth" books, I suppose.
dxforth
2022-11-03 02:46:27 UTC
Permalink
Post by Paul Rubin
Post by dxforth
Oh, I'm not advocating variables - locals or otherwise. What I see is a lot
of promiscuous use of the former as if there were no cost. Here's an example
https://gforth.org/manual/Locals-programming-style.html
In that example, there is some stuff in locals, but getting rid of the
locals would mean keeping the same stuff on the data stack (and maybe
partly the return stack). The total memory used is the same. In some
systems the return (or locals) stack might be too small, but if you're
going to use locals (or anything else) at all, obviously you want to
size the stacks to accomodate your usage.
My point was it went from 4 locals to 6 for reasons of style. It may be
C-style to expend resources this way (I wouldn't know) but I doubt it's
Moore's. But back to your comment. You argue the same resources are
being spent (albeit differently apportioned) so it really doesn't matter
whether one is using locals or stack operators. I suspect you are not
alone. But what are implications of using locals in Forth? It's this:
A stack-based language that cannot exist without the help of locals has
failed and therefore does not deserve to exist.
Paul Rubin
2022-11-03 03:31:15 UTC
Permalink
Post by dxforth
My point was it went from 4 locals to 6 for reasons of style. It may be
C-style to expend resources this way (I wouldn't know)
In C these days, the extra locals wouldn't expend any resources in most
cases, since the compiler would optimize them away.
Post by dxforth
but I doubt it's Moore's.
This I don't know. A traditional thread Forth interpreter (Moore's
invention) is probably 10x slower than CODE doing the same thing, but
Moore only used CODE when he had to. If 9x extra compute cycles is
fine, why are a few memory cells a big deal?

There's a similar thing from the Forth stylistic convention that words
should consume their arguments. The convention makes your code more
understandable but it means consuming a little more data stack space
than necessary, and running some extra dups and drops.
Post by dxforth
But what are implications of using locals in Forth? It's this: A
stack-based language that cannot exist without the help of locals has
failed and therefore does not deserve to exist.
Well, Moore used VARIABLEs instead of locals, so the pure stack vision
already failed because of that. One could simply say Forth works better
in the presence of some impurity. Locals are another, perhaps cleaner,
way to add the impurity.

There is a similar thing in functional programming called "point-free
style". That style avoids using named function parameters, so instead
of "f x = sqrt (sin x)" you'd say "f = sqrt . sin". Nice, but it can
get really confusing, like "f x y z = (x+y)*z" becomes "f = ((*) .) . (+)".

Point-free is a nice theoretical construct and you can apparently
write every program that way, but for practical purposes it's much nicer
to use named parameters except in the simpler cases. The feeling of
point-free coding is not that much different from Forth stackrobatics.
It can be fun but if your goal is to write programs that work, it's
not that good a use of mental energy.
dxforth
2022-11-03 04:10:36 UTC
Permalink
Post by Paul Rubin
Post by dxforth
My point was it went from 4 locals to 6 for reasons of style. It may be
C-style to expend resources this way (I wouldn't know)
In C these days, the extra locals wouldn't expend any resources in most
cases, since the compiler would optimize them away.
AFAIK optimizers do a better job when the code presented is already optimal.
Post by Paul Rubin
Post by dxforth
But what are implications of using locals in Forth? It's this: A
stack-based language that cannot exist without the help of locals has
failed and therefore does not deserve to exist.
Well, Moore used VARIABLEs instead of locals, so the pure stack vision
already failed because of that.
Does he?
Post by Paul Rubin
One could simply say Forth works better
in the presence of some impurity. Locals are another, perhaps cleaner,
way to add the impurity.
Well, in that case I'd say you're more desperate to use Forth than I am.
No way would I tolerate using a broken language :)
Paul Rubin
2022-11-03 04:36:54 UTC
Permalink
Post by dxforth
AFAIK optimizers do a better job when the code presented is already optimal.
These days optimizers work by first splitting everything out into
separate variables so that nothing is ever updated (static single
assignment or SSA form), and then recombining the variables. I have
never implemented SSA and don't understand it perfectly, but I believe
the SSA forms of the 4-local and 6-local versions of that code would be
very similar. Anton would know this much better than I do.
Post by dxforth
Post by Paul Rubin
Well, Moore used VARIABLEs instead of locals,
Does he?
I believe so. I remember looking at cmforth and some of the GA144 code
and seeing variables used that way. I also remember asking Elizabeth
about the complex multiplication example, and her replying that Moore
didn't hesitate to use CODE if it made things easier. He wasn't a
purist about staying with Forth words.

In the 1970s there just wasn't room for purity in software. Maybe APL
came closest. Lisp was born from the most purest mathematical
abstraction (lambda calculus) but its implementations were full of low
level hackery. The purest language now is Haskell but it is also full
of hacks.
dxforth
2022-11-03 05:29:34 UTC
Permalink
Post by Paul Rubin
[...]
Post by dxforth
Post by Paul Rubin
Well, Moore used VARIABLEs instead of locals,
Does he?
I believe so. I remember looking at cmforth and some of the GA144 code
and seeing variables used that way. I also remember asking Elizabeth
about the complex multiplication example, and her replying that Moore
didn't hesitate to use CODE if it made things easier. He wasn't a
purist about staying with Forth words.
I've nothing against the occasional variable or CODE when it makes sense.
That's a different proposition to dumping everything to locals because
one views stack operators as either ugly or clumsy.
Post by Paul Rubin
In the 1970s there just wasn't room for purity in software. Maybe APL
came closest. Lisp was born from the most purest mathematical
abstraction (lambda calculus) but its implementations were full of low
level hackery. The purest language now is Haskell but it is also full
of hacks.
You've read Moore's stance on locals, which has never changed. Others
will go where their interest and thinking takes them. It's not my problem.
My interest is in discovering whether Moore's system works. I can't do
that if I'm using locals.
minf...@arcor.de
2022-11-03 13:06:22 UTC
Permalink
Post by dxforth
My interest is in discovering whether Moore's system works.
Did it work for himself? I gathered that he changed it all the time.
dxforth
2022-11-05 01:15:51 UTC
Permalink
Post by ***@arcor.de
Post by dxforth
My interest is in discovering whether Moore's system works.
Did it work for himself? I gathered that he changed it all the time.
Not the basics AFAIK. No mega-compiler, no locals, no creeping complexity.
It was those who came after that did all that for one reason or another.
Jon Nicoll
2022-11-04 22:16:18 UTC
Permalink
Post by Paul Rubin
Post by dxforth
AFAIK optimizers do a better job when the code presented is already optimal.
These days optimizers work by first splitting everything out into
separate variables so that nothing is ever updated (static single
assignment or SSA form), and then recombining the variables. I have
never implemented SSA and don't understand it perfectly, but I believe
the SSA forms of the 4-local and 6-local versions of that code would be
very similar. Anton would know this much better than I do.
Post by dxforth
Post by Paul Rubin
Well, Moore used VARIABLEs instead of locals,
Does he?
I believe so. I remember looking at cmforth and some of the GA144 code
and seeing variables used that way. I also remember asking Elizabeth
about the complex multiplication example, and her replying that Moore
didn't hesitate to use CODE if it made things easier. He wasn't a
purist about staying with Forth words.
In the 1970s there just wasn't room for purity in software. Maybe APL
came closest. Lisp was born from the most purest mathematical
abstraction (lambda calculus) but its implementations were full of low
level hackery. The purest language now is Haskell but it is also full
of hacks.
I have wondered about that myself - hacks in the implementation of
'pure' languages. Thanks for these snippets.
Hans Bezemer
2022-10-28 16:14:18 UTC
Permalink
Post by dxforth
Post by Anton Ertl
Post by Hans Bezemer
I also don't like long definitions. They're a horror to maintain.
Short words make the logic comprehensible - at every level. Both the lower
level words as well as the higher level words.
I am awaiting you posting code for this problem to support your
claims.
It's not enough he doesn't use locals in his own code - he should do
your coding as well? :)
ROFL! We're slowly becoming soulmates I think. I was contemplating my
answer along the same line of thought - before I read your post. ;-)

Hans Bezemer
dxforth
2022-10-29 05:20:03 UTC
Permalink
Post by Hans Bezemer
Post by dxforth
Post by Anton Ertl
Post by Hans Bezemer
I also don't like long definitions. They're a horror to maintain.
Short words make the logic comprehensible - at every level. Both the lower
level words as well as the higher level words.
I am awaiting you posting code for this problem to support your
claims.
It's not enough he doesn't use locals in his own code - he should do
your coding as well? :)
ROFL! We're slowly becoming soulmates I think. I was contemplating my
answer along the same line of thought - before I read your post. ;-)
It's not as if we hadn't been here before and don't know the answers :)
Anton Ertl
2022-10-29 13:34:32 UTC
Permalink
Post by Hans Bezemer
Post by dxforth
Post by Anton Ertl
Post by Hans Bezemer
I also don't like long definitions. They're a horror to maintain.
Short words make the logic comprehensible - at every level. Both the lower
level words as well as the higher level words.
I am awaiting you posting code for this problem to support your
claims.
It's not enough he doesn't use locals in his own code - he should do
your coding as well? :)
I did do my own coding. Hans Bezemer just made general claims, so I
called him to show that there is substance to his words (and it's
about long definitions, not locals).
Post by Hans Bezemer
ROFL! We're slowly becoming soulmates I think. I was contemplating my
answer along the same line of thought - before I read your post. ;-)
Apparently there is no substance to your words. As for being
soulmates, dxforth has posted code to demonstrate a part of his
positions (and his code actually inspired me to transform the control
structure in the way he suggested).

- 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
Hans Bezemer
2022-10-29 18:09:37 UTC
Permalink
Post by Anton Ertl
I did do my own coding.
What can I say. It shows. LOL!
Post by Anton Ertl
Apparently there is no substance to your words. As for being
soulmates, dxforth has posted code to demonstrate a part of his
positions (and his code actually inspired me to transform the control
structure in the way he suggested).
C'mon. Do you know how old I am? Do you really expect me to dance to
your tune? That's how kids challenge each other in kindergarten!

Hans Bezemer
Loading...