6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Tue Sep 24, 2024 8:25 pm

All times are UTC




Post new topic Reply to topic  [ 19 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: Macros vs. subroutines
PostPosted: Thu Feb 25, 2021 7:55 pm 
Offline

Joined: Mon Feb 15, 2021 2:11 am
Posts: 100
It seems to me that large blocks of assembly code used in multiple places should be written as subroutines, and short blocks of code that are repeated frequently should be macros. I reasoned this is so because I don't want to needlessly duplicate large blocks of code, so a subroutine makes sense for large blocks of code use in multiple places. For example, I have written as a subroutine my code to loop through a string and print characters to the console, and code to draw a collection of shapes to screen.

OTOH, subroutine calls have a bit of overhead associated with them, including moving data into correct registers or memory location in order to set arguments and deal with return values, so probably those should be macros. So I've written code for 16-bit addition and subtract, about sixteen bytes apiece, as macros.

Is my reasoning sound? Am I missing anything?


Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 25, 2021 8:05 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8515
Location: Southern California
There will be times where the repeating pattern is the same but internal details are not, so you can't just use a JSR. The differences from one occurrence to another might be an operand, a string or other data, an address, a condition, etc.. It would be helpful to be able to tell the assembler, "Do this sequence here; except when you get down to this part, substitute-in such-and-such," or, "under such-and-such condition, assemble this alternate code." That's where it's time for a macro.

See more in my article at http://wilsonminesco.com/StructureMacros/ . I use these macros even to do program flow-control structures, as you can see in the last 40% of the page on simple multitasking methods at http://wilsonminesco.com/multitask/ .

_________________
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  
PostPosted: Fri Feb 26, 2021 12:56 am 
Offline

Joined: Sun Apr 26, 2020 3:08 am
Posts: 357
My programming method differs substantially enough from the norm that macros are pretty much redundant for useability. What I do is find as many common routines as possible with a lot of similarities and try to join them into a single sub-routine, then make multiple uses for that sub-routine. It really goes a long way to keeping code small.

I also find source with macros very hard to streamline until I get the source expanded without macros.

What it mostly comes down to though, is readability by the developer and what you are trying to accomplish.

My recommendation is If you want the assembled code to be more compact with the use of sub-routines as the final result, you won't need macros because very little code gets duplicated to make macros useful.
If you want the source to be more compact and somewhat easier to read or follow, then use macros.

One last mention is that in-line code is faster to process compared to subroutines or loops and if memory permits, by all means use it.


Top
 Profile  
Reply with quote  
PostPosted: Fri Feb 26, 2021 3:31 pm 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1467
Location: Scotland
Sean wrote:
It seems to me that large blocks of assembly code used in multiple places should be written as subroutines, and short blocks of code that are repeated frequently should be macros. I reasoned this is so because I don't want to needlessly duplicate large blocks of code, so a subroutine makes sense for large blocks of code use in multiple places. For example, I have written as a subroutine my code to loop through a string and print characters to the console, and code to draw a collection of shapes to screen.

OTOH, subroutine calls have a bit of overhead associated with them, including moving data into correct registers or memory location in order to set arguments and deal with return values, so probably those should be macros. So I've written code for 16-bit addition and subtract, about sixteen bytes apiece, as macros.

Is my reasoning sound? Am I missing anything?


At the simple level, the trade-off is space vs. speed.

Use subroutines - compact code, but runs slower. Use Macros, then it runs faster but the code is bigger.

I use macros extensively in my BCPL VM environment as I want to make every cpu cycle count for as much as possible - however I have the luxury of lots of RAM. I'm even unwinding loops just to save a JMP in some places too - and that was initially by defining a macro then just repeating it 32 times until I realised my assembler (as65 from the cc65 suite) could auto repeat loops...

Cheers,

-Gordon

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
PostPosted: Fri Feb 26, 2021 5:50 pm 
Offline
User avatar

Joined: Mon May 12, 2014 6:18 pm
Posts: 365
One way to think of macros is like a sophisticated copy and paste. For something like 16 bit addition, you will always be using the same 7 line sequence, so a macro can save you typing. You probably wouldn't want to make that it's own subroutine unless you're going for extreme size reduction.


Top
 Profile  
Reply with quote  
PostPosted: Fri Feb 26, 2021 6:17 pm 
Offline
User avatar

Joined: Fri Aug 14, 2015 5:19 pm
Posts: 28
Location: ENCOM mainframe.
imo which ever best suits individual programming style is the one to use, no right answer.

I don't use macros because I didn't have them with EDTASM+ in the 80's though that Assembler line numbers like BASIC interestingly enough! :)

I also think they can be limiting because they are high level programming language that creates blocks of asm.

I wrote BASIC compilers for the 6502 and a soft blittter in 6502 Assembly without using any macros but I did use subroutine to create nuanced macros; the BASIC Compiler is a macro compiler in that sense, the macros written by hand in assembly with the subroutine psuedo-op can be tuned in more detail than macros written by hand in a macro language.

Depending upon your application, high level Macro languages can go very nicely combined with Assembly to speed development in much the same way BASIC and assembly were combined bitd with the hotspots benefitting the most from pure assembly coding.

_________________
Load BASIC from tape on your Atari 2600:
http://RelationalFramework.com/vwBASIC.htm


Top
 Profile  
Reply with quote  
PostPosted: Fri Feb 26, 2021 11:33 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8393
Location: Midwestern USA
Macros were invented back in the early days of assembly language programming specifically to automate source code generation in areas where instruction sequences were similar, and varied only in what was in the parameters. Some early systems didn't have subroutine capabilities (some didn't even have a hardware stack), so repetitive code was unavoidable. Writing assembly language code on the early mainframes was not an interactive process and even by the time I had started professionally programming in 1970, there was little interactivity. So anything that could reduce the tedium and risk of errors was welcomed. That made a macro-capable symbolic assembler worth its weight in gold. When I bought my Commodore 64 in 1983, the first piece of software I purchased for it was Commodore's Macro Assembler Development System (MADS). It cost 30 bucks at the time, and was worth every penny.

