<rdrop-exit>
the basic mechanism boils down to unwinding the return stack to a previous point
<rdrop-exit>
and having a "register" that holds that point
<remexre>
er, for CL conditions, the interesting case is being able to say "after the condition was thrown"
<rdrop-exit>
what is CL?
<remexre>
common lisp
<rdrop-exit>
ick
<remexre>
?
<rdrop-exit>
I haven't looked at lisp in 30 years, it's not fresh in my memory
<rdrop-exit>
The core mechanism of exception handling in Forth is very simple due to Forth having two stacks, ANS adds a little overhead with their throw codes, but it's still very basic
<rdrop-exit>
Anything more sophisticated just requires storing extra stuff in the exception frame
<rdrop-exit>
and having code that knows what to do with that extra stuff
<rdrop-exit>
Whether a more sophisticated mechanism would be worth the trouble, I'm not sure
<remexre>
CL's are mainly more useful when you want to express things like "if opening this file fails, create it and retry," or "if a divide by zero occurs, make the result 0"
<remexre>
CL's condition mechanism is*
<rdrop-exit>
None of that requires an extension of the basic mechanism
<rdrop-exit>
try / div/0 cancel
<rdrop-exit>
oops
<remexre>
where cancel moves control flow back to the place where the exception was thrown?
<remexre>
where call-with-handler ( throw-code handler xt -- )
<rdrop-exit>
that's not a fallback handler
<remexre>
correct
<rdrop-exit>
a fallback handler is what happens when no one has handled the exception and control returns to your system
<remexre>
er, yeah, this was me showcasing something you can do w/ conditions that can't be done with ANS-like exceptions (as far as I can tell)
<remexre>
sorry if I was unclear
tabemann has quit [Ping timeout: 240 seconds]
<rdrop-exit>
but you can make a normal handler as sophisticated as you want
<remexre>
sure, but it can't jump back to where the exception was thrown, unless I'm misunderstanding how it works
<rdrop-exit>
you end up at the next instruction
<rdrop-exit>
try foo zorba
<rdrop-exit>
always ends up at zorba
<rdrop-exit>
where you go from there doesn't require the exception mechanism
<rdrop-exit>
begin try foo ... again
<remexre>
yeah, I'm saying in some cases you want to continue where you threw from
<rdrop-exit>
you mean in foo ?
<remexre>
yeah
<remexre>
if somewhere deep in foo there's a recoverable error, it's nice to be able to recover from it and keep executing
<rdrop-exit>
that's what the normal mechanism does
<remexre>
keep executing as if the throw were a no-op*
<rdrop-exit>
some code had to handle the throw
<rdrop-exit>
you handle it then you move on
<remexre>
sure, but handling it unwinds the stack, no?
<rdrop-exit>
yes, you're always catching something that was thrown from a deeper level in the code
<remexre>
whereas conditions can choose to, uh, re-wind? (I guess in practice, they're probably implemented by not unwinding until after the handler runs)
<remexre>
condition handlers*, that is
<rdrop-exit>
you unwind until someone deals with it (and doesn't throw it upstairs)
<rdrop-exit>
the level dealing with it is the level that has the required context knowledge to deal with it presumably
<remexre>
but that makes the case where e.g. you want to describe how to recover from file-not-found somewhat more annoying
<rdrop-exit>
how to recover depends on higher up context knowledge
<remexre>
yeah
<rdrop-exit>
if it doesn't there's no need to throw
<remexre>
but after the higher-up code chooses how to recover (choosing a different path to open, creating the file, etc, etc) it wants to tell the lower code "now continue"
<rdrop-exit>
it still needs to open the file, so it would call open again with the new path
<remexre>
yeah, that's what the "restart retry" was about in my example
<rdrop-exit>
a loop is all that's needed
<remexre>
code that knows an exception is potentially about to be thrown can set up (named) restart points, for various actions you want to do
<remexre>
: print-file ." this should only be printed once" open-file dup read-file type close-file ;
<remexre>
and then a loop no longer suffices
<rdrop-exit>
there are only two situations IMO
<rdrop-exit>
the code has enough context to deal with the situation, then it doesn't need to throw, or the code doesn't then it throws/
<rdrop-exit>
.
<rdrop-exit>
exceptions are only for when the code throws up its hands
<rdrop-exit>
they are not for normal control flow
<remexre>
that's why conditions aren't called exceptions :P
<remexre>
I mean, would you make file-not-found an exception?
<rdrop-exit>
only if the file is always expected to be there
<remexre>
(as a side note, CL has warnings implemented as conditions, since the default handler just continues)
<rdrop-exit>
Lisp has a lot of constly abstractions, you can layer those on Forth's more basic mechanism's but then you get the abstraction overhead in Forth as well
<rdrop-exit>
you want the banana you get the gorilla
<remexre>
no disagreement, though as far as I know, conditions aren't significantly more expensive than normal exceptions would be
<remexre>
mainly because CL has stack unwinding implemented separately from either
<rdrop-exit>
you could do the same in Forth, not use exceptions for these "conditions"
<remexre>
like, in the implementation I've seen, the only difference is that exceptions unwind then run the handler, while conditions run the handler, which itself does the unwind
<remexre>
the main reason I haven't implemented them in forth is because any code I write is so dang ugly :P
<remexre>
because I'm passing two XTs in, and either a numeric code or another XT as well
<rdrop-exit>
down the rabbit hole of complexity
<rdrop-exit>
Forth isn't about surface simplicity
<rdrop-exit>
unlike other systems
<rdrop-exit>
Forth doesn't buy surface simplicity at the expense of more complexity under the hood
<rdrop-exit>
Forth is simple all the way down
<remexre>
sure, but conditions are very little additional code over exceptions
<rdrop-exit>
But exceptions are already expensive
<remexre>
yeah, i've avoided implementing them in the past because of that
<rdrop-exit>
I've never felt a need for anything more sophisticated
<rdrop-exit>
(or more costly)
<rdrop-exit>
the definition that throws an exception is basically saying I give up, caller(s) you deal with it
<rdrop-exit>
that's all that's needed as far I can tell
<remexre>
yeah, and conditions are letting the caller say "I'm done handling it, keep going / retry the last syscall / etc"
<remexre>
/shrug
<rdrop-exit>
That's definitely going to add complexity
<remexre>
since condition handlers run before you unwind, it's minimal
<rdrop-exit>
If a division throws a divide by zero, it's done
<rdrop-exit>
The caller can do what he wants
<rdrop-exit>
there's no need for a predefined menu of options
<remexre>
it's defined by the code that calls THROW
<rdrop-exit>
I don't want my division primitive to have that overhead
<remexre>
isn't it already having the overhead of a branch and potential call to throw, if you're not relying on some hardware exception/interrupt/etc?
<rdrop-exit>
how it does it isn't the point, the callee should be as simple as possible, since the caller is the one with the context to best know what to do
<remexre>
my point is, you're not adding much more code, you're mostly moving it from CATCH to the handler and THROW
<rdrop-exit>
It doesn't make sense to me to burden the lower level with the overhead
<rdrop-exit>
Context increases naturally as you go up
<remexre>
like, from an aesthetic or a performance perspective? because in the "simple exception" case, it'd be surprising to me if you had >10 extra instructions running
<rdrop-exit>
10 instructions is a lot in low level code, but not much in high level code
<remexre>
10 extra instructions in the throwing case*; should be 0 in the non-throwing case
<rdrop-exit>
they're still there all the time even if you only exceptionally use them
<remexre>
like in code size? sure, but they're in the THROW word?
<rdrop-exit>
People are always claiming that their pet complication has very little overhead, even if the complication isn't needed.
<remexre>
/shrug it's not *needed*, but it generalizes exceptions in a very nice way, and it makes some things a lot nicer to express
<rdrop-exit>
Exceptions are already a very general mechanism, this seems to impose a menu of predefined options on the lower level code.
<remexre>
er, no, the "menu" is presented to the handler, not the thrower
<rdrop-exit>
The situation now is that a throw just punts.
<rdrop-exit>
Sorry
<rdrop-exit>
The situation now is that a thrower just punts.
<rdrop-exit>
The smarts on what to do about it are all at a higher level.
<rdrop-exit>
Can't get a more general mechanism than that.
<remexre>
conditions have a superset of the functionality of exceptions
<remexre>
for example, in lisp, you have warnings, which are conditions, where the fallback handler does nothing (or perhaps logs it, I believe it's implementation-specific)
<rdrop-exit>
That's not a problem, the fallback is handled at the system-wide context by the outer interpreter.
<remexre>
sure, but how does the outer interpreter get control flow back to the code that signalled the warning?
<rdrop-exit>
No need
<rdrop-exit>
For example
<rdrop-exit>
If you're loading source from a file and you get a word not defined exception, you stop the load and print a message.
<rdrop-exit>
If you're typing the name interactively, you might just emit a BEEP.
<rdrop-exit>
The exception percolates to the level that knows what to do about it because it has the contextual knowledge.
<remexre>
for an example of warnings, which I don't believe you can easily do with exceptions:
tabemann has joined #forth
<remexre>
I'm writing control code in a loop (e.g. a PID controller), and the sensor I'm reading from gets a value out of the expected range
<rdrop-exit>
ok
<tabemann>
hey
<rdrop-exit>
hey
<remexre>
depending on what the sensor is, I might want to clamp it, might want to terminate execution, might want to use the last value; or (let's pretend this is the case), just ignore it
<remexre>
hiya
<remexre>
er, just ignore it == let the out-of-expected-range value be used
<remexre>
the "just ignore it" case can't be done in an exception handler
<rdrop-exit>
nor should it
<rdrop-exit>
it should be done via a defered word
<rdrop-exit>
that you set to how you want to handle it
<rdrop-exit>
no one's giving up, therefore no exception
<rdrop-exit>
exceptions are a way to punt
<remexre>
I guess...
<remexre>
though then once I have two sensors, I need two deferred words
<rdrop-exit>
(if you always deal with it the same way, you don't even need the deffered word)
<remexre>
yeah, the assumption I'm making here is that I'm controlling multiple sensors with a DO-PID-CONTROLLER word or smth
<rdrop-exit>
you only need one per sensor, if you want different behaviors vs a global behavior
<rdrop-exit>
None of that has to do with exceptions
<remexre>
like this is an example of where you can't make the code use exceptions, because they're not general enough
<remexre>
signalling a condition (instead of calling the deferred word) would be sufficient, though
<rdrop-exit>
No, it's an example where exceptions aren't needed
<rdrop-exit>
Exceptions are a heavyweight mechanism that should be avoided in most cases
<remexre>
idk, I agree in Java / C++, but ANS Forth exceptions are relatively cheap, and Lisp conditions that are handled w/out unwinding are similarly cheap
<remexre>
the major disadvantage I see in Forth and Lisp are that exception-involving control flow is more difficult to read
<rdrop-exit>
Relatively cheap is not a reason to use them inappropriately
<remexre>
I don't see this case being particularly inappropriate
<rdrop-exit>
The only reason to use an exception in real time loop is to bail out completely from the real-time loop
<rdrop-exit>
It's not a replacement for run of the mill control flow, it's a punt
<rdrop-exit>
it's panic
<remexre>
not all uses of conditions are exceptions
<rdrop-exit>
but you said they had more overhead than exceptions
<remexre>
when you're unwinding
<rdrop-exit>
so what are you doing instead of unwinding?
<rdrop-exit>
you're not just simply returning to the caller
<remexre>
from the PID controller example, I might be clamping the top-of-stack value, I might be doing a no-op, I might be logging and one of the above
<remexre>
and then I'd be returning to the caller, yeah
<rdrop-exit>
So either a regular word, or a defered word would do the job, how does a condition mechanism differ from the normal solution?
<remexre>
I don't need to copy the DO-PID-CONTROLLER code and add an extra deferred word for each sensor (or at least each case)
<rdrop-exit>
I don't get it
<remexre>
so I've got three sensors, I'd need three deferred words, right?
<remexre>
and 3 versions of DO-PID-CONTROLLER, one per deferred word
<rdrop-exit>
are you dealing with the out-of-range condition differently depending on the sensor?
<rdrop-exit>
or is it a global setting?
<rdrop-exit>
If it's global then one defered word, if it depends
<rdrop-exit>
then a jump table
<remexre>
per sensor; wdym a jump table? like "what a switch() {} statement compiles to" ?
boru` has joined #forth
boru has quit [Disconnected by services]
boru` is now known as boru
<rdrop-exit>
an array of XTs
<remexre>
one per sensor?
<rdrop-exit>
one XT per sensor
<rdrop-exit>
assuming you have a setting per sensor, if its a global setting you just need one XT (or a defered word)
<remexre>
I guess? seems less elegant than just signalling out of range, though
<rdrop-exit>
for example if its sensor #5 that is out of range
<rdrop-exit>
5 extreme-handler perform
<rdrop-exit>
the 5 would probably be already on the stack
<remexre>
yeah, I've got an ARRAY defining word that does that automatically; but sure, makes sense
<rdrop-exit>
that's one way of dealing with it, but there are tons of other common ways
<rdrop-exit>
PERFORM used to be a common Forth primitive, not sure why they didn't standardize it
<rdrop-exit>
(maybe they did, I can't recall)
<remexre>
huh, okay
<rdrop-exit>
some Forths use the name @EXECUTE
<remexre>
I only started using Forth a year ago, and mainly learned it from the 2012 standard
<rdrop-exit>
I've always prefered the name PERFORM
<remexre>
so I don't know most things that are "used to be", heh
<rdrop-exit>
sure :)
<rdrop-exit>
I find that most of the baggage that new Forthers try to graft onto Forth from other languages unecessary pure overhead
<rdrop-exit>
There's usually a simpler Forth idiom
<rdrop-exit>
which doesn't require a new mechanism to be implemented
<rdrop-exit>
The problem with the standard is that it's complicating Forth by grafting on a lot of the cruft people are used to from other systems
<remexre>
yeah, I kinda feel like the "core" section should be a lot smaller and "core extension" should be larger
<remexre>
but for other sections, it's kinda nice that they've already got a solved, well-explored design
<remexre>
for if you want that cruft
<rdrop-exit>
It's less and less representative of what a Forth should be
<remexre>
eh, I'd be fine with these extensions, if they could be ./configure'd out (or equivalent)
<remexre>
'cause for e.g. exceptions or dynamic memory allocation, sometimes they're worth the overhead, and there's basically no reason to implement them yourself
<rdrop-exit>
exceptions are simple to implement, much of other stuff is C-oriented, there's some really ugly stuff like recognizers
<rdrop-exit>
I've no need for structs, objects, dynamic memory allocation, and the like.
<remexre>
oh, huh, yeah, I wouldn't want those... I don't think they're in the 2012 standard, though
<remexre>
structs are nice, though I usually just "define them" as a bunch of ( addr -- addr ) words with suggestive names, rather than any language support
<rdrop-exit>
I haven't looked at the standard in a while, I sometimes follow discussions on clf and /r/forth
<rdrop-exit>
Maybe I'm misrembering what they've added, or confusing what's in the standard with new stuff being discussed
<remexre>
I think the latter, though they might've dropped some stuff since ANS? idk tho
<rdrop-exit>
I've never implemented a standard compliant Forth, the closest I came was a 83S Forth a long time ago
<rdrop-exit>
The only reason for me to keep up with the standard, is to be able to understand what people are talking about in Forth discussions
<remexre>
I'm not fully standard-compliant, but I try to be "close enough"
<rdrop-exit>
I don't even try, I'm too far gone :)
<rdrop-exit>
Even my most basic words like VARIABLE are non-compliant.
<remexre>
what're you doing non-compliant there?
<rdrop-exit>
I take a size/alignment parameter
<rdrop-exit>
32-bit VARIABLE
<rdrop-exit>
16-bit VARIABLE
<rdrop-exit>
128 VARIABLE
<remexre>
oh, huh
<remexre>
I'm on ARM, so CREATE aligns for me
<rdrop-exit>
128 bytes on a 128 aligned boundary
<rdrop-exit>
My VARIABLE aligns on the size
<rdrop-exit>
64 VARIABLE
<rdrop-exit>
creates a 64 byte variable aligned on a 64 byte boundary
<rdrop-exit>
16-bit VARIABLE or 2 VARIABLE
<remexre>
huh, any particular reason for that?
<rdrop-exit>
creates a 2 byte variable aligned on a 2-byte boundary
<rdrop-exit>
The reason is that I used to have BVARIABLE, 16VARIABLE, 32VARIABLE, VARIABLE
<rdrop-exit>
then I decided it was just bad factoring to have it that way
<remexre>
I meant more for e.g. why 128 VARIABLE aligns to 128
<remexre>
instead of to your cell size
<remexre>
or some other convenient size
<remexre>
e.g. my CREATE aligns to 4 bytes, even though I've got a 64-bit cell size, because code only needs to be aligned to 4 bytes
<remexre>
(and the CPU I'm using does unaligned loads and stores, just not unaligned execution)
<rdrop-exit>
unaligned loads and stores are still slower, it also makes some other things more convenient
<rdrop-exit>
I also have an equivalent to CREATE that takes an alignment without alloting
<remexre>
it's only an extra cycle for me /shrug; I'll possibly change it later, but I've got lower-hanging fruit wrt efficiency
<rdrop-exit>
It's a factor of VARIABLE just as with typical Forth
<rdrop-exit>
It's a freebie, when I'm laying down data I usually know what alignment I'd like it to be at
<rdrop-exit>
(and what size)
<rdrop-exit>
My equiv
<rdrop-exit>
oops
<rdrop-exit>
My equivalent to CREATE is ALIGNED
<remexre>
yeah, I'd just personally make the alignment and size separate
<proteus-guy>
Almost have a working basic (very) runtime compiler going for ActorForth!
<tabemann>
TSC_CR_PGPSC_2 = %100 I think
<tabemann>
hey proteus-guy
<tabemann>
for me zeptoforth is awfully functional for something I only began debugging on wednesday and which is my first semi-working project in Thumb assembly
<proteus-guy>
tabemann, howdy.
<proteus-guy>
nice going!
<tabemann>
right now it doesn't execute words properly when you enter them, but it complains when you enter something that it doesn't see as a word or a number
<tabemann>
I have to figure out why it's not executing words yet
<tabemann>
it took a while to get the last-word-in-flash lookup working properly
<tabemann>
YES
<tabemann>
I got immediate word execution working
<tabemann>
it was a very stupid bug - I switched around the test for interpretation mode and compilation mode by accident
jsoft has joined #forth
<tabemann>
okay, gotta get ready for work
tabemann has quit [Ping timeout: 240 seconds]
X-Scale has quit [Ping timeout: 255 seconds]
X-Scale` has joined #forth
X-Scale` is now known as X-Scale
xek_ has joined #forth
xek has quit [Ping timeout: 272 seconds]
dys has quit [Ping timeout: 240 seconds]
dys has joined #forth
WickedShell has joined #forth
<veltas>
Hyphens or underscores?
<crc>
hyphens
<veltas>
Actually I have a real question
<veltas>
Do Forths typically use the actual 'stack' for the return stack, or do they tend to put return stack somewhere else?
<crc>
two separate stacks, one for data, one for return addresses
<veltas>
Yes I get that
<veltas>
I mean like the stack maintained by a special register, usually called 'SP'
tp has quit [Read error: Connection reset by peer]
<crc>
in terms of hardware, it'd depend on the implementation
<veltas>
That is the 'return stack' on many architectures, including Z80 which is what I'm writing for right now
<veltas>
I'm just wondering if it's a bad idea to use that stack as my 'return stack'
<crc>
in my old x86 system, I used the hardware stack for return addresses
<veltas>
Like if I'm going to shoot myself in the foot by doing that
<crc>
are you dealing with a host os, or running on the hardware directly?
<veltas>
Hardware directly
tp has joined #forth
tp has joined #forth
tp has quit [Changing host]
<crc>
i'd think you'd be fine then, as you'll have full control of everything
<veltas>
Hmm, okay thanks
<veltas>
It can only go so wrong, after all forth implementations aren't meant to be that big
<crc>
on a unix host, mine has 497 words, of which 60 are in the kernel, 262 are in the standard library, and 175 are platform-specific
<tp>
veltas, I've been disconnected so didnt see CRC's reply. The Forth I use, Mecrisp-Stellaris has two separate stacks, the data stack and the return stack
<siraben>
veltas: my z80 Forth uses a simulated stack for the return stack
<siraben>
the data stack is just the processor's normal stack
gravicappa has quit [Ping timeout: 258 seconds]
reepca has quit [Ping timeout: 260 seconds]
dave0 has joined #forth
reepca has joined #forth
<veltas>
siraben: That makes sense as well because of the PUSH/POP efficiency
<proteus-guy>
veltas, if your hardware has another register with addressing options that support a second stack (6809 was good for this) then you take advantage of it, but most CPUs you have to fake it.
<veltas>
Even without CALL/RET
<veltas>
proteus-guy: On Z80 the byte at (HL) can be treated like a slow 8-bit register
<veltas>
So for most purposes (HL) is your good 'second' choice for memory access, but the stack is just far superior in access speed, especially dealing with 16-bit words