show_string_II

In this post, you read about the reason why I decided to change the code of show_string and make a second version for it.

show_string_II kept the same core as it was in show_string but I cast it again in the new format and, very important now, I preserved the register used by the procedure itself by pushing them on the stack at the beginning of the procedure and popping them off the stack again at the end. This is a change that I realized I had to bring in my code since I had a couple of unexpected bugs because in my previous coding style the procedures weren't preserving the registers. Previously, I was writing which registers were used during the procedure, meaning that I couldn't rely on the state of such registers when the procedure returned. In simple context it was working, but as complexity rose and procedures were calling other procedures this written notification wasn't working anymore. This was not safe and I made some mistakes. So I changed strategy and I decided to preserve the registers used so that every procedure had to return in the same state as it was before the call and this kept the calling chain clean. It is disadvantageous to push and pop values on the stack because it consumes bytes of opcodes, and in the past, I used to struggle with every byte to keep my toy bootloader the "Space Shuttle" fit in the first boot block. At that time, with a program which was relatively short and had to stay all in 456 Bytes (512 byte of block minus the Bytes of the Bios Parameter Block minus 2 Bytes of boot signature), it was ok to keep the trace on a piece of paper of the values assumed by registers at each step and fit all pieces of the puzzle together. This technique is impossible to be used with code that grows in a modular way. When I wrote show_string_II, I was still not sure and wondering myself whether it was good to push and pop only the registers used or it was better to push and pop all registers in a sort of standardized way of beginning and ending a procedure. I liked the idea of a standardized opening and closing for the procedure that I could reuse everywhere, however, the useless pushing and popping of all other registers that didn't change seamed to be inefficient to me, especially when I thought of a calling chain from procedure to procedure each of them pushing all register to stack. I would eventually end with a huge stack queue with a lot of copies of registers. When I wrote show_string_II, I was still experimenting which way to follow and I had no strong experience that could have helped me decide in which direction to go.

Besides preserving the used registers by pushing them on the stack, I observed that the show_string procedure ended as soon as the Byte 0x00 was found and that it printed nothing on the screen if the very first Byte was 0x00. Based on this consideration I decided to check immediately whether or not the first Byte of the string pointed with DS:[SI] was 0x00, because I could avoid the execution of the procedure if it was like that. You can see here the procedure show_string_II and compare this one with the previous version of show_string. You find both procedures in the DOWNLOAD AREA.

show_string_II.npp
[...] ;234567890123456789012345678901234567890123456789012345678901234567890123456789 ;-------10--------20--------30--------40--------50--------60--------70-------79 ;############################################################################## ; SHOW_STR: SHOW STRing II version. ; ; Copyright (C) 2020 - Michele Musci ; Distributed under the GNU Affero General Public License version 3. ; See https://www.gnu.org/licenses/agpl-3.0.txt ; ; This procedure shows a string on the video starting at address DS:[SI] ; and terminating as soon as the function finds the byte 0x00. ; This procedure preserves the registers used. ; ; INPUT: DS:[SI] ; OUTPUT: --- ; REGISTER USAGE: AX, BX, SI ; THIS ONE CALLS: INT 10 ; ; Build it with command: ; debug < show_str.npp > show_str_dbg.npp ;############################################################################## ; ;------------------------------------------------------------------------------ ; Check the first byte at beginning of the string. ; ; In 99% of cases, the first byte at beginning of the string is different ; from 0x00, but if it was equal to 0x00, then I can avoid to push registers ; on stack and pop them back. When the procedure finds the byte 0x00 ; it terminates execution. In this case, I can avoid the beginning ; of execution at all. ;------------------------------------------------------------------------------ cmp byte ptr [SI], 0 ; flags = DS:[SI] - 0x00 jne 7c16 ; JumpNotEqual isOK_A: ---> ; ; ret ; I return immediatelly to the caller if the ; there is nothing to print on screen. ; ; ; isOK_A: <--- ; ;------------------------------------------------------------------------------ ; Save all used registers on the stack. ;------------------------------------------------------------------------------ push ax ; push bx ; push si ; ; ;------------------------------------------------------------------------------ ; standard setup for displaying characters on the screen ;------------------------------------------------------------------------------ mov ah, 0e ; int10/ah = 0e : video teletype out ; al = character to write mov bx, 7 ; bh = page number ; bl = foreground color (graphics mode only) ; int10 returns nothing ; cld ; Clear Direction Flag to set string operation ; with auto increment. ;------------------------------------------------------------------------------ ; displays characters on the screen until the byte 00 is found ;------------------------------------------------------------------------------ ; ;do_until: <--- ; lodsb ; al = DS:[SI]; SI = SI + 1 cmp al, 00 ; flags = al - 00 je 7c28 ; JumpEqual end: ---> ; jump if ZeroFlag is set (ZF=1) ; int 10 ; put a char on the screen and returns nothing. ; jmp 7c1f ; Jump do_until: ---> ; ;############################################################################## ; END of Procedure MAIN. ;############################################################################## ; ; end: <--- ; pop si ; Restore the registers used before return. pop bx ; pop ax ; ret ; [...]

Comments