Even though today's development environments are interactive there's no getting around the fundamental nature of assembly language. Each machine instruction only accomplishes a little bit in the overall scheme of things, which means even a small program could have a lot of source instructions. Given assembly language's one-for-one relationship to the resulting object code, judicious use of macros can save the programmer from a lot of typing. That reason alone is sufficient justification for their use.

Macros are best used in three places: 1) extensions to raw assembly language to produce elements of higher-level languages (see Garth's website for a good discussion on this); 2) synthesis of machine instructions not present on the target system; 3) preparatory operations needed to call a function, be it a local subroutine or an operating system API function.

An example of the latter use is particularly applicable to the 65C816. The 816 offers stack addressing capabilities that are not available with the 65C02. Having such capabilities opens the door to being able to pass an arbitrary number of parameters of arbitrary sizes and types into a function call by pushing them in a defined order to the stack, mimicking the behavior of function calls in C. The problem comes in making sure the resulting stack frame being created is correct in all respects—an error could unbalance the stack and provoke a crash. This is an ideal application for a macro.

For example, in my code library, I have a function that will fetch one or more blocks from a SCSI random access device. Prior to calling this function, the caller must push the SCSI ID, SCSI LUN, logical block address (LBA), number of blocks desired, and the buffer's starting address, which may be anywhere in the 816's 16MB address space.. The SCSI ID and LUN are eight bit values. The number of blocks is a 16-bit value. The LBA and buffer address are 32-bit values:

Code:
        sep #%00100000        ;8-bit accumulator
        lda #id               ;SCSI ID (0-7)
        xba                   ;move to B-accumulator
        lda #lun              ;SCSI logical unit (0-7)
        rep #%00110000        ;16-bit registers
        ldx !#lba & $ffff     ;logical block address LSW
        ldy !#lba >> 16       ;logical block address MSW
        pha                   ;push ID & LUN
        phx                   ;push LBA LSW
        phy                   ;push LBA MSW
        lda !#nblk            ;number of blocks requested
        ldx !#buffer & $ffff  ;buffer address LSW
        ldy !#buffer >> 16    ;buffer address MSW
        pha                   ;push number of blocks
        phx                   ;push buffer address LSW
        phy                   ;push buffer address MSW
        jsr getblk            ;get blocks
        bcs scsierr           ;error

That's a lot of stuff to keep straight...and there are other calls like it. A trivial mistake could create a useless stack frame or worse yet, unbalance the stack. All that is avoidable with a macro invocation:

Code:
          getblk id,lun,lba,nblk,buffer

The macro creates the proper stack frame, the details of which are of no concern to the main program, and calls the function. Hence the macro both insulates the programmer from the ugliness of the required assembly language mumbo-jumbo and offers a friendly syntax for fetching disk blocks.

So the point is, macros are best used where the effects of a higher-level language are desired without the execution speed penalties of HL languages, or where a complicated function call must be made, e.g., my aforementioned SCSI block-read function, necessitating a lot of setup code. There is, of course, a caveat. Wanton use of macros may cause considerable code bloat and may inadvertently introduce bugs due to unanticipated side-effects from register re-use. I generally discourage the use of macros as "subroutines," except in those cases where in-line code will patently improve execution speed. Subroutine call machine instructions exist for a reason.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Sat Feb 27, 2021 7:14 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8515
Location: Southern California
BigDumbDinosaur wrote:
Macros are best used in three places: 1) extensions to raw assembly language to produce elements of higher-level languages (see Garth's website for a good discussion on this); 2) synthesis of machine instructions not present on the target system; 3) preparatory operations needed to call a function, be it a local subroutine or an operating system API function.

I just thought of another one:
Macros can also be used for situations where you're not even generating actual executable code. Macros are really code for the assembler itself to run; and although they do usually generate executable code for the target system, they can be used for managing data in the assembler itself, or for laying down data bytes for the target system.

An example of the latter would be my headers for Forth words. Headers are used so Forth can find the words later for compilation. (If you're making a system where the compiler won't be accessible to the user, you can forgo the headers.) The HEADER macro looks at variables HEADERS? and OMIT_HEADERS to see whether globally or locally headerless code is in effect at the moment, and if not, lays down the name field with the actual name, its length, the precedence bit, and smudge bit, then the two-byte link to the last previous name, aligning if necessary (ie, making sure it starts on an even address).
Code:
HEADER:         MACRO NAME, precedence          ; Lay down name and link fields.

 IF HEADERS? && ! OMIT_HEADERS  ; If HEADERS is true and OMIT_HEADERS is false,
                                ; then go ahead lay down the header.
    last_NFA:   SETL    new_NFA
     new_NFA:   SETL    $
                DFB     precedence | {npc-$-1}, NAME
         npc:                           ; Use this to calc name length above.
        IF      $ & 1                   ; If next addr is odd,
                DFB     0               ; add a 0 byte before you
        ENDI
                DWL     last_NFA        ; lay down the link field.
 ELSE
        IF      $ & 1
                DFB     0               ; Even if headers are not allowed,
        ENDI                            ; you should still align.
 ENDI
                ENDM
 ;-------------------

Then the its use (which comes up hundreds of times in the code) looks like in this example from my 65816 code:
Code:
        HEADER "2*", NOT_IMMEDIATE      ; ( n -- 2n )
_2STAR: PRIMITIVE
        ASL     0,X
        GO_NEXT
 ;-------------------

The two-byte code field address is laid down separately, in this case by macro PRIMITIVE. Obviously the HEADER macro is far more friendly than writing out its contents every time, and you sure can't make it a subroutine (because it does not lay down any executable code, which is the point here), and it's definitely less prone to error.

An example of managing data for the assembler itself and not laying down even a single byte is the BEGIN in this BEGIN...UNTIL program structure:
Code:
        BEGIN
           <actions>
           <actions>
           <actions>
        UNTIL_ZERO

The BEGIN macro only puts the current assembly address on a stack I set up in the assembler itself. The UNTIL_ZERO macro lays down a BNE back to where the BEGIN marked. No labels needed. When the structure's contents result in the Z flag being set at the end, execution will drop through at the BNE instead of branching back up.

Because the markers are on an actual stack I set up in the assembler itself, the program structures can be nested with the same type or other types, many levels deep, and the branches will all go to the correct places.

In the case of an IF...END_IF structure, the END_IF macro doesn't lay down a single byte of its own, only fills in the branch distance for the conditional branch instruction laid down by the IF. (And again, they're nestable.) You could have for example,
Code:
        CMP  #14
        IF_EQ
           <actions>
           <actions>
           <actions>
        END_IF

