6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sun Sep 22, 2024 9:19 am

All times are UTC




Post new topic Reply to topic  [ 23 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Mon Aug 24, 2020 5:17 am 
Offline

Joined: Wed Aug 12, 2020 2:30 am
Posts: 43
Is it common, or frowned upon, to use memory for arguments to a subroutine?

for example...
Code:
lda #1
sta args
lda #2
sta args+1
lda #3
sta args+2

jsr some_routine_in_another_file

args$ byte 0,0,0 ; Global for CBM prg Studio

And in the other file...
Code:
some_routine_in_another_file
    lda args$
    ldx args$+1
    ldy args$+2
; use these values for something


The reason I ask is because I spent a few hours tracking down a bug. Take the following code...
Code:
defm some_macro
; more code is here but not needed for this post
@done
                    ; restore the original vector
        lda @temp_draw_vector
        sta /1     
        lda @temp_draw_vector+1
        sta /1+1   
        lda #$ea ; $ea = nop opcode
        sta @temp_draw_vector
        sta @temp_draw_vector+1
       
@temp_draw_vector byte $ea,$ea
        endm


What was happening is when the last sta was executed, the bytes for temp_draw_vector were treated as executable code. So as a workaround I just assigned them nop values.

This is developed with CBM prg Studio.


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 24, 2020 5:52 am 
Offline
User avatar

Joined: Tue Jul 17, 2018 9:58 am
Posts: 106
Location: Long Island, NY
Er, it's certainly normal to use memory to store arguments to a subroutine. But you seem to be doing it in a rather strange way, choosing a memory location smack in the middle of your code. You should use a location in RAM well away from the path of your code, and if you don't need to worry about nested calls you can just dedicate a few bytes in zero page to general subroutine argument purpose instead of dedicating bytes to each instance of the macro.


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 24, 2020 6:03 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8510
Location: Southern California
Off the top of my head (where there's no much left :lol: ), five methods come to mind. The following are more or less in descending order of commonness:

  • in A, X, and Y (used most)
  • in variables
  • on the hardware stack (in page 1 on the 6502)
  • on a virtual stack, normally in ZP
  • as data immediately following the JSR. This makes for more overhead for the subroutine to find the data (based on the stacked return address) and then increment the return address past it; but it makes for very neat solutions sometimes.

The stack options add a few cycles' execution time but are of course always re-entrant, with no worries about variables getting stepped on while they're still needed; so in that respect, the stack options prevent bugs.

There's no problem with putting variables between code segments in program RAM. It's done all the time in Forth.

What is the / used for in the assembler you're using, as in "sta /1"?

_________________
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: Mon Aug 24, 2020 6:06 am 
Offline

Joined: Wed Aug 12, 2020 2:30 am
Posts: 43
Agumander wrote:
Er, it's certainly normal to use memory to store arguments to a subroutine. But you seem to be doing it in a rather strange way, choosing a memory location smack in the middle of your code. You should use a location in RAM well away from the path of your code, and if you don't need to worry about nested calls you can just dedicate a few bytes in zero page to general subroutine argument purpose instead of dedicating bytes to each instance of the macro.


I just picked arbitrary numbers for the post.

[EDIT] I see what you're saying I think. The variable is defined somewhere close to my code.


Last edited by DanielS on Mon Aug 24, 2020 6:14 am, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 24, 2020 6:10 am 
Offline

Joined: Wed Aug 12, 2020 2:30 am
Posts: 43
GARTHWILSON wrote:

What is the / used for in the assembler you're using, as in "sta /1"?


It's used in CBM prg Studio as a placeholder for a parameter to a macro.

Example...
Code:
    some_macro 0400 ; Passes 0400 as an argument


Then somewhere else the macro is defined
Code:
defm some_macro
    lda $20
    sta $/1 ; "/1" is replaced with "0400" in the assembly output.
endm


It keeps from having to repeat a common piece of code over and over.


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 24, 2020 6:12 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8510
Location: Southern California
So it doesn't allow macro parameter names that are local to the macro? That would be kind of a pain, but sure makes it easier to write the assembler.

_________________
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: Mon Aug 24, 2020 6:15 am 
Offline

Joined: Wed Aug 12, 2020 2:30 am
Posts: 43
GARTHWILSON wrote:
So it doesn't allow macro parameter names that are local to the macro? That would be kind of a pain, but sure makes it easier to write the assembler.

Apparently not. What do you use for development?


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 24, 2020 6:27 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8510
Location: Southern California
DanielS wrote:
What do you use for development?

For 65c02 and 65816, I use Cross-32 ("C32" for short) on the PC if I'm assembling something that will go into ROM. It was formerly from Universal Cross Assemblers in Canada, but now is sold by Data Sync Engineering, at http://www.datasynceng.com/c32doc.htm . Also available from MPE at http://www.mpeforth.com/cross32.htm . For assembling on my workbench computer code that goes in RAM, I use my home-written assembler that's part of the Forth system onboard in the ROM. In the 1980's I used the 2500AD assembler which my employer paid an arm and a leg for. 2500AD was apparently bought out by Avocet which was a competitor, but then Avocet did not support it, and they eventually shut it down.

When I develop for PIC microcontrollers, I use Microchip's MPASM. There's not much difference between the macro capabilities and languages of these commercial assemblers They mostly work the same.

_________________
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: Mon Aug 24, 2020 8:30 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10938
Location: England
Consider this part of your code:
Code:
jsr some_routine_in_another_file
args$ byte 0,0,0 ; Global for CBM prg Studio


What do you expect to happen when the called routine returns? Unfortunately, the next byte - the next instruction - is some of your data.

It feels to me you haven't quite got the picture, that your assembly source is a description of what content you want in memory. It's not like a high level language, where you would normally interleave descriptions of variables with descriptions of actions:

Code:
funcA (x) {
   int y = 3;
   funcB(x, y)
}


There's a lot going on in a high level language: you give names to values, and the storage for those values is allocated and managed for you. You can initialise those values. Some values are global, some are parameters, some are allocated in the stack.

None of this happens in assembly language programming! You need to decide where to store your values, and manage those allocations: whether globals or stack variables, and if global, whether stored in zero page or in main memory. Whether the storage location is known at assembly time, or only at run time.

Because zero page is limited, it's very common to use the same locations for different purposes at different times.

It's common enough for a program not to use a heap at all: for all locations to be known at assembly time.

I'm not sure what series of exercises or readings is the best way to learn this stuff - perhaps just coding, and struggling, fixing things, and eventually getting it, is the best way.

But I hope it helps to be given the message, that things are different in the land of assembly, and your program text bears a much closer relationship to the machine than you are used to.

I would in fact suggest that macros are potentially confusing you: that the beginner should write a fair amount of plain code before venturing into the use of macros. Their productivity and the efficiency of their code will then improve - macros are great, but possibly not the first thing to learn.


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 24, 2020 8:52 am 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1467
Location: Scotland
DanielS wrote:
Is it common, or frowned upon, to use memory for arguments to a subroutine?


It's not uncommon but it's typically more suited to high level languages than hand written assembler. There are even some CPU architectures that provide instructions to access stack data directly as well as (or instead of) a "push onto a stack" style. This can allow for local variables which can be accessed in any order and recursion, providing the stack is large enough. You also reset the stack at the end of the function - often by storing the last stack pointer at a known location in the stack, so a stack might look like:

Code:
stack+: last program counter
stack+1: last stack pointer
stack+2: parameter 1
stack+3: parameter ...
stack+n: local variable, etc.


so to return you just set the stack pointer to contents of stack+1 and the program counter to stack+0 and off you go.

This can work with the 6502 stack - you just TSX and use it as an index - just remember the small size of the stack, especially if you ever use recursion.

The architecture of the bytecode VM I'm working on does it this way - the code never touches the hardware stack at all as its all done in RAM and it has instructions to let it efficiently access stack variables - a single byte can load or store the first dozen stack variables - it's a very compact bytecode. (Target for the BCPL compiler I'm using on my 816 system)

However... while this provides a good (and generic) mechanism for higher level languages, for 6502 code, you might want to pick the method that's most efficient for that particular function, so e.g. a line draw where the co-ords fit into a byte (e.g. small retro 240x160 screen) - you might as well use X and Y as it saves the store before the function and the load inside the function. This means that you might even have different calling methods for different functions - which from a hand written and speed/efficiency point of view if fine (IMO) you just need to know how each function is called.

You could even develop a 2 tier mechanism though - fast, efficient "byte" type functions and slower, but still compact "word" orientated functions which need to pass in more data. The BBC Micro OS did it this way back in '81 with OSBYTE and OSWORD calls and even modern C compilers are getting good at it, however you still need to provide hints. e.g. in cc65 you can declare a function to be:

Code:
; void __fastcall__ cputc (char c);


and the compiler will generate code to place the value of 'c' in A rather than push it into a stack.

-Gordon

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


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 24, 2020 10:44 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10938
Location: England
I'm tempted to say that it's pretty common in assembly language not even to think in terms of parameters. Your program has a whole bunch of values in memory, and subroutines act on those values. In a sense, there are no functions: there are subroutines.

Of course it's not universal - I can think of a few commonly seen exceptions to this:
- when a function's arguments are small enough and few enough to be passed in registers: it's very common for input and output routines to take a byte or return a byte, and it's natural and efficient to use the accumulator
- when there's an API, so there's a library or the OS, there will be parameters to calls
- when a program is actually written in a style of threaded interpretation

I wonder if learning to program with FORTRAN, or on a programmable calculator, would be a much more appropriate precursor to assembly language, compared to experience with a high level language like C, C++, Java, and so on.


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 24, 2020 12:15 pm 
Offline

Joined: Wed Aug 12, 2020 2:30 am
Posts: 43
BigEd wrote:
Consider this part of your code:
What do you expect to happen when the called routine returns?

It feels to me you haven't quite got the picture...


I guess I didn't expect anything. I just hadn't considered it.

You're right about the picture. I'm working at it though. This is my first assembly project and I'm doing it just to learn C64 assembly programing.


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 24, 2020 12:20 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10938
Location: England
Oh yes, I don't mean to imply that this can't all become clear - I recognise you're on the beginning of a journey. I mean not to criticise, but to describe, what I think might sometimes be going on in the mind of the beginner.

(These days I seem increasingly to be thinking about the process of learning, and the challenges of teaching. There's so much more to teaching than reciting truths!)


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 24, 2020 12:40 pm 
Offline

Joined: Tue Jul 24, 2012 2:27 am
Posts: 674
I concur with BigEd, especially regarding styles widely used on the C64. There are utility pointers and flag/state values in well-known RAM locations that have defined purposes that multiple functions use. These represent the overall state of the system which would be modified by your code. Functions read & write to the global state, instead of necessarily to local parameters.

Some locations would be legitimately temporary work space, and can be shared by multiple functions, depending on reentrancy. But if your input to a function takes more space than just the registers, passing parameters and having the function modify the global state is much more redundant than just having the caller directly modify the global state before calling the function.

_________________
WFDis Interactive 6502 Disassembler
AcheronVM: A Reconfigurable 16-bit Virtual CPU for the 6502 Microprocessor


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 24, 2020 1:11 pm 
Offline

Joined: Wed Aug 12, 2020 2:30 am
Posts: 43
White Flame wrote:
I concur with BigEd, especially regarding styles widely used on the C64. There are utility pointers and flag/state values in well-known RAM locations that have defined purposes that multiple functions use. These represent the overall state of the system which would be modified by your code. Functions read & write to the global state, instead of necessarily to local parameters.

Some locations would be legitimately temporary work space, and can be shared by multiple functions, depending on reentrancy. But if your input to a function takes more space than just the registers, passing parameters and having the function modify the global state is much more redundant than just having the caller directly modify the global state before calling the function.

By global state... Do you mean global variables that I create? Or do you mean areas of memory that I utilize?


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 23 posts ]  Go to page 1, 2  Next

All times are UTC


Who is online

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