Parameter Passing: Difference between revisions

 
(8 intermediate revisions by one other user not shown)
Line 23:
Certain commands such as <code>INC</code>,<code>DEC</code>,<code>ASL</code>,<code>LSR</code>,<code>ROL</code>, and <code>ROR</code> inherently alter the memory address that is used as an argument. Thus their use on a memory address is pass-by-reference.
 
<syntaxhighlight lang="6502asm">
<lang 6502asm>ROL $81 ;rotate left the bits of the value stored at memory address $0081. The old value of $0081 is discarded.
INCROL $2081 ;addrotate 1left tothe bits of the value stored at memory address $200081. The old value of $0081 is discarded.</lang>
INC $20 ;add 1 to the value at memory address $20.
</syntaxhighlight>
 
However, opcodes that use a register as their primary operand are strictly pass-by-value.
 
<syntaxhighlight lang="6502asm">
<lang 6502asm>ADC $81 ;add the value stored at memory address $81 to the accumulator. The value at memory address $81 is unchanged.
;If the carry was set prior to this operation, also add an additional 1 to the accumulator.
 
Line 39 ⟶ 42:
 
LDA $40 ;load the value stored at memory address $40 into the accumulator
ROR A ;rotate right the accumulator. This is only a copy. The actual value stored at $40 is unchanged.</lang>
</syntaxhighlight>
 
The main takeaway from this is that by default, the use of <code>LDA/LDX/LDY</code> commands to load from memory is pass-by-value. In order to actually update the memory location, you must store the value back into the source memory address.
Line 47 ⟶ 51:
Generally, parameters are either passed using the stack or with temporary zero-page memory addresses. While these are both forms of memory that are getting overwritten, the actual sources of the data remain the same.
=====Example of in (immutable)=====
<syntaxhighlight lang ="6502asm"> lda sourceData
lda sourceData
jsr MultiplyBy4
 
Line 54 ⟶ 59:
lsr A
lsr A
rts</lang>
</syntaxhighlight>
 
=====Example of out (result)=====
This routine reads the joystick output on Commodore 64 and stores it in the output variable <code>joystick1</code>.
<langsyntaxhighlight lang="6502asm">ReadJoystick:
ReadJoystick:
lda $DC00
ora #%11100000
sta joystick1
rts</lang>
</syntaxhighlight>
 
=====Example of in/out (mutable)=====
This one's tricky because of the way pointers work on the 6502. Unlike the other architectures of its day there are no "address registers" per se, but we can make our own using the zero page.
 
<syntaxhighlight lang="6502asm">
<lang 6502asm> LDA #$03 ;load the low byte of the parameter's address
STA $20 ;store it in the low byte of our pointer variable.
LDA #$00 ;load the high byte.
Line 82 ⟶ 93:
adc #$20 ; 0x20 = decimal 32
sta ($20),y ; overwrite the old value stored in $0003 with the new one.
rts</lang>
</syntaxhighlight>
 
===Example [[68000 Assembly]]===
Line 88 ⟶ 100:
Parameters are typically passed via the stack or through registers. For human-written assembly, the programmer can pass an argument by reference by passing a pointer to the argument rather than the argument itself. Of course, the function will need to dereference that pointer; however whether the function writes back the new value to that memory address is also decided by the programmer. This means that just because a parameter is passed by reference does not mean that it gets altered by the function that received it. This (contrived) example shows this concept in action.
 