In this one, the IF_EQ macro just lays down a BNE instruction, but the branch distance is not valid until the END_IF macro is encountered and fills in the branch distance above. Again, no labels needed.

These macros lay down exactly the same code you would do by hand (meaning there's no penalty in run speed or memory taken), but it's more clear, maintainable, less prone to bugs, and you'll be more productive. I have these and a lot more in my article, all tested (for the C32 assembler), and you could of course make more if you like.

_________________
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  
PostPosted: Sat Feb 27, 2021 2:35 pm 
Offline

Joined: Wed Jan 08, 2014 3:31 pm
Posts: 578
I am a big fan of macros when I program in assembly. The reason is that they reduced typing of repeated code blocks and raise the level of semantic abstraction when reading code. For example:

The 6502 has no add without carry, so you always clear carry then add. As pointed out above, you're synthesizing a missing machine instructions. There's several other missing instructions that macros make easier.

Incrementing a word is always the same pattern of code. Repeating it by hand is unnecessary typing and potentially error prone. This is also true for all 16 bit mathematical operations.

The lack of stack relative addressing and small stack means the 6502 lacks a robust procedure calling mechanism. There are patterns to workaround this, but after you pick one you might want to wrap it in macros. That way if you change your mind, you change the macro, and not all the code that invokes it.


Top
 Profile  
Reply with quote  
PostPosted: Sat Feb 27, 2021 3:47 pm 
Offline

Joined: Sun Nov 08, 2009 1:56 am
Posts: 396
Location: Minnesota
I studiously avoided learning how to use macros until I ran out of memory space for source code in a large program I was writing. Source larger than memory would hold could be read in from disk, but this imposed a huge time penalty in assembly time. So I decided that to save space I would write macros. This would be a net win if the size of the macro definition plus all its invocations was less than writing out what it was supposed to do in detail each time. I created macros for common 16-bit operations, move, add, subtract, etc. It turns out that if your assembler allows nested macros, then you can write little macros that do just a little bit and then combine them into more capable bigger macros. So you can write a "move" macro that understand most of the addressing modes of the 6502 and then use many of the little macros again in add and subtract macros that also "understand" the same address modes.

So that worked for keeping everything in memory, at least for a while. But you can also write macros to do things like allocate labelled memory without worrying too much about exactly where it is (within limits, of course). The trick is to have a macro that "remembers" the last address allocated and puts the next request right after that, updating as it goes. And that was also helpful.

You can also rename or alias mnemonics. These don't do anything but make clearer programmer intention. My most common use of this is to rename BIT zp and BIT ab into "SKPB" (skip byte) and "SKPW" (skip word). They execute but the result doesn't interest me; generally there is a value in one of the registers that I want preserved through a whole chain of these with entry points to load those registers. Most often these are error codes, because this technique saves space at the expense of time, but I don't really care how long it takes because reporting an error is not usually time sensitive.


Top
 Profile  
Reply with quote  
PostPosted: Sun Feb 28, 2021 11:06 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8393
Location: Midwestern USA
Martin_H wrote:
The lack of stack relative addressing and small stack means the 6502 lacks a robust procedure calling mechanism. There are patterns to workaround this, but after you pick one you might want to wrap it in macros. That way if you change your mind, you change the macro, and not all the code that invokes it.

Or, you could use the 65C816 and have stack relative addressing, along with the ability to create an ephemeral direct (zero) page on the stack. :D

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Thu Mar 04, 2021 11:08 pm 
Offline

Joined: Mon Feb 15, 2021 2:11 am
Posts: 100
Thank you all for your diverse input. Most of my macros so far have been to data between struct fields and registers, or to simplify 16 bit operations. On the 16 bit operations front, I got most of what I needed implemented, then had a sudden thought: my 16-bit macros overlap about half of what the SWEET16 opcodes cover.


Top
 Profile  
Reply with quote  
PostPosted: Fri Mar 05, 2021 12:22 am 
Offline

Joined: Wed Jan 08, 2014 3:31 pm
Posts: 578
Sean wrote:
On the 16 bit operations front, I got most of what I needed implemented, then had a sudden thought: my 16-bit macros overlap about half of what the SWEET16 opcodes cover.

That makes sense as Woz invented SWEET16 for 16 bit operations. But macros inline their code so they are faster than a byte code VM.


Top
 Profile  
Reply with quote  
PostPosted: Fri Mar 05, 2021 1:04 am 
Offline

Joined: Mon Feb 15, 2021 2:11 am
Posts: 100
Martin_H wrote:
Sean wrote:
On the 16 bit operations front, I got most of what I needed implemented, then had a sudden thought: my 16-bit macros overlap about half of what the SWEET16 opcodes cover.

That makes sense as Woz invented SWEET16 for 16 bit operations. But macros inline their code so they are faster than a byte code VM.


Thanks for that confirmation. That's why I kept going with the macros I needed rather than implementing SWEET16. I figured macros might result in three times as many bytes for each 16-bit op, but I'm not using so many such ops that the decreased code size seemed worth the slower speed. 6502 code assembled by CA65 is already so much smaller than what CC65 generated from my first stab with C for the 6502.


Top
 Profile  
Reply with quote  
PostPosted: Fri Mar 05, 2021 2:33 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8515
Location: Southern California
A macro can call a subroutine and still make the code to set up the parameters for the subroutine more automated. There's no reason it has to make the assembled code any longer; it should just make the source code more clear and concise.

_________________
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  [ 19 posts ]  Go to page 1, 2  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 5 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: