# Stack arguments and a stack frame

[Courses](https://asm-editor.specy.app/learn/courses.md) › [Z80 assembly](https://asm-editor.specy.app/learn/courses/z80.md) › Examples

Web page: https://asm-editor.specy.app/learn/courses/z80/examples/subroutine-with-stack-arguments

> Pass two arguments on the stack, use ix to reach them, and keep a local value across another call.

The same program in: [M68K](https://asm-editor.specy.app/learn/courses/m68k/examples/subroutine-with-stack-arguments.md), [MIPS](https://asm-editor.specy.app/learn/courses/mips/examples/subroutine-with-stack-arguments.md), [RISC-V](https://asm-editor.specy.app/learn/courses/risc-v/examples/subroutine-with-stack-arguments.md), [x86-64](https://asm-editor.specy.app/learn/courses/x86/examples/subroutine-with-stack-arguments.md)

`sum_of_squares(x, y)` takes its two arguments on the stack, calls a second subroutine twice to
square them, and returns their sum in `hl`. It needs a local variable to hold the first square while
the second call runs, and that local lives on the stack too, in a frame built for it by hand.

In the previous example, the caller passed arguments in registers. Here the caller pushes two
arguments, and `sum_of_squares` makes room on the same stack for a value it must keep across a
second call. Each routine says which registers it changes; in this program, `square` preserves
`de` but changes `a` and `b`.

Open the program in the editor and choose **Build**. Use **Step** to watch the frame form, or
**Run** to see the answer. The register and memory panels show hexadecimal values; the inputs
`3` and `4` in the program are decimal.

```z80
    .org 0x8000
    ld hl, 4
    push hl             ; the second argument, y
    ld hl, 3
    push hl             ; the first argument, x
    call sum_of_squares
    pop bc              ; the caller takes the two arguments back
    pop bc
    jp done

; sum_of_squares(x, y): inputs 0..255, each pushed as a word with H=0;
; x at (ix+4), y at (ix+6), local at (ix-2), result in hl
sum_of_squares:
    push ix             ; the caller's ix, kept
    ld ix, 0
    add ix, sp          ; ix = sp, and now it stops moving
    dec sp
    dec sp              ; two bytes of local room, below the frame
    ld l, (ix+4)
    ld h, (ix+5)        ; x
    call square
    ld (ix-2), l
    ld (ix-1), h        ; local = x * x, kept across the next call
    ld l, (ix+6)
    ld h, (ix+7)        ; y
    call square
    ld e, (ix-2)
    ld d, (ix-1)
    add hl, de          ; x * x + y * y
    ld sp, ix           ; the local room given back
    pop ix
    ret

; square(v): v is 0..255 in hl (h=0); result in hl.
; de comes back as it was found; a and b do not.
square:
    push de
    ld d, h
    ld e, l             ; de = v
    ld b, l             ; v times round
    ld hl, 0
    ld a, b
    or a
    jr z, square_done   ; nothing times nothing
loop:
    add hl, de
    djnz loop
square_done:
    pop de
    ret

done:
    halt
```

The five instructions after `sum_of_squares:` build its frame. The pointer changes in stages:

| After                         | `sp`   | `ix`   | What is on top of the stack |
| ----------------------------- | ------ | ------ | --------------------------- |
| Caller pushes `y` and `x`     | `FFFB` | —      | `x`                         |
| `call sum_of_squares`         | `FFF9` | —      | return address              |
| `push ix`; set `ix` from `sp` | `FFF7` | `FFF7` | saved `ix`                  |
| Both `dec sp` instructions    | `FFF5` | `FFF7` | two reserved bytes          |

In this playground, `sp` starts at `FFFF`.

`push ix` puts the caller's `ix` on the stack. The caller may have been using `ix` for something of
its own, and this subroutine is about to overwrite it, so it has to go back exactly as it was found.

`ld ix, 0` then `add ix, sp` copies `sp` into `ix`. It takes two instructions because there is no
`ld ix, sp`; zeroing `ix` and adding `sp` to it gets there. From this moment `ix` stops moving and
`sp` carries on, which is the whole point of the arrangement.

`dec sp` twice lowers the stack pointer by two bytes. Those bytes are reserved for this routine's
local, but they do not yet contain a useful value. The next `push` or `call` grows the stack below
the new `sp`, leaving the reserved bytes alone.

At the end, `ld sp, ix` throws the local room away by putting `sp` back where `ix` has been sitting
all along, and `pop ix` hands the caller's `ix` back.

After `ld (ix-1), h` stores the first square, and before `sum_of_squares` loads `y`, the frame is
this. 🟢 marks `sp`; `ix` still points at `FFF7`:

|  address | value        | reached as | what it is                                   |
| -------: | :----------- | ---------- | -------------------------------------------- |
| `0xFFF5` | 🟢 `09` `00` | `(ix-2)`   | the local, `x * x`                           |
| `0xFFF7` | `00` `00`    | `(ix+0)`   | the caller's `ix`, and where `ix` now points |
| `0xFFF9` | `0B` `80`    | `(ix+2)`   | the return address                           |
| `0xFFFB` | `03` `00`    | `(ix+4)`   | `x`                                          |
| `0xFFFD` | `04` `00`    | `(ix+6)`   | `y`                                          |

The arguments are above `ix` because the caller pushed them before the `call`. The local is below
`ix`, in the room reserved by the two `dec sp` instructions. To inspect this moment, **Step** past
`ld (ix-1), h` and enter `FFF5` in the memory panel. After **Run**, the same bytes remain in memory,
although `sp` has returned to `FFFF`: changing `sp` does not erase them.

A word takes two instructions to move through `(ix+dd)`, because the displaced mode reads and writes
one byte: `ld l, (ix+4)` and `ld h, (ix+5)` are one 16 bit argument arriving, low byte first. The
displacement is a constant written into the instruction, from -128 to 127. A byte farther away
needs its address worked out separately.

`square` keeps to a smaller agreement of its own: it uses `de` and puts it back. It changes `a`
and `b`, and uses `l` as its loop count, so its input must be from 0 to 255 with `h = 0`. The Z80
has no multiply instruction; repeated addition makes each square. With the shown inputs,
`hl` ends at `0019`, which is 25 in decimal: 9 plus 16. For larger inputs, the final sum can exceed
what 16 bits hold; `hl` then contains only the low 16 bits.

Try changing `x` from `3` to `5`, keeping `y` at `4`. Build and run again. What should `hl` show?
The squares are 25 and 16, so the sum is 41 in decimal, or `0029` in the hexadecimal register
panel. You can also **Step** to the store at `(ix-2)` and check that the local contains `19 00`:
the first square, low byte first.

## Instructions in this Lecture

- Z80: [`ld`](https://asm-editor.specy.app/documentation/z80/instruction/ld.md), [`push`](https://asm-editor.specy.app/documentation/z80/instruction/push.md), [`call`](https://asm-editor.specy.app/documentation/z80/instruction/call.md), [`pop`](https://asm-editor.specy.app/documentation/z80/instruction/pop.md), [`jp`](https://asm-editor.specy.app/documentation/z80/instruction/jp.md), [`add`](https://asm-editor.specy.app/documentation/z80/instruction/add.md), [`dec`](https://asm-editor.specy.app/documentation/z80/instruction/dec.md), [`ret`](https://asm-editor.specy.app/documentation/z80/instruction/ret.md), [`or`](https://asm-editor.specy.app/documentation/z80/instruction/or.md), [`jr`](https://asm-editor.specy.app/documentation/z80/instruction/jr.md), [`djnz`](https://asm-editor.specy.app/documentation/z80/instruction/djnz.md), [`halt`](https://asm-editor.specy.app/documentation/z80/instruction/halt.md)

Previous: [A subroutine with its arguments in registers](https://asm-editor.specy.app/learn/courses/z80/examples/subroutine-with-register-arguments.md)

Next: [Recursion: factorial and Fibonacci](https://asm-editor.specy.app/learn/courses/z80/examples/factorial-and-fibonacci.md)
