6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sat Nov 23, 2024 8:22 am

All times are UTC




Post new topic Reply to topic  [ 110 posts ]  Go to page Previous  1 ... 4, 5, 6, 7, 8  Next
Author Message
PostPosted: Wed Sep 19, 2018 8:56 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895
chitselb wrote:
Code:
: CASE[  ( -- ADR )  COMPILE (CASE#)  HERE 0 , ; IMMEDIATE
: ]END-CASE  ( ADR -- )  HERE SWAP ! ; IMMEDIATE

pretty sure this should be :
: CASE[  ( -- ADR )  COMPILE (CASE#)  HERE 0 C, ; IMMEDIATE
: ]END-CASE  ( ADR -- )  HERE SWAP C! ; IMMEDIATE

No, that is for another variation on (CASE#) that uses an inline branch address instead of an inline byte count. It, like the other version, allows 127 compiled cases, 0 - 126. In both versions, any case number on the stack that falls outside the range of cases defaults to case 0.
Code:
SCR# 93
// (CASE#) -- INLINE BRANCH ADDRESS
HEX
CODE (CASE#)  ( N -- )
   CLC,
   IP )Y LDA,  IP SBC,
   .A LSR,  N STA,
   0 ,X LDA,  N CMP,
   CS IF,  TYA,  THEN,
   .A ASL,  TAY,  INY,  INY,
   IP )Y LDA,  W    STA,  INY,
   IP )Y LDA,  W 1+ STA,  1 # LDY,
   IP )Y LDA,  PHA,  DEY,
   IP )Y LDA,  IP STA,
   PLA,  IP 1+ STA,
   INX,  INX,  W 1- JMP,
END-CODE

