x86 MASM Chapter 9
Chapter 9: Strings and Arrays
9.1 Introduction
Understanding efficient string and array processing is crucial for code optimization.
Studies indicate that most programs spend 90% of their execution time in 10% of their code.
This critical 10% is often marked by loops that handle strings and arrays.
Techniques for efficient string and array processing will be covered.
Starting with optimized string primitive instructions for data manipulation (moving, comparing, loading, and storing).
Introduction to several string-handling procedures in Irvine32 and Irvine64 libraries, similar to the standard C string library.
The manipulation of two-dimensional arrays will be discussed, including advanced addressing modes: base-index and base-index-displacement.
Section 9.5 will focus on searching and sorting integer arrays, emphasizing practical algorithms: bubble sort and binary search.
Encouragement to study these algorithms in Java or C++ as well as assembly language.
9.2 String Primitive Instructions
The x86 instruction set includes five groups of instructions for processing arrays.
Although termed string primitives, they apply to more than just character arrays.
In 32-bit mode, instructions use ESI and EDI registers for addressing memory, while the accumulator uses AL, AX, or EAX based on data size.
String primitives execute operations efficiently by automatically handling array indexes.
9.2.1 MOVSB, MOVSW, and MOVSD
MOVSB: Move string bytes from memory at ESI to memory at EDI.
MOVSW: Move string words.
MOVSD: Move string doublewords.
The increment/decrement behavior of ESI and EDI depends on the Direction flag (DF).
The size of the increment or decrement based on the instruction:
MOVSB: 1 byte
MOVSW: 2 bytes
MOVSD: 4 bytes
Example for Copying Doubleword Array:
.data
source DWORD 20 DUP (FFFFFFFFH)
target DWORD 20 DUP(?)
.code
cld
mov ecx, LENGTHOF source
mov esi, OFFSET source
mov edi, OFFSET target
rep movsd
9.2.2 CMPSB, CMPSW, and CMPSD
CMPSB: Compare string bytes.
CMPSW: Compare string words.
CMPSD: Compare string doublewords.
It is possible to use a repeat prefix with these instructions to compare multiple elements efficiently.
9.2.3 SCASB, SCASW, and SCASD
SCASB: Compare AL to a string byte (memory at EDI).
SCASW: Compare AX to a string word.
SCASD: Compare EAX to a string doubleword.
Used for searching single values in strings or arrays.
9.2.4 STOSB, STOSW, and STOSD
STOSB: Store contents of AL into the memory location pointed by EDI.
STOSW: Store AX; STOSD: store EAX.
When used with the REP prefix, useful for filling a string or array with a single value.
9.2.5 LODSB, LODSW, and LODSD
LODSB: Load byte from memory at ESI into AL.
LODSW: Load word into AX; LODSD: load doubleword into EAX.
Each increment/decrement based on Direction flag.
9.2.6 Section Review
This section includes questions regarding string primitives and their behavior.
Register that acts as the accumulator in reference to string primitives is EAX.
9.3 Selected String Procedures
Demonstrating several procedures from the Irvine32 library for manipulating null-terminated strings similar to standard C library functions.
9.3.1 Str_compare Procedure
Compares two strings and affects the Zero and Carry flags accordingly.
Call format:
INVOKE Str_compare, ADDR string1, ADDR string2.
9.3.2 Str_length Procedure
Returns the length of a string (excluding the null byte) in EAX.
Call format:
INVOKE Str_length, ADDR myString.
9.3.3 Str_copy Procedure
Copies a null-terminated string from one location to another.
Ensures the target string has enough space.
Call format:
INVOKE Str_copy, ADDR source, ADDR target.
9.3.4 Str_trim Procedure
Removes trailing occurrences of a specified character from a string.
Call format:
INVOKE Str_trim, ADDR string, char_to_trim.Handles multiple cases of string content during trimming.
9.3.5 Str_ucase Procedure
Converts a string to uppercase. Call format:
INVOKE Str_ucase, ADDR myString.
9.3.6 String Library Demo Program
Example 32-bit program illustrating the use of the above procedures in Irvine32 library.
9.3.7 String Procedures in the Irvine64 Library
Discusses translation of string-handling procedures to 64-bit mode.
Changes include stack parameters and register adjustments (32-bit -> 64-bit).
9.4 Two-Dimensional Arrays
9.4.1 Ordering of Rows and Columns
Two-dimensional arrays can be stored in row-major order or column-major order.
Row-major order places data of the first row consecutively in memory.
9.4.2 Base-Index Operands
Combines a base register and an index register to produce an address.
Example usage provided for clarity regarding accessing array elements.
9.4.3 Base-Index-Displacement Operands
Combines an additional displacement with the previous operands to effectively address two-dimensional arrays.
9.4.4 Base-Index Operands in 64-Bit Mode
Register usage changes from 32-bit to 64-bit mode must be carefully managed in assembly.
9.5 Searching and Sorting Integer Arrays
9.5.1 Bubble Sort
Simple sorting algorithm that compares adjacent elements.
Describes the operation through pseudocode transitioning to assembly.
9.5.2 Binary Search
Efficient search algorithm for sorted arrays leveraging divide-and-conquer.
Described with both C++ and assembly language implementations.
9.6 Java Bytecodes: String Processing (Optional Topic)
Illustrates how Java handles strings through bytecode analysis.
9.7 Chapter Summary
Recap of string primitive instructions and their optimizations.
Summary of string and array manipulations and the performance benefits from assembly language.
9.8 Key Terms and Instructions
base-index operands
base-index-displacement operands
CMPSB, CMPSW, CMPSD
column-major order
Direction flag
LODSB, LODSW, LODSD
MOVSB, MOVSW, MOVSD
REP, REPE, REPNE, REPNZ, REPZ
9.1 Introduction
Importance of Optimization: Studies indicate that most programs spend of their execution time in of their code. This critical section often involves loops processing large datasets, strings, and arrays.
Efficiency: Assembly language is preferred for these tasks because string primitive instructions are optimized at the hardware level, executing faster than manual loops in high-level languages.
Key Areas of Focus:
String primitive instructions for data movement, comparison, loading, and storing.
Irvine32 and Irvine64 library procedures for higher-level string manipulation.
Multi-dimensional array addressing via advanced modes: base-index and base-index-displacement.
Algorithmic efficiency through implementations of Bubble Sort and Binary Search.
9.2 String Primitive Instructions
General Mechanism: String primitives use specific registers to facilitate automatic iteration. In 32-bit mode:
(Source Index): Points to the source memory location.
(Destination Index): Points to the destination memory location.
(): Acts as the accumulator for loading or comparing data.
: Acts as a counter for repeat prefixes.
Direction Flag (): Determines the direction of processing.
CLD(Clear Direction Flag): Sets , causing indices to increment (forward processing).STD(Set Direction Flag): Sets , causing indices to decrement (backward processing).
9.2.1 MOVSB, MOVSW, and MOVSD
Function: Copies data from the memory location pointed to by to the location pointed to by .
Sizes:
MOVSB: Copies 1 byte ( and increment/decrement by 1).MOVSW: Copies 2 bytes (increment/decrement by 2).MOVSD: Copies 4 bytes (increment/decrement by 4).
Repeat Prefix (
REP): When prepended, the instruction repeats times, decrementing after each operation until it reaches 0.
9.2.2 CMPSB, CMPSW, and CMPSD
Function: Compares the value at
[ESI]with the value at[EDI]by performing an implicit subtraction. It updates the status flags (Zero, Sign, Overflow, etc.) but does not change the operands.Repeat Prefixes:
REPE/REPZ: Repeat while the elements are equal () and .REPNE/REPNZ: Repeat while the elements are not equal () and .
9.2.3 SCASB, SCASW, and SCASD
Function: Scans a string or array by comparing the value in the accumulator () to the value in memory pointed to by .
Usage: Highly effective for finding a specific character or value within an array when combined with
REPEorREPNE.
9.2.4 STOSB, STOSW, and STOSD
Function: Copies the value in the accumulator to the memory pointed to by .
Application: Frequently used to initialize an entire array or string with a specific constant value (e.g., zeroing out a buffer).
9.2.5 LODSB, LODSW, and LODSD
Function: Loads a value from memory at into the accumulator.
Note: This instruction is rarely used with the
REPprefix because it would overwrite the accumulator on every iteration, leaving only the last value. It is typically used inside a manual loop where further processing is needed on each element.
9.3 Selected String Procedures
Str_compare: Performs a lexicographical comparison of two null-terminated strings. It sets the Zero flag if the strings are identical.
Str_length: Scans a string until the null terminator () is found and returns the count in .
Str_copy: Copies source to target; the programmer must ensure the target buffer size is at least
Str_length(source) + 1to prevent buffer overflows.Str_trim: Removes a trailing character by searching from the end of the string backwards and inserting a null terminator at the first non-matching character position.
64-bit Adjustments: In 64-bit mode, pointers must be 64 bits (), and parameters are passed via registers () instead of the stack.
9.4 Two-Dimensional Arrays
9.4.1 Ordering and Addressing
Row-Major Order: Consecutive rows are stored in memory. The address of an element at is calculated as:
.
Base-Index Operands: Uses two registers, such as
[ebx + esi], where one register usually holds the row offset and the other holds the column index.Base-Index-Displacement: Formatted as
[base + index + displacement]. This is ideal for arrays of structures, where 'displacement' is the offset of a specific field within the structure.
9.5 Searching and Sorting Integer Arrays
9.5.1 Bubble Sort
Logic: Iterates through an array, comparing adjacent elements and swapping them if they are in the wrong order. This process is repeated until no swaps occur.
Complexity: , making it inefficient for large datasets but simple for small assembly tasks.
9.5.2 Binary Search
Pre-requisite: The array must be sorted beforehand.
Logic: Compares the target value to the middle element. If not equal, the half in which the target cannot lie is eliminated, and the search continues on the remaining half.
Performance: , significantly faster than linear search for large arrays.
9.7 Chapter Summary
String primitives () provide hardware-accelerated array processing.
Repeat prefixes () control the execution of primitives based on and the Zero flag.
Two-dimensional arrays are managed using complex addressing modes that combine base registers, index registers, and constant displacements.
9.1 Introduction
The 90/10 Rule of Optimization: Optimization efforts are most effective when directed at the critical of code where programs spend of their execution time. These bottlenecks are typically loops that traverse large strings or arrays.
Hardware-Level Efficiency: x86 string primitives are highly optimized at the microcode level. Unlike manual loops that fetch, decode, and execute multiple instructions per iteration (e.g.,
MOV,INC,LOOP), string primitives perform the operation and the pointer adjustment in a single, highly pipelined execution cycle.Scope of Primitives: Despite the name "string," these instructions are versatile and process any contiguous data blocks, including integer arrays, floating-point buffers, or custom structures.
9.2 String Primitive Instructions
Register Roles in 32-bit Mode:
(Source Index): Holds the memory address of the input data. By convention, it points to the source.
(Destination Index): Holds the memory address of the output or target data.
: Serves as the loop counter when used with repeat prefixes (, , etc.).
Accumulator (): Stores the operand for instructions like , , and .
The Direction Flag ():
If (via
CLD), the addresses in and are incremented after each operation (Forward processing).If (via
STD), the addresses are decremented (Backward processing).Safety Note: It is vital to clear the direction flag after completing backward operations to prevent unexpected behavior in subsequent code.
9.2.1-9.2.5 Detailed Primitive Breakdown
:
Moves data from
[ESI]to[EDI].Often used with the
REPprefix.REPcontinues as long as . Each iteration decrements and updates .
:
Logic:
[ESI] - [EDI].Updates the flags () based on the result.
Used with
REPE(Repeat while Equal) for checking if two strings are identical, orREPNE(Repeat while Not Equal) to find the first differing element.
:
Logic:
accumulator - [EDI].Scans memory for a match to the value in the accumulator.
REPNE SCASBis the standard assembly idiom forstrchr(finding a character) or calculating string length (searching for a null terminator).
:
Logic:
[EDI] = accumulator.Combined with
REP, this is the most efficient way to implementmemset()(e.g., clearing a buffer to zero).
:
Logic:
accumulator = [ESI].Unlike others, it is rarely paired with
REPbecause each iteration would simply overwrite the previous value in the accumulator. It is usually used inside a loop to fetch a value for custom processing.
9.3 Selected String Procedures
Implementation: It compares strings lexicographically. If String 1 < String 2, the Carry flag is set. If String 1 = String 2, the Zero flag is set. It handles the null terminator internally.
Mechanics: It often uses
SCASBwith a target value of in . It finds the offset of the null terminator and subtracts the starting address to find the count.Strategy:
Calculate string length.
Point to the end of the string (just before the null terminator).
Move backwards as long as the current character matches the "chartotrim".
Place a new null terminator () at the first non-matching position found.
64-bit Procedure Variations:
Addresses are 64-bit ().
The calling convention changes: instead of pushing arguments to the stack, the first four arguments are passed in .
9.4 Two-Dimensional Arrays
Row-Major Storage: Standard in most languages (C, C++, Java). The row is the primary index. For an array with columns, the offset of element is:
Column-Major Storage: Used in languages like Fortran. The column is the primary index. The offset is calculated as:
(where is the number of rows).
Addressing Modes:
Base-Index:
[EBX + ESI].EBXmight hold the base address of a specific row, andESIthe offset of the column within that row.Base-Index-Displacement:
[EBX + ESI + displacement]. Often used for an array of structures, where the displacement targets a specific field within the struct.
9.5 Searching and Sorting
Bubble Sort Process:
Uses nested loops. The outer loop runs times. The inner loop compares adjacent elements and .
If , they are swapped.
After the first pass, the largest element is "bubbled" to the last position. The effective size of the array for the next pass then decreases by 1.
Binary Search Algorithm:
Requires a sorted array.
Variables:
Low,High,Mid.While
Low <= High:
If
Target == Array[Mid], return index.If
Target < Array[Mid], setHigh = Mid - 1.If
Target > Array[Mid], setLow = Mid + 1.
This logarithmic approach () is vastly superior to linear search () for large datasets.
9.7 Performance Summary
Using
REPwith string primitives eliminates the overhead of the instruction fetch cycle for the loop body during repetition.Assembly allows for precise control over the Direction Flag and the specific increment size ( bytes), allowing for fine-tuned data alignment which is crucial for maximizing cache performance.