<langsyntaxhighlight lang="68000devpac">start:
start:
MOVE.L #$00FF0000,D0 ;load D0 with the pointer 0x00FF0000 (I decided this was a pointer just for example's sake.)
MOVE.L D0,-(SP) ;push that value onto the stack.
Line 101 ⟶ 114:
MOVE.L (A0),D1 ;dereference the pointer (we're treating it as a pointer to an int)
ADD.L D1,D0 ;add the values.
RTS ;and return</lang>
</syntaxhighlight>
 
 
===Example [[Ada]]===
Line 115 ⟶ 128:
 
Early versions of the ARM were unable to directly operate on memory without loading the value into a register first. So almost all commands were inherently pass-by-value unless they explicitly stored an updated value in the same memory location.
<syntaxhighlight lang="ARM Assembly">
<lang ARM Assembly>;this example is for the ARM7TDMI and might not be true for newer revisions.
mov r0,#memoryAddress ;r0 contains the memory address, not the value stored within.
mov r1,#anotherMemoryAddress ;r1 contains the memory address, not the value stored within.
Line 124 ⟶ 138:
mov r2,#memoryAddress ;load the MEMORY ADDRESS into r2
add r0,r0,r1 ;add r1 to r0 and store the result in r0
str r0,[r2] ;store the value of r0 into the MEMORY ADDRESS contained in r2.</lang>
</syntaxhighlight>
 
Typically when talking about pass-by-value or pass-by-reference, a programmer is referring to the contents of a memory address. The stack and the CPU's data registers are not included in the conversation. It's expected that a data register gets "clobbered," after all, it's impossible to do actual math otherwise. Or so you might think, but the ARM takes the concept of "pass-by-value" one step further. The data registers themselves can be operated on in some ways without altering their contents:
Line 130 ⟶ 145:
* In any operation, the contents of a data register can be bit-shifted or bit-rotated just for that operation, and the actual value is unchanged.
[[x86 Assembly]] can do neither of the above; at least one of its registers used as an operand must be the destination for the result.
 
 
===Example [[C]]/[[C++]]===
Line 136 ⟶ 150:
 
===Example [[Fortran]]===
Early versions of the language used only by reference parameter passing for the arguments, but the formal parameters were copied locally and then copied back to the actual parameters at the end of the call. This is also known as "call by value result" or "call by copy-restore".
 
Starting from Fortran 90 (and later), functions and subroutines can specify an ''intent'' for the argument; intent can be: '''in''' (unmodificableunmodifiable argument), '''out''' (argument used to return a value; the starting value must be intended not significativesignificant, undefined), '''inout''' (argument both is intended for input and can be modified, i.e. return the value modified by the function/subroutine).
 
Trying to modify an argument with ''intent in'' triggers an error at compile time.
 
===Example [[M2000 Interpreter]]===
All parameters are passed by value is a stack of values. This stack is a collection of values, and that collection is passed from a module to a module as is, and also used for return back values. Functions start with a fresh stack of values, and at that stack all the parameters from the call stored there. To read parameters from start of values we use Read. A definition Function a(k as integer) {code here} is identical with that: Function a {read j as integer : code here}. Because we can use read at any point of a module or function we can handle parameters with many ways before actually pop them from the stack, like we can check type, we can rearrange and use the stack of values for any use.
 
We can pass by reference values, although actually we store a weak reference and at the Read statement this reference change to normal if it is valid (or an error occur). So call thisModule &Z pass a string as a weak reference, and at the thisModule we have the Read &t which make t to hold the same memory as the Z.
 
Some objects are passed by value but because we pass the pointer to object we can change it. If we pass an array with parenthesis like thisModule A() we pass a pointer but the interpreter knows that this is a by value pass so at read statement the Read K() copy the A() to K(). If we wish to pass by reference then we use this thisModule &A() but the Read statement has to be prepared to get the reference like this Read &K(). So we can't use the same parameter by value and by reference at will, we have to make the code at read statement to be by value or by reference. We can use code to examine the type of parameter in the stack of values and then decide which read statement to use. So there is always a way to program a stack handler, to cope with by parameter types, although there is no automatic except for those which is always by reference and those which is always by value.
 
We can pass by reference functions. This is a copy of function code (as string) inside a block {} so the Read statement if we setup like this Read &A() expect to find a string which have a block of code {}.
 
A reference can't get as second reference. But we can use temporary block using For This { } so anything we make there just deleted after exit from block, so the next time we enter the block we may define a new reference (because the name which take the reference is new). Some times we use a special call, the "call local" which place the current scope to a module or function (which its called as module), so there we use Read New to make any local name as a new name, including by reference passing values (to shadow same names from same scope module). This used for event service functions.
 
This show how we can make a function reference (just a copy of code), and a second function which use the same scope. We can use lazy$() which make this automatic for a function k() as lazy$(&k()) which put the module and the module name to code of k(). Here we see only the way we do this simply by using strings. Module$ return the internal compact name of module. Use module.name$ to print the expanded named.
 
<syntaxhighlight lang="m2000 interpreter">
module kappa {
push "{read x, y:=x**y}"
read &a()
print a(2, 3)=8
long m
push "{module "+module$+" : m++}"
read &b()
call b()
print m=1
}
call kappa
</syntaxhighlight>
 
Array items is a special case. Because arrays are objects which hold the actual array, we can pass an array item by reference and interpreter use a copy in/copy out method, so we actually get the value of array item in a local value (the parameter) and at the exit from call, interpreter feed the array back (if the array item exist, because we can pass the array and deallocate items, reducing the length of array). We can't pass by reference array items to subs and simple functions (these two structures are light versions, which didn't have logic to do something for this).
 
<syntaxhighlight lang="m2000 interpreter">
module checkArr (m){
dim arr(10) as long=10
module kappa (&x as long, &a(), m) {
dim a(m)
x++
}
kappa &arr(3), &arr(), m
try ok {
print arr(3)=11
}
if not ok then print "nothing to show"
}
checkArr 2 ' nothing to show
checkArr 5
Module checkArr (m){
function kappa(&x as long, &a(), m) {
dim a(m)
x++
}
dim arr(10) as long=10
kk=kappa(&arr(3), &arr(), m) // this call use new stack of values.
// call kappa(&arr(3), &arr(), m) // you can use call kappa() also, passing the current stack of values, like module's call.
try ok {
print arr(3)=11
}
if not ok then print "nothing to show"
}
checkArr 2
checkArr 5
</syntaxhighlight>
 
===Example [[Z80 Assembly]]===
44

edits