Flow-control structures: Difference between revisions

Line 3,021:
 
=={{header|Z80 Assembly}}==
===JP and JR===
<lang z80>JP &XXXX ; jump to an absolute address
This is the equivalent of <code>GOTO</code> in BASIC, in that after the jump takes place, the CPU has no way of knowing where it came from.
JP z,&XXXX ; conditionally jump to an absolute address. If the condition is not met, no jump shall occur and execution continues to the
* <code>JP</code> takes three bytes, one for the jump itself, and two for the address. This instruction can jump conditionally based on the status of the zero, carry, sign, and overflow/parity flags. (On Game Boy, there are no sign or overflow/parity flags so those jumps won't work even for <code>JP</code>)
; next instruction.
 
* <code>JR</code> is a "short jump" that is program-counter relative. This instruction takes two bytes - one for the jump, and the second is an 8-bit signed offset. The offset represents how many bytes forward or backward to jump - <code>JR</code> can only travel forward 127 bytes and backward 128 bytes, and cannot branch based on the sign or overflow/parity flags - only zero and carry.
JP (HL) ; copy the value in HL to the program counter
JR &XX ; jump XX bytes forward or backward (this is a signed offset)
JR z,&XX ; conditionally jump forward or backward (JR cannot use parity, overflow, or sign flags to jump. JP, CALL, and RET can.)
PUSH rr RET ; fake a return address and "return" to that address
CALL label ; call a subroutine named "label"
CALL z,label ; call a subroutine named "label" conditionally
RET ; return from subroutine
RET z ; return from subroutine conditionally
DJNZ label ; decrement, jump if B nonzero to label
LDIR ; load, increment, repeat until BC = 0
LDDR ; load, decrement, repeat until BC = 0
CPIR ; compare, increment, repeat until BC = 0
CPDR ; compare, decrement, repeat until BC = 0
OTIR ; out, increment, repeat until BC = 0
OTDR ; out, decrement, repeat until BC = 0
INIR ; in, increment, repeat until BC = 0
INDR ; in, decrement, repeat until BC = 0
RST # ; call a subroutine in low memory. This only takes 1 byte to encode (a CALL takes three.)
HALT ; the program counter won't move past this instruction until an interrupt occurs. (See below).</lang>
 
===Breaking out of a loop===
In addition, there are multiple hardware interrupt modes. These are not directly controlled by the programmer, but the programmer needs to be aware of them so that they do not destroy the flow control of the program. At a minimum they should push all registers they use as soon as possible and pop them before returning (or alternatively use the exchange commands <code>EX AF,AF'</code> and <code>EXX</code> at the beginning and end.) What the interrupts do, when they occur, and which interrupt modes are used, will vary depending on the hardware.
A subroutine that loops is often escaped with a conditional return, or, if it needs to unwind the stack frame, a conditional jump to an unconditional return.
</lang z80>PrintString:
ld a,(hl) ;HL is our pointer to the string we want to print
cp 0 ;it's better to use OR A to compare A to zero, but for demonstration purposes this is easier to read.
ret z ;return if accumulator = zero
call PrintChar ;prints accumulator's ascii code to screen - on Amstrad CPC for example this label points to memory address &BB5A
inc hl ; next instruction.char
jr PrintString ;jump back to the start of the loop. RET Z is our only exit.</lang>
 
In the above example, the stack was never modified (besides the CALL pushing the return address) so <code>RET Z</code> was safe to use. Conditional returns are not safe to use if the stack needs to be unwound prior to exiting, since there's no way to conditionally unwind the stack without conditionally jumping to a section of code that does just that. In which case you don't need the return to be conditional anyway. This contrived example shows this in action.
 
<lang z80>foo:
* <code>NMI</code> executes the instruction <code>CALL &0066</code> and is returned from with <code>RETN</code>. Unlike other interrupt modes, the <code>DI</code> instruction will not prevent it.
push af
* Interrupt mode 0 is selected with <code>IM 0</code>. The external hardware that generates the interrupt will do so by feeding the CPU the <code>CALL</code> instruction to the interrupt handler's memory location. Check the hardware documentation so that you know where to assemble the code for your interrupt handler. This interrupt, and the other two below, should be returned from with <code>RETI</code>.
bar:
* Interrupt mode 1 is selected with <code>IM 1</code>. The external hardware that generates the interrupt will do so by feeding the CPU the instruction <code>RST 38</code>. This means that the interrupt handler (or a jump to it) will need to be located at memory address 0x0038.
ld a,(hl)
* Interrupt mode 2 is selected with <code>IM 2</code>. This one is a bit weird. It uses the 8-bit value in the <code>I</code> register as well as am 8-bit value provided by whatever hardware is generating the interrupt, and will concatenate them into a memory address which will then be <code>CALL</code>ed. Generally, this interrupt mode is best handled by filling 257 bytes of memory with the same value, so that all interrupt addresses generated by this method are the same. Whatever address that will be is where the interrupt handler will go.
cp 255
jr z,exit
inc hl
jr bar
 
exit:
pop af
ret</lang>
 
===DJNZ===
(Side note: The Game Boy uses none of the above, its interrupts are handled differently. The only thing the Game Boy has in common with the Zilog Z80 is that interrupts will not occur if they were disabled with <code>DI</code>. The Game Boy has no NMI.)
<code>DJNZ</code> stands for "decrement, jump if nonzero." This is the equivalent of x86's <code>LOOP</code> command - as it subtracts 1 from the B register (just B, not BC) and if it's nonzero, jumps to a specified signed 8-bit offset. (It's better to use a label and let the assembler compute the offset for you.) Although this is usually used for looping, it can also jump forward. The same distance limits of <code>JR</code> apply to <code>DJNZ</code> as well. <code>DJNZ</code> cannot be made conditional based on flags, and it doesn't actually change the flags the same way <code>DEC B</code> would.
 
(Game Boy doesn't have this instruction - you'll have to use a combination of <code>DEC B</code> and <code>JR NZ</code>)
 
<lang z80>loop:
;your code goes here
DJNZ loop</lang>
 
===Block Instructions===
These instructions repeat until <code>BC</code> equals zero. They're useful for doing the same thing in a row, but have one-off equivalents that are faster. However, you can often save space by using these, especially if you can't hardcode a fixed repetition count before using one.
 
<code>LDIR</code> is the equivalent of <code>memcpy()</code> in C and <code>rep movsb</code> in x86. It loads from the address stored in HL and stores in the address pointed to by DE, increments both HL and DE, decrements BC, and repeats if BC doesn't equal zero.
 
Essentially it's equivalent to the code below, except in the real <code>LDIR</code> the accumulator isn't actually altered in any way.
<lang z80>_LDIR:
ld a,(hl)
ld (de),a
inc hl
inc de
dec bc
ld a,b
or c ;compare BC to zero
jr nz,_LDIR ;Game Boy doesn't have LDIR so you'll have to use this code instead.</lang>
 
There are several others that work in a similar fashion, such as:
* <code>LDDR</code> - equivalent of x86's <code>REP MOVSB</code> with direction flag set
* <code>CPIR</code> - equivalent of x86's <code>REPNZ SCASB</code> with direction flag clear
* <code>CPDR</code> - equivalent of x86's <code>REPNZ SCASB</code> with direction flag set
* <code>OTIR</code> - equivalent of x86's <code>REP OUTSB</code> with direction flag clear
* <code>OTDR</code> - equivalent of x86's <code>REP OUTSB</code> with direction flag set
* <code>INIR</code> - equivalent of x86's <code>REP INSB</code> with direction flag clear
* <code>INDR</code> - equivalent of x86's <code>REP INSB</code> with direction flag set
 
=={{header|zkl}}==
1,489

edits