SCR# 94
// CASE[ ]END-CASE
HEX
: CASE[  ( -- ADR )
   COMPILE (CASE#)
   HERE 0 , ; IMMEDIATE
: ]END-CASE  ( ADR -- )
   HERE SWAP ! ; IMMEDIATE

Using >MARK and >RESOLVE makes CASE[ and ]END-CASE smaller. Renaming CASE[ to CASE# and using it with THEN makes it smaller still ( ]END-CASE is no longer needed as it is an alias for THEN). However, this assumes your >MARK and >RESOLVE behave similarly to this:
Code:
: >MARK  ( -- ADR 1 )
   HERE 0 , 1 ;

: >RESOLVE  ( ADR 1 -- )
   ?COMP
   1 ?PAIRS  HERE SWAP ! ;

?COMP just aborts with a message if not compiling and ?PAIRS aborts with a message if the two numbers it consumes are not equal. A different compiler security number could be used. Here are the versions without compiler security ( at least on my system ).
Code:
: >MARK  ( -- ADR )
   HERE 0 , ;

: >RESOLVE  ( ADR -- )
   HERE SWAP ! ;

And here is THEN:
Code:
: THEN  ( ADR CS -- )
   >RESOLVE ; IMMEDIATE

CASE[ is renamed CASE# and becomes:
Code:
: CASE#  ( -- ADR CS )
   COMPILE (CASE#)
   >MARK ; IMMEDIATE

And ]END-CASE is an alias for THEN, so it is not needed.
The demo then becomes:
Code:
SCR# 98
// TEST OF CASE#
HEX
: POWER  ." POWERING UP SYSTEMS." ;
: TAKEOFF  ." VERTICAL ASCENT." ;
: HOVER   ." AIRWOLF HOVERING." ;
: TURBO   ." TURBOS ENGAGED!" ;
: LAND   ." LANDING AIRWOLF." ;
VARIABLE HELICOPTER
: AIRWOLF  ( -- )
   HELICOPTER @ CASE#
      POWER TAKEOFF HOVER TURBO
      LAND
   THEN
   CR .S ;
: TEST  ( -- )  6 -1 DO
      I HELICOPTER ! CR AIRWOLF
   LOOP ;

On my system, the version which takes an inline branch is about 27 bytes smaller than the version which takes an inline byte count, including the compiler words, while each use of the mini case structure requires one more byte for the version that takes an inline branch. If I were to use it 27 times the memory usage would be even. If I use it less than 27 times, the branch address version wins out. If I use it more than 27 times, the byte count version wins out.
IIRC you plan to remove the headers for your final application ( you'll probably find a way to strip out the compiler words as well, since they will no longer be needed ). In that event, the first version, the one that takes an inline byte count would be more memory efficient for you.

Here are the two versions in high level Forth.

Here is the version of (CASE#) that takes an inline byte count in high level Forth:
Code:
SCR# 99
// (CASE#) -- HIGH LEVEL BYTE COUNT
HEX
: (CASE#)  ( N -- )
   R> COUNT 2DUP 2* + >R
   ROT TUCK SWAP
   U< AND
   2* +
   @ EXECUTE ;
: CASE[  ( -- ADR )
   COMPILE (CASE#)  HERE 0 C, ;
   IMMEDIATE
: ]END-CASE  ( ADR -- )
   HERE OVER - 1- 2/ SWAP C! ;
   IMMEDIATE

And here is the version that takes an inline branch address in high level Forth:
Code:
SCR# 9B
// (CASE#)  -- HIGH LEVEL BRANCH
HEX
: (CASE#)  ( N -- )
   R> 2DUP DUP @ DUP>R
   SWAP - 2/ 1-
   U< ROT AND
   1+ 2* +
   @ EXECUTE ;
: CASE#  ( -- ADR CS )
   COMPILE (CASE#)
   >MARK ; IMMEDIATE

If you don't have DUP>R, just replace it with DUP >R.

And here are some notes about memory size.
Code:
All versions of mini case structure default to case 0 if the number is outside the range of cases.
The two code level versions of (CASE#) allow a maximum of 127 compiled cases, 0 - 126
The high level version of (CASE#) with inline byte count allows a maximum of 255 cases, 0 - 254.
The high level version of (CASE#) with inline branch allows as many cases as will fit in available memory.
Here are some statistics about the sizes. The bodies tally includes the code fields of the words.

(CASE#) with inline count ( 1 byte )
Size of bodies:     Whole word:
(CASE#)    41 bytes    51 bytes
CASE[      14 bytes    22 bytes
]END-CASE  18 bytes    30 bytes
-------------------------------
Total:     73 bytes   103 bytes

(CASE#) with inline branch address ( 2 bytes )
Size of bodies:     Whole word:
(CASE#)    48 bytes    58 bytes
CASE[      14 bytes    22 bytes
]END-CASE  10 bytes    22 bytes
-------------------------------
Total:     72 bytes   102 bytes

(CASE#) with inline branch address
using >MARK and >RESOLVE
Size of bodies:     Whole word:
(CASE#)    48 bytes    58 bytes
CASE[      10 bytes    18 bytes
]END-CASE   6 bytes    18 bytes
-------------------------------
Total:     64 bytes    94 bytes

(CASE#) with inline branch address
using CASE# and THEN
Size of bodies:     Whole word:
(CASE#)    48 bytes    58 bytes
CASE#      10 bytes    18 bytes
-------------------------------
Total      58 bytes    76 bytes

High level (CASE#) with inline count ( 1 byte )
Size of bodies:     Whole word:
(CASE#)    34 bytes    44 bytes
CASE[      14 bytes    22 bytes
]END-CASE  18 bytes    30 bytes
-------------------------------
Total:     66 bytes    96 bytes

High level (CASE#) with inline branch address with DUP>R
Size of bodies:     Whole word:
(CASE#)    38 bytes    48 bytes
CASE#      10 bytes    18 bytes
-------------------------------
Total:     48 bytes    66 bytes


Cheers,
Jim

BTW this is for a Forth-83 ITC system with 16 bit addressing.


Top
 Profile  
Reply with quote  
PostPosted: Sun Nov 25, 2018 8:44 am 
Offline

Joined: Sat Aug 21, 2010 7:52 am
Posts: 231
Location: Arlington VA
JimBoyd wrote:
Code:
much incredibly helpful code
Cheers,
Jim

BTW this is for a Forth-83 ITC system with 16 bit addressing.

Thank you, this was very useful! Unless there are well-reasoned objections, I intend to implement it to use a syntax like this, using the new CASE# word with ELSE and THEN
Code:
case# thing1 thing2 thing3 thingn ( else defaulted ) then
So in this example, it could look like:
Code:
variable helicopter
: power  ." powering up systems." ;
: takeoff  ." vertical ascent." ;
: hover   ." airwolf hovering." ;
: turbo   ." turbos engaged!" ;
: land   ." landing airwolf." ;
: airwolf
    helicopter off
    true
    begin
        helicopter @
        case#
            ( 1     2       3     4     5 )
            power takeoff hover turbo land
        else
           ( 0  6 )
            not
        then
        1 helicopter +!
        dup
    until drop ;


Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 26, 2018 1:51 am 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895
chitselb wrote:
Thank you, this was very useful!

You're welcome.
Quote:
Unless there are well-reasoned objections, I intend to implement it to use a syntax like this, using the new CASE# word with ELSE and THEN
Code:
case# thing1 thing2 thing3 thingn ( else defaulted ) then
So in this example, it could look like:
Code:
variable helicopter
: power  ." powering up systems." ;
: takeoff  ." vertical ascent." ;
: hover   ." airwolf hovering." ;
: turbo   ." turbos engaged!" ;
: land   ." landing airwolf." ;
: airwolf
    helicopter off
    true
    begin
        helicopter @
        case#
            ( 1     2       3     4     5 )
            power takeoff hover turbo land
        else
           ( 0  6 )
            not
        then
        1 helicopter +!
        dup
    until drop ;

What about this?
Code:
variable helicopter
: power  ." powering up systems." ;
: takeoff  ." vertical ascent." ;
: hover   ." airwolf hovering." ;
: turbo   ." turbos engaged!" ;
: land   ." landing airwolf." ;
: airwolf
    helicopter off
    false
    begin
        helicopter @
        case#
            ( 0     1       2     3     4 )
            power takeoff hover turbo land
        else
           ( unsigned greater than 4 )
            not
        then
        1 helicopter +!
        dup
    until drop ;


Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 26, 2018 8:07 am 
Offline

Joined: Sat Aug 21, 2010 7:52 am
Posts: 231
Location: Arlington VA
I agree, 0-based would be better. The only reason I can come up with for making it 1-based is BASIC's ON-GOTO construct.

JimBoyd wrote:
What about this?
Code:
variable helicopter
: power  ." powering up systems." ;
: takeoff  ." vertical ascent." ;
: hover   ." airwolf hovering." ;
: turbo   ." turbos engaged!" ;
: land   ." landing airwolf." ;
: airwolf
    helicopter off
    false
    begin
        helicopter @
        case#
            ( 0     1       2     3     4 )
            power takeoff hover turbo land
        else
           ( unsigned greater than 4 )
            not
        then
        1 helicopter +!
        dup
    until drop ;


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 04, 2018 7:36 am 
Offline

Joined: Sat Aug 21, 2010 7:52 am
Posts: 231
Location: Arlington VA
I'm building PETTIL with xa65 on Ubuntu, as I don't have metacompilation yet. There's this '?:' operator that usually squeezes a few bytes and I'm fond of using it. Usage:
Code:
: foo   ( -- )
   blah blah ( flag ) ?: whentrue whenfalse  blah blah ;
The run time end of this, `(?:)` occasionally winds up very near the top of a page, and when it adds 6 to IP (for NEXT to get past it and the two choices), if the caller that called `(?:)` is also very near the top of a page, the page pointer in NEXT gets incremented twice! That's not very good, because now IP is 256 bytes away from where it should point to.

When that happens, I relocate a few things to get away from the page boundary, build PETTIL, and things work again. I'm considering two solutions:

1. `?:` compiles a NOP word in front of `(?:)` when it's at that magic address

2. have ENTER increment IP+2 instead of having EXIT do it.

Is #2 kosher, even in a DTC Forth? I've never seen it done, it would involve some other code changes as well, and I'm not too sure if I would bag myself in some unforseen way.


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 04, 2018 9:42 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10986
Location: England
As a general comment, I'd say add the NOP. It's a local and simple solution with very little cost. (That said, I haven't tried to understand where the double-increment comes from.)


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 04, 2018 3:55 pm 
Offline

Joined: Sat Dec 13, 2003 3:37 pm
Posts: 1004
Does DTC use indirect jumps? Or is this a different problem?


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 04, 2018 4:04 pm 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1949
Location: Sacramento, CA, USA
AFAIK, it's a different problem. I believe PETTIL's super-tight super-fast NEXT is the culprit, in that it depends on external mechanisms to increment the high byte of IP, and these external mechanisms can occasionally misfire.

P.S. I have been following Charlie's progress for the last few years, and his dedication and skill in squeezing every last drop of performance out of the NMOS instruction set is quite remarkable. Sometimes squeezing too hard has consequences, though ...

_________________
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)


Last edited by barrym95838 on Tue Dec 04, 2018 4:20 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 04, 2018 4:20 pm 
Offline

Joined: Sat Aug 21, 2010 7:52 am
Posts: 231
Location: Arlington VA
BigEd wrote:
As a general comment, I'd say add the NOP. It's a local and simple solution with very little cost. (That said, I haven't tried to understand where the double-increment comes from.)

Code:
*=$0086
         next:
E6 8B       inc nexto+1
E6 8B       inc nexto+1
         nexto:
6C A8 66    jmp (ip)
   ^^ ^^ <-- self modifying code with indirect jmp, the special sauce


Only 15 clocks, but 'the ugly has to go somewhere.' rule must be obeyed. The responsibility for crossing page boundaries (at compile time) lies with the compiler.
  • For straight line code, the compiler inserts a `page` word into the code stream when DP reaches the end of the page. This bumps the high byte of IP and jumps to `next`
  • For skipping past inline arguments in primitives, a `jmp pad` exit adds some constant number to IP, adjusting IP page when needed, before proceeding to `next`.
  • There's a subroutine `padjust` that does the same thing, but returns to the caller.

As I recall from the bug hunt for this, it was midnight and it showed up unexpectedly, and then it was tricky to reproduce and even trickier to figure out what was going on. First `(?:)` would call `padjust` which would notice the page crossing and increment IP page, leaving the low byte of IP as $xxFE, Then `next` will add two more to that, and we are in the right place.

When the next word following this activity is `exit`it will also notice that we're at $xxFE and bump the page number. Often that was exactly what I wanted. But not twice. That was when I had the eureka moment about inverting the roles of `enter` and `exit`
Code:
      old exit:
A9 02       lda #$02
18          clc
85 8B       sta ip
68          pla
65 8B       adc ip
A8          tay
68          pla
69 00       adc #$00
85 8C       sta ip+1
84 8B       sty ip
4C 8A 00    jmp nexto

      new exit:
68          pla
85 8B       sta ip
68          pla
85 8C       sta ip+1
4C 00 8A    jmp nexto
and this also necessitates fixing `enter` to position IP prior to pushing it to the return stack. And having a look at any code that reads or writes IP.


Top
 Profile  
Reply with quote  
PostPosted: Tue Dec 04, 2018 6:50 pm 
Offline

Joined: Sat Aug 21, 2010 7:52 am
Posts: 231
Location: Arlington VA
I'm trying to automate a build process that will generate several target versions of PETTIL (e.g. PET Upgrade ROM, PET 4.0 ROM, 80-column PET, VIC-20 +24K, VIC-20 16K cart, C=64, C=64 cart... ) with different upper dictionary addresses so it can run standalone or coexist peacefully with either Micromon or Supermon. I've figured out how to get things to autostart and stuff enough into the keyboard buffer that I can get a sort of autoexec.bat capability, enough to load and run some screens of code. This will lead to automated regression tests. Homebrew package management is fun. not.

I should add that since I haven't needed to do it yet, I haven't tested out `?:` yet. This will hopefully do the job.
Code:
: ?:   ( "name1" "name2" == ; flag -- )
    ?comp  $F8 ?page [compile] (?:) ' , ' , ; immediate

20 04 11          jsr enter
xx xx             ' ?comp
xx xx F8          ' (?page) $F8   <-- inline argument follows the CFA
xx xx xx xx xx xx ' (?:) ' name1 ' name2
xx xx [here]      ' exit
where `(?page)` is a word which here checks DP.low vs. $F8 and bumps things a bit when they match, to negotiate the page boundary


Top
 Profile  
Reply with quote  
PostPosted: Fri Feb 22, 2019 12:25 am 
Offline

Joined: Sat Aug 21, 2010 7:52 am
Posts: 231
Location: Arlington VA
As I start work on the metacompiler, I'm thinking of adding three additional `number` punctuation prefixes: " & '
Code:
" quoted_character"                e.g. "P" puts $0050 on the stack
# decimal_value                 \
$ hex_value                      \ these are working
% binary_value                   / already in `number`
& resolve_reference                    resolves reference
' labeled_forward_reference        forward reference


Top
 Profile  
Reply with quote  
PostPosted: Sat Aug 31, 2019 1:01 am 
Offline

Joined: Sat Aug 21, 2010 7:52 am
Posts: 231
Location: Arlington VA
Original way, also FIG & Blazin' way, seems like how everybody does it way
Code:
ENTER pushes the current IP to the return stack
EXIT adds 2

I'm changing it to this:
Code:
ENTER pushes the +2 address where we will wind up, e.g. IP := IP+2 -(IP==$FF), with page crossing considered
EXIT simply pops the return stack to IP, without modification


The reason I am doing this is NOT because I felt like redesigning the inner interpreter for fun. It's because i added a word `?:` to PETTIL. The word `?:` compiles 6 bytes, and if DP is at xxFF then `?:` will also ALLOT a byte. PETTIL does this so that the following CFA will not straddle a page boundary --
Code:
the (headerless) CFA of `(?:)`
the CFA to EXECUTE when TRUE
the CFA to EXECUTE when FALSE
maybe a junk byte if DP is $xxFF


This compilation strategy remains unchanged. What's different is the pre-increment vs. post-increment of IP in the inner interpreter, which must behave uniformly throughout.

I ran into situations where `(?:)` near the top of a page would increment the page (IP high byte) and then EXIT would do it again, ouch, crash! This is what I came up with. As always, when I do a thing that seems nonstandard, I am plagued with self-doubt. I will remain confused until I get the MSW (my stuff works) award and am about halfway through rewriting everything that touches IP , about a dozen words. Any thoughts on this would be most welcome.

In other news, having test automation and judicious use of AutoKey has made the VICE debugger very useful.


Top
 Profile  
Reply with quote  
PostPosted: Sun Sep 01, 2019 2:24 am 
Offline

Joined: Wed Aug 21, 2019 6:10 pm
Posts: 217
Seems simpler if ?: checks if it's GOING to end up at $[xx][FF], and if so, compiles PAGE and then allots to place (?:) at $[xx+1][00]. Wastes 7 bytes in a roughly 1/256 chance.

But it is not standard whether to increment the IP before or after stacking. When NEXT is a short macro, it's often after, just because that makes EXIT into "POP RS; NEXT". But in a processor where the two are symmetric, doing the one that is more convenient for words manipulating the inner interpreter seems to make sense.


Top
 Profile  
Reply with quote  
PostPosted: Sun Sep 01, 2019 6:15 am 
Offline

Joined: Sat Aug 21, 2010 7:52 am
Posts: 231
Location: Arlington VA
BruceRMcF wrote:
But it is not standard whether to increment the IP before or after stacking. When NEXT is a short macro, it's often after, just because that makes EXIT into "POP RS; NEXT". But in a processor where the two are symmetric, doing the one that is more convenient for words manipulating the inner interpreter seems to make sense.


my exit was "POP RS->IP; JMP NEXT"
now it is "POP RS->IP; JMP NEXTO"
Code:
next    inc ip
        inc ip
nexto   jmp (ip)


Several other words also have inline parameters, and I'd like to resolve any problems without just tossing `PAGE` in there, optimally wasting only 1 byte to realign things.

In one situation (JSR ENTER, JSR DODOES) the CFA immediately follows the JSR call. This calls for the "insert junk byte before" cure. In the other situation, there's an inline parameter with its last byte at $xxFE, where "insert junk byte after" fixes things up. In the latter case, the junk byte is only required when the very next thing to follow at $xxFF is a (2-byte) execution token (aka "XT"). If the thing following the inline parameter ending a $xxFE is a primitive, or data, or the JSR at the CFA of another word, or anything else, then dropping a junk byte is unnecessary. I use the words `CFA,` (which compiles a JSR opcode with an address from the stack, three bytes), or `XT,` (does the code field address from the stack, allot two bytes). CFA, will prepend a junk byte at $xxFC, and XT, will append a junk byte at $xxFF, but it does so by prepending it before XT, encloses the execution token itself. That should do it from the compiler side.


Top
 Profile  
Reply with quote  
PostPosted: Mon Sep 02, 2019 5:12 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8544
Location: Southern California
My unnest for the '816 (actually 65802) ITC Forth assembly-language source code is just:
Code:
        HEADER "unnest", NOT_IMMEDIATE  ; ( -- )
unnest: PRIMITIVE                       ; This does the opposite of
        PLA                             ; nest, and the same as EXIT.
        STA     IP                      ; It is often called SEMIS
        GO_NEXT                         ; because it's compiled by ;
 ;-------------------

(and EXIT's CFA just points to unnest's code. The reason to have both is that SEE, the de-compiling word, stops when it finds unnest but not EXITs that might come before the end of the word.) PRIMITIVE is a macro that just puts the parameter field's address in the CFA. GO_NEXT is a macro which in most people's applications would just assemble JMP NEXT.

In my assembly source code for the kernel, the HEADER macro includes the lines:
Code:
        IF    $ & 1        ; If next addr is odd,
              DFB   0      ; add a 0 byte before you
        ENDI               ; lay down the link field.

("DFB" in the C32 assembler is "DeFine Byte"; so the above just lays down a zero byte.) This way, the LFA, CFA, and PFA are even-aligned, regardless of the name's length and whether or not it started out aligned. The '816 of course doesn't have the JMP (xxFF) bug, but I did it this way because there was a benefit for de-compiling with SEE. (It has been many years since I worked on it, and I can't remember off the top of my head why it helped.) CREATE for the target does the same kind of thing, using ALIGN.

nest is:
Code:
nest:   PEI     IP       ; PEI IP replaces LDA IP , PHA here.  nest is the
        LDA     W        ; runtime code of : (often called DOCOL ). It is not
        INA2             ; really a Forth word itself per se; but it is pointed
        STA     IP       ; to by the CFA of secondaries.
        GO_NEXT
 ;-------------------

(INA2 is just a macro that lays down INA, INA.)

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 110 posts ]  Go to page Previous  1 ... 4, 5, 6, 7, 8  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 16 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron