Quick Sort in x86 assembly (MASM)

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
6
down vote

favorite












My task was to implement Quick Sort in assembly from pseudocode.



;*******************************************************************************
; This program implements the Quick Sort algorithm and sorts an array
;
; Author: William
; Date: 4/6/18

TITLE Quick Sort
.386
.model flat, stdcall
.stack 4096

; prototype from Kernel32 lib
ExitProcess PROTO, dwExitCode:DWORD

.data

; An array of 25 random integers to be sorted
array DWORD 43, 91, 97, 63, 52,
58, 99, 19, 33, 26,
77, 11, 41, 89, 27,
99, 98, 68, 26, 5,
73, 47, 46, 59, 21

.code

;-------------------------------------------------------------------------------
; _partition@12 (A , p, r)
;
; Quicksort subroutine
;
; Entry: A @ ebp + 8 ; pointer to array being sorted
; p @ ebp + 12 ; integer index to start of sub-array
; r @ ebp + 16 ; integer index to end of sub-array
;
; Local: x ; The pivot
; i ; index of smaller element
;
; Exit: the index of the pivot stored in eax
;-------------------------------------------------------------------------------
_partition@12:
; set up stack, save regs
push ebp
mov ebp, esp
push ebx
push ecx
push edx
push esi
push edi

; Load function parameters into registers and initialize local variables
mov ebx, [ebp + 8] ; A stored in ebx
mov ecx, [ebp + 12] ; p stored in ecx
mov edx, [ebp + 16] ; r stored in edx
mov esi, [ebx + edx * 4] ; x initialized to A[r]
lea edi, [ecx - 1] ; i initialized to p - 1

; Loop from index p to r - 1
mov eax, ecx ; j (loop count) initialized to p
L2: cmp eax, edx
jnl endL2

; If A[j] <= x
cmp [ebx + eax * 4], esi
jnle endIf1
inc edi ; i = i + 1

; swap A[i] with A[j]
mov ecx, [ebx + edi * 4]
xchg ecx, [ebx + eax * 4]
mov [ebx + edi * 4], ecx
endIf1:
inc eax
jmp L2
endL2:
; swap A[i + 1] with A[r]
inc edi ; i = i + 1
mov ecx, [ebx + edi * 4]
xchg ecx, [ebx + edx * 4]
mov [ebx + edi * 4], ecx

mov eax, edi ; return i + 1

; clean up stack, restore regs
pop edi
pop esi
pop edx
pop ecx
pop ebx
pop ebp
ret 12

;-------------------------------------------------------------------------------
; _quickSort@12 (A , p, r)
;
; Sorts array of DWORD integers
;
; Entry: A @ ebp + 8 ; pointer to array to being sorted
; p @ ebp + 12 ; integer index to start of sub-array
; r @ ebp + 16 ; integer index to end of sub-array
;
;-------------------------------------------------------------------------------
_quickSort@12:
; set up stack, save regs
push ebp
mov ebp, esp
push ebx
push ecx
push edx

; Load function parameters into registers
mov ebx, [ebp + 8] ; A stored in ebx
mov ecx, [ebp + 12] ; p stored in ecx
mov edx, [ebp + 16] ; r stored in edx

; if (p < r)
cmp ecx, edx
jnl endIf2

;q = Partition(A, p, r)
push edx
push ecx
push ebx
call _partition@12 ; q stored in eax

;Quicksort(A, p, q - 1)
dec eax
push eax
push ecx
push ebx
call _quickSort@12

;Quicksort(A, q + 1, r)
add eax, 2
push edx
push eax
push ebx
call _quickSort@12
endIf2:
; clean up stack, restore regs
pop edx
pop ecx
pop ebx
pop ebp
ret 12

main:
push 24
push 0
push OFFSET array
call _quickSort@12

; array is sorted, but code does not
; print it to the console

INVOKE ExitProcess, 0
END main


This was the pseudocode I used:



Partition(A, p, r)
x = A[r]
i = p - 1
for j = p to r - 1
if A[j] <= x
i = i + 1
exchange A[i] with A[j]
exchange A[i + 1] with A[r]
return i + 1

Quicksort(A, p, r)
if p < r
q = Partition(A, p, r)
Quicksort(A, p, q - 1)
Quicksort(A, q + 1, r)


I took the advice given to me on my last post and loaded all function parameters into registers instead of reading and writing to memory over and over. All criticism is appreciated.







share|improve this question





















  • Is the array-to-be-sorted supposed to be hardcoded in or were you supposed to take user input?
    – Mast
    Apr 7 at 9:45











  • Let me give you a thought experiment: What would happen if you commented out the push edx, mov edx, [ebp + 16], and pop edx from _partition@12? Passing parameters on the stack is a common way to call functions, and there are benefits to using a known and understood parameter passing protocol. But it's not the only way, or even the fastest. For a pure asm program (such as this), passing (some of) the parameters in registers might be useful.
    – David Wohlferd
    Apr 7 at 12:59










  • @ David Wohlferd If i didn't use edx, I would have to read [ebp + 16] from memory for every iteration of the loop L2, which is supposed to be slow. The rational is that it's better to read it from memory just once into a register.
    – William
    Apr 7 at 14:53










  • @ Mast We are not given the initial array to be sorted, and I don't know how to generate an array of random integers in assembly, so I just hardcoded one. There is some I/O but I left that out because it was too much code to post here.
    – William
    Apr 7 at 14:58
















up vote
6
down vote

favorite












My task was to implement Quick Sort in assembly from pseudocode.



;*******************************************************************************
; This program implements the Quick Sort algorithm and sorts an array
;
; Author: William
; Date: 4/6/18

TITLE Quick Sort
.386
.model flat, stdcall
.stack 4096

; prototype from Kernel32 lib
ExitProcess PROTO, dwExitCode:DWORD

.data

; An array of 25 random integers to be sorted
array DWORD 43, 91, 97, 63, 52,
58, 99, 19, 33, 26,
77, 11, 41, 89, 27,
99, 98, 68, 26, 5,
73, 47, 46, 59, 21

.code

;-------------------------------------------------------------------------------
; _partition@12 (A , p, r)
;
; Quicksort subroutine
;
; Entry: A @ ebp + 8 ; pointer to array being sorted
; p @ ebp + 12 ; integer index to start of sub-array
; r @ ebp + 16 ; integer index to end of sub-array
;
; Local: x ; The pivot
; i ; index of smaller element
;
; Exit: the index of the pivot stored in eax
;-------------------------------------------------------------------------------
_partition@12:
; set up stack, save regs
push ebp
mov ebp, esp
push ebx
push ecx
push edx
push esi
push edi

; Load function parameters into registers and initialize local variables
mov ebx, [ebp + 8] ; A stored in ebx
mov ecx, [ebp + 12] ; p stored in ecx
mov edx, [ebp + 16] ; r stored in edx
mov esi, [ebx + edx * 4] ; x initialized to A[r]
lea edi, [ecx - 1] ; i initialized to p - 1

; Loop from index p to r - 1
mov eax, ecx ; j (loop count) initialized to p
L2: cmp eax, edx
jnl endL2

; If A[j] <= x
cmp [ebx + eax * 4], esi
jnle endIf1
inc edi ; i = i + 1

; swap A[i] with A[j]
mov ecx, [ebx + edi * 4]
xchg ecx, [ebx + eax * 4]
mov [ebx + edi * 4], ecx
endIf1:
inc eax
jmp L2
endL2:
; swap A[i + 1] with A[r]
inc edi ; i = i + 1
mov ecx, [ebx + edi * 4]
xchg ecx, [ebx + edx * 4]
mov [ebx + edi * 4], ecx

mov eax, edi ; return i + 1

; clean up stack, restore regs
pop edi
pop esi
pop edx
pop ecx
pop ebx
pop ebp
ret 12

;-------------------------------------------------------------------------------
; _quickSort@12 (A , p, r)
;
; Sorts array of DWORD integers
;
; Entry: A @ ebp + 8 ; pointer to array to being sorted
; p @ ebp + 12 ; integer index to start of sub-array
; r @ ebp + 16 ; integer index to end of sub-array
;
;-------------------------------------------------------------------------------
_quickSort@12:
; set up stack, save regs
push ebp
mov ebp, esp
push ebx
push ecx
push edx

; Load function parameters into registers
mov ebx, [ebp + 8] ; A stored in ebx
mov ecx, [ebp + 12] ; p stored in ecx
mov edx, [ebp + 16] ; r stored in edx

; if (p < r)
cmp ecx, edx
jnl endIf2

;q = Partition(A, p, r)
push edx
push ecx
push ebx
call _partition@12 ; q stored in eax

;Quicksort(A, p, q - 1)
dec eax
push eax
push ecx
push ebx
call _quickSort@12

;Quicksort(A, q + 1, r)
add eax, 2
push edx
push eax
push ebx
call _quickSort@12
endIf2:
; clean up stack, restore regs
pop edx
pop ecx
pop ebx
pop ebp
ret 12

main:
push 24
push 0
push OFFSET array
call _quickSort@12

; array is sorted, but code does not
; print it to the console

INVOKE ExitProcess, 0
END main


This was the pseudocode I used:



Partition(A, p, r)
x = A[r]
i = p - 1
for j = p to r - 1
if A[j] <= x
i = i + 1
exchange A[i] with A[j]
exchange A[i + 1] with A[r]
return i + 1

Quicksort(A, p, r)
if p < r
q = Partition(A, p, r)
Quicksort(A, p, q - 1)
Quicksort(A, q + 1, r)


I took the advice given to me on my last post and loaded all function parameters into registers instead of reading and writing to memory over and over. All criticism is appreciated.







share|improve this question





















  • Is the array-to-be-sorted supposed to be hardcoded in or were you supposed to take user input?
    – Mast
    Apr 7 at 9:45











  • Let me give you a thought experiment: What would happen if you commented out the push edx, mov edx, [ebp + 16], and pop edx from _partition@12? Passing parameters on the stack is a common way to call functions, and there are benefits to using a known and understood parameter passing protocol. But it's not the only way, or even the fastest. For a pure asm program (such as this), passing (some of) the parameters in registers might be useful.
    – David Wohlferd
    Apr 7 at 12:59










  • @ David Wohlferd If i didn't use edx, I would have to read [ebp + 16] from memory for every iteration of the loop L2, which is supposed to be slow. The rational is that it's better to read it from memory just once into a register.
    – William
    Apr 7 at 14:53










  • @ Mast We are not given the initial array to be sorted, and I don't know how to generate an array of random integers in assembly, so I just hardcoded one. There is some I/O but I left that out because it was too much code to post here.
    – William
    Apr 7 at 14:58












up vote
6
down vote

favorite









up vote
6
down vote

favorite











My task was to implement Quick Sort in assembly from pseudocode.



;*******************************************************************************
; This program implements the Quick Sort algorithm and sorts an array
;
; Author: William
; Date: 4/6/18

TITLE Quick Sort
.386
.model flat, stdcall
.stack 4096

; prototype from Kernel32 lib
ExitProcess PROTO, dwExitCode:DWORD

.data

; An array of 25 random integers to be sorted
array DWORD 43, 91, 97, 63, 52,
58, 99, 19, 33, 26,
77, 11, 41, 89, 27,
99, 98, 68, 26, 5,
73, 47, 46, 59, 21

.code

;-------------------------------------------------------------------------------
; _partition@12 (A , p, r)
;
; Quicksort subroutine
;
; Entry: A @ ebp + 8 ; pointer to array being sorted
; p @ ebp + 12 ; integer index to start of sub-array
; r @ ebp + 16 ; integer index to end of sub-array
;
; Local: x ; The pivot
; i ; index of smaller element
;
; Exit: the index of the pivot stored in eax
;-------------------------------------------------------------------------------
_partition@12:
; set up stack, save regs
push ebp
mov ebp, esp
push ebx
push ecx
push edx
push esi
push edi

; Load function parameters into registers and initialize local variables
mov ebx, [ebp + 8] ; A stored in ebx
mov ecx, [ebp + 12] ; p stored in ecx
mov edx, [ebp + 16] ; r stored in edx
mov esi, [ebx + edx * 4] ; x initialized to A[r]
lea edi, [ecx - 1] ; i initialized to p - 1

; Loop from index p to r - 1
mov eax, ecx ; j (loop count) initialized to p
L2: cmp eax, edx
jnl endL2

; If A[j] <= x
cmp [ebx + eax * 4], esi
jnle endIf1
inc edi ; i = i + 1

; swap A[i] with A[j]
mov ecx, [ebx + edi * 4]
xchg ecx, [ebx + eax * 4]
mov [ebx + edi * 4], ecx
endIf1:
inc eax
jmp L2
endL2:
; swap A[i + 1] with A[r]
inc edi ; i = i + 1
mov ecx, [ebx + edi * 4]
xchg ecx, [ebx + edx * 4]
mov [ebx + edi * 4], ecx

mov eax, edi ; return i + 1

; clean up stack, restore regs
pop edi
pop esi
pop edx
pop ecx
pop ebx
pop ebp
ret 12

;-------------------------------------------------------------------------------
; _quickSort@12 (A , p, r)
;
; Sorts array of DWORD integers
;
; Entry: A @ ebp + 8 ; pointer to array to being sorted
; p @ ebp + 12 ; integer index to start of sub-array
; r @ ebp + 16 ; integer index to end of sub-array
;
;-------------------------------------------------------------------------------
_quickSort@12:
; set up stack, save regs
push ebp
mov ebp, esp
push ebx
push ecx
push edx

; Load function parameters into registers
mov ebx, [ebp + 8] ; A stored in ebx
mov ecx, [ebp + 12] ; p stored in ecx
mov edx, [ebp + 16] ; r stored in edx

; if (p < r)
cmp ecx, edx
jnl endIf2

;q = Partition(A, p, r)
push edx
push ecx
push ebx
call _partition@12 ; q stored in eax

;Quicksort(A, p, q - 1)
dec eax
push eax
push ecx
push ebx
call _quickSort@12

;Quicksort(A, q + 1, r)
add eax, 2
push edx
push eax
push ebx
call _quickSort@12
endIf2:
; clean up stack, restore regs
pop edx
pop ecx
pop ebx
pop ebp
ret 12

main:
push 24
push 0
push OFFSET array
call _quickSort@12

; array is sorted, but code does not
; print it to the console

INVOKE ExitProcess, 0
END main


This was the pseudocode I used:



Partition(A, p, r)
x = A[r]
i = p - 1
for j = p to r - 1
if A[j] <= x
i = i + 1
exchange A[i] with A[j]
exchange A[i + 1] with A[r]
return i + 1

Quicksort(A, p, r)
if p < r
q = Partition(A, p, r)
Quicksort(A, p, q - 1)
Quicksort(A, q + 1, r)


I took the advice given to me on my last post and loaded all function parameters into registers instead of reading and writing to memory over and over. All criticism is appreciated.







share|improve this question













My task was to implement Quick Sort in assembly from pseudocode.



;*******************************************************************************
; This program implements the Quick Sort algorithm and sorts an array
;
; Author: William
; Date: 4/6/18

TITLE Quick Sort
.386
.model flat, stdcall
.stack 4096

; prototype from Kernel32 lib
ExitProcess PROTO, dwExitCode:DWORD

.data

; An array of 25 random integers to be sorted
array DWORD 43, 91, 97, 63, 52,
58, 99, 19, 33, 26,
77, 11, 41, 89, 27,
99, 98, 68, 26, 5,
73, 47, 46, 59, 21

.code

;-------------------------------------------------------------------------------
; _partition@12 (A , p, r)
;
; Quicksort subroutine
;
; Entry: A @ ebp + 8 ; pointer to array being sorted
; p @ ebp + 12 ; integer index to start of sub-array
; r @ ebp + 16 ; integer index to end of sub-array
;
; Local: x ; The pivot
; i ; index of smaller element
;
; Exit: the index of the pivot stored in eax
;-------------------------------------------------------------------------------
_partition@12:
; set up stack, save regs
push ebp
mov ebp, esp
push ebx
push ecx
push edx
push esi
push edi

; Load function parameters into registers and initialize local variables
mov ebx, [ebp + 8] ; A stored in ebx
mov ecx, [ebp + 12] ; p stored in ecx
mov edx, [ebp + 16] ; r stored in edx
mov esi, [ebx + edx * 4] ; x initialized to A[r]
lea edi, [ecx - 1] ; i initialized to p - 1

; Loop from index p to r - 1
mov eax, ecx ; j (loop count) initialized to p
L2: cmp eax, edx
jnl endL2

; If A[j] <= x
cmp [ebx + eax * 4], esi
jnle endIf1
inc edi ; i = i + 1

; swap A[i] with A[j]
mov ecx, [ebx + edi * 4]
xchg ecx, [ebx + eax * 4]
mov [ebx + edi * 4], ecx
endIf1:
inc eax
jmp L2
endL2:
; swap A[i + 1] with A[r]
inc edi ; i = i + 1
mov ecx, [ebx + edi * 4]
xchg ecx, [ebx + edx * 4]
mov [ebx + edi * 4], ecx

mov eax, edi ; return i + 1

; clean up stack, restore regs
pop edi
pop esi
pop edx
pop ecx
pop ebx
pop ebp
ret 12

;-------------------------------------------------------------------------------
; _quickSort@12 (A , p, r)
;
; Sorts array of DWORD integers
;
; Entry: A @ ebp + 8 ; pointer to array to being sorted
; p @ ebp + 12 ; integer index to start of sub-array
; r @ ebp + 16 ; integer index to end of sub-array
;
;-------------------------------------------------------------------------------
_quickSort@12:
; set up stack, save regs
push ebp
mov ebp, esp
push ebx
push ecx
push edx

; Load function parameters into registers
mov ebx, [ebp + 8] ; A stored in ebx
mov ecx, [ebp + 12] ; p stored in ecx
mov edx, [ebp + 16] ; r stored in edx

; if (p < r)
cmp ecx, edx
jnl endIf2

;q = Partition(A, p, r)
push edx
push ecx
push ebx
call _partition@12 ; q stored in eax

;Quicksort(A, p, q - 1)
dec eax
push eax
push ecx
push ebx
call _quickSort@12

;Quicksort(A, q + 1, r)
add eax, 2
push edx
push eax
push ebx
call _quickSort@12
endIf2:
; clean up stack, restore regs
pop edx
pop ecx
pop ebx
pop ebp
ret 12

main:
push 24
push 0
push OFFSET array
call _quickSort@12

; array is sorted, but code does not
; print it to the console

INVOKE ExitProcess, 0
END main


This was the pseudocode I used:



Partition(A, p, r)
x = A[r]
i = p - 1
for j = p to r - 1
if A[j] <= x
i = i + 1
exchange A[i] with A[j]
exchange A[i + 1] with A[r]
return i + 1

Quicksort(A, p, r)
if p < r
q = Partition(A, p, r)
Quicksort(A, p, q - 1)
Quicksort(A, q + 1, r)


I took the advice given to me on my last post and loaded all function parameters into registers instead of reading and writing to memory over and over. All criticism is appreciated.









share|improve this question












share|improve this question




share|improve this question








edited Apr 8 at 0:44
























asked Apr 7 at 9:24









William

28615




28615











  • Is the array-to-be-sorted supposed to be hardcoded in or were you supposed to take user input?
    – Mast
    Apr 7 at 9:45











  • Let me give you a thought experiment: What would happen if you commented out the push edx, mov edx, [ebp + 16], and pop edx from _partition@12? Passing parameters on the stack is a common way to call functions, and there are benefits to using a known and understood parameter passing protocol. But it's not the only way, or even the fastest. For a pure asm program (such as this), passing (some of) the parameters in registers might be useful.
    – David Wohlferd
    Apr 7 at 12:59










  • @ David Wohlferd If i didn't use edx, I would have to read [ebp + 16] from memory for every iteration of the loop L2, which is supposed to be slow. The rational is that it's better to read it from memory just once into a register.
    – William
    Apr 7 at 14:53










  • @ Mast We are not given the initial array to be sorted, and I don't know how to generate an array of random integers in assembly, so I just hardcoded one. There is some I/O but I left that out because it was too much code to post here.
    – William
    Apr 7 at 14:58
















  • Is the array-to-be-sorted supposed to be hardcoded in or were you supposed to take user input?
    – Mast
    Apr 7 at 9:45











  • Let me give you a thought experiment: What would happen if you commented out the push edx, mov edx, [ebp + 16], and pop edx from _partition@12? Passing parameters on the stack is a common way to call functions, and there are benefits to using a known and understood parameter passing protocol. But it's not the only way, or even the fastest. For a pure asm program (such as this), passing (some of) the parameters in registers might be useful.
    – David Wohlferd
    Apr 7 at 12:59










  • @ David Wohlferd If i didn't use edx, I would have to read [ebp + 16] from memory for every iteration of the loop L2, which is supposed to be slow. The rational is that it's better to read it from memory just once into a register.
    – William
    Apr 7 at 14:53










  • @ Mast We are not given the initial array to be sorted, and I don't know how to generate an array of random integers in assembly, so I just hardcoded one. There is some I/O but I left that out because it was too much code to post here.
    – William
    Apr 7 at 14:58















Is the array-to-be-sorted supposed to be hardcoded in or were you supposed to take user input?
– Mast
Apr 7 at 9:45





Is the array-to-be-sorted supposed to be hardcoded in or were you supposed to take user input?
– Mast
Apr 7 at 9:45













Let me give you a thought experiment: What would happen if you commented out the push edx, mov edx, [ebp + 16], and pop edx from _partition@12? Passing parameters on the stack is a common way to call functions, and there are benefits to using a known and understood parameter passing protocol. But it's not the only way, or even the fastest. For a pure asm program (such as this), passing (some of) the parameters in registers might be useful.
– David Wohlferd
Apr 7 at 12:59




Let me give you a thought experiment: What would happen if you commented out the push edx, mov edx, [ebp + 16], and pop edx from _partition@12? Passing parameters on the stack is a common way to call functions, and there are benefits to using a known and understood parameter passing protocol. But it's not the only way, or even the fastest. For a pure asm program (such as this), passing (some of) the parameters in registers might be useful.
– David Wohlferd
Apr 7 at 12:59












@ David Wohlferd If i didn't use edx, I would have to read [ebp + 16] from memory for every iteration of the loop L2, which is supposed to be slow. The rational is that it's better to read it from memory just once into a register.
– William
Apr 7 at 14:53




@ David Wohlferd If i didn't use edx, I would have to read [ebp + 16] from memory for every iteration of the loop L2, which is supposed to be slow. The rational is that it's better to read it from memory just once into a register.
– William
Apr 7 at 14:53












@ Mast We are not given the initial array to be sorted, and I don't know how to generate an array of random integers in assembly, so I just hardcoded one. There is some I/O but I left that out because it was too much code to post here.
– William
Apr 7 at 14:58




@ Mast We are not given the initial array to be sorted, and I don't know how to generate an array of random integers in assembly, so I just hardcoded one. There is some I/O but I left that out because it was too much code to post here.
– William
Apr 7 at 14:58










1 Answer
1






active

oldest

votes

















up vote
2
down vote













This is just a review of the code; I have not verified that the assembly code correctly implements the algorithm in the pseudocode.



Your use of some of the registers is a bit atypical. EAX is typically used as a temporary register or accumulator (so much so that some operations on it have shorter encodings than if those same operations are done with other registers). In your "swap" code, this would normally use EAX instead of ECX. You're using EAX as a count, but you start it off by copying the value from ECX. So just use ECX as the count, and EAX to cycle temporary values thru.



Which leads to my next point: when you make your recursive _quickSort calls, you assume that the value in EAX will not change during the first call, but it can since you're not saving and restoring the value during the call. This will make the second call work on the wrong bounds, resulting in either a longer execution (by sorting a larger area than necessary) or incorrect results (if the full area isn't sorted).



Since _partition is only used locally, it could be given a non-mangled name and parameters passed directly in the registers, rather than using the stack. (The same applies to _quickSort, but if you're going to use this code in a later project, or call it from another language, you might want to keep the passing of parameters on the stack.)



At the end of your while loop (between the endIf1 and endL2 labels), rather than unconditionally jumping to the top of the loop and checking the condition, check the condition and conditionally jump back to the top of the loop. This duplicates the condition check, but avoids an extra jump when the loop terminates. It is also a common optimization in some compiled languages (specifically C and C++).



Your use of negative conditions in conditional jump instructions is (for me) harder to read than the equivalent positive ones. JNL (jump not less) is the same as JGE (jump greater or equal), and JNLE (jump not less or equal) equal to JG (jump greater). Nothing wrong with what you have, but I'm used to the positive versions. Just stay consistent with your usage.






share|improve this answer





















  • @ Regiter use. I've been choosing registers almost arbitrarily! LOL it seems like a bit too much fuss to worry about what roles each reg is supposed to play. I see what you mean about ecx (p), though. It could have been the counter instead of assigning it's value to another counter variable. I didn't notice that. : )
    – William
    Apr 7 at 23:08










  • @ EAX in _quicksort. Wow! That's a huge bug I didn't notice. But mysteriously, it works. For the past 30 minutes I've been trying to figure out why the program works. lol I think it's because _quicksort first sets the value of eax (with it's partition call) before it uses it to make other calls. The result is that it's not affected by any changes to eax made by the calling function, and in turn, it doesn't have any affect on it's calls to itself - the net result, nothing changes, no bug. It worked out without having to fix it. lol. I'll save and restore eax anyway to stick to convention.
    – William
    Apr 7 at 23:23










  • @Calling covention in partition. This is a good point, i never thought of that. We're supposed to stick to std_call convention, but you're right. If it's a subroutine that's only used by this function, there's no reason to stick to any specific convention. No need to even bother with a stack frame. Good observation.
    – William
    Apr 7 at 23:34










  • @Duplicate coditional jmp. Hmmm. This never crossed my mind cause that's not even possible in the control structures in high level languages. That's like a combination of while and do-while. But is saving a jump instruction really more efficient than duplicating cmp instructions?
    – William
    Apr 7 at 23:49










Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f191465%2fquick-sort-in-x86-assembly-masm%23new-answer', 'question_page');

);

Post as a guest






























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
2
down vote













This is just a review of the code; I have not verified that the assembly code correctly implements the algorithm in the pseudocode.



Your use of some of the registers is a bit atypical. EAX is typically used as a temporary register or accumulator (so much so that some operations on it have shorter encodings than if those same operations are done with other registers). In your "swap" code, this would normally use EAX instead of ECX. You're using EAX as a count, but you start it off by copying the value from ECX. So just use ECX as the count, and EAX to cycle temporary values thru.



Which leads to my next point: when you make your recursive _quickSort calls, you assume that the value in EAX will not change during the first call, but it can since you're not saving and restoring the value during the call. This will make the second call work on the wrong bounds, resulting in either a longer execution (by sorting a larger area than necessary) or incorrect results (if the full area isn't sorted).



Since _partition is only used locally, it could be given a non-mangled name and parameters passed directly in the registers, rather than using the stack. (The same applies to _quickSort, but if you're going to use this code in a later project, or call it from another language, you might want to keep the passing of parameters on the stack.)



At the end of your while loop (between the endIf1 and endL2 labels), rather than unconditionally jumping to the top of the loop and checking the condition, check the condition and conditionally jump back to the top of the loop. This duplicates the condition check, but avoids an extra jump when the loop terminates. It is also a common optimization in some compiled languages (specifically C and C++).



Your use of negative conditions in conditional jump instructions is (for me) harder to read than the equivalent positive ones. JNL (jump not less) is the same as JGE (jump greater or equal), and JNLE (jump not less or equal) equal to JG (jump greater). Nothing wrong with what you have, but I'm used to the positive versions. Just stay consistent with your usage.






share|improve this answer





















  • @ Regiter use. I've been choosing registers almost arbitrarily! LOL it seems like a bit too much fuss to worry about what roles each reg is supposed to play. I see what you mean about ecx (p), though. It could have been the counter instead of assigning it's value to another counter variable. I didn't notice that. : )
    – William
    Apr 7 at 23:08










  • @ EAX in _quicksort. Wow! That's a huge bug I didn't notice. But mysteriously, it works. For the past 30 minutes I've been trying to figure out why the program works. lol I think it's because _quicksort first sets the value of eax (with it's partition call) before it uses it to make other calls. The result is that it's not affected by any changes to eax made by the calling function, and in turn, it doesn't have any affect on it's calls to itself - the net result, nothing changes, no bug. It worked out without having to fix it. lol. I'll save and restore eax anyway to stick to convention.
    – William
    Apr 7 at 23:23










  • @Calling covention in partition. This is a good point, i never thought of that. We're supposed to stick to std_call convention, but you're right. If it's a subroutine that's only used by this function, there's no reason to stick to any specific convention. No need to even bother with a stack frame. Good observation.
    – William
    Apr 7 at 23:34










  • @Duplicate coditional jmp. Hmmm. This never crossed my mind cause that's not even possible in the control structures in high level languages. That's like a combination of while and do-while. But is saving a jump instruction really more efficient than duplicating cmp instructions?
    – William
    Apr 7 at 23:49














up vote
2
down vote













This is just a review of the code; I have not verified that the assembly code correctly implements the algorithm in the pseudocode.



Your use of some of the registers is a bit atypical. EAX is typically used as a temporary register or accumulator (so much so that some operations on it have shorter encodings than if those same operations are done with other registers). In your "swap" code, this would normally use EAX instead of ECX. You're using EAX as a count, but you start it off by copying the value from ECX. So just use ECX as the count, and EAX to cycle temporary values thru.



Which leads to my next point: when you make your recursive _quickSort calls, you assume that the value in EAX will not change during the first call, but it can since you're not saving and restoring the value during the call. This will make the second call work on the wrong bounds, resulting in either a longer execution (by sorting a larger area than necessary) or incorrect results (if the full area isn't sorted).



Since _partition is only used locally, it could be given a non-mangled name and parameters passed directly in the registers, rather than using the stack. (The same applies to _quickSort, but if you're going to use this code in a later project, or call it from another language, you might want to keep the passing of parameters on the stack.)



At the end of your while loop (between the endIf1 and endL2 labels), rather than unconditionally jumping to the top of the loop and checking the condition, check the condition and conditionally jump back to the top of the loop. This duplicates the condition check, but avoids an extra jump when the loop terminates. It is also a common optimization in some compiled languages (specifically C and C++).



Your use of negative conditions in conditional jump instructions is (for me) harder to read than the equivalent positive ones. JNL (jump not less) is the same as JGE (jump greater or equal), and JNLE (jump not less or equal) equal to JG (jump greater). Nothing wrong with what you have, but I'm used to the positive versions. Just stay consistent with your usage.






share|improve this answer





















  • @ Regiter use. I've been choosing registers almost arbitrarily! LOL it seems like a bit too much fuss to worry about what roles each reg is supposed to play. I see what you mean about ecx (p), though. It could have been the counter instead of assigning it's value to another counter variable. I didn't notice that. : )
    – William
    Apr 7 at 23:08










  • @ EAX in _quicksort. Wow! That's a huge bug I didn't notice. But mysteriously, it works. For the past 30 minutes I've been trying to figure out why the program works. lol I think it's because _quicksort first sets the value of eax (with it's partition call) before it uses it to make other calls. The result is that it's not affected by any changes to eax made by the calling function, and in turn, it doesn't have any affect on it's calls to itself - the net result, nothing changes, no bug. It worked out without having to fix it. lol. I'll save and restore eax anyway to stick to convention.
    – William
    Apr 7 at 23:23










  • @Calling covention in partition. This is a good point, i never thought of that. We're supposed to stick to std_call convention, but you're right. If it's a subroutine that's only used by this function, there's no reason to stick to any specific convention. No need to even bother with a stack frame. Good observation.
    – William
    Apr 7 at 23:34










  • @Duplicate coditional jmp. Hmmm. This never crossed my mind cause that's not even possible in the control structures in high level languages. That's like a combination of while and do-while. But is saving a jump instruction really more efficient than duplicating cmp instructions?
    – William
    Apr 7 at 23:49












up vote
2
down vote










up vote
2
down vote









This is just a review of the code; I have not verified that the assembly code correctly implements the algorithm in the pseudocode.



Your use of some of the registers is a bit atypical. EAX is typically used as a temporary register or accumulator (so much so that some operations on it have shorter encodings than if those same operations are done with other registers). In your "swap" code, this would normally use EAX instead of ECX. You're using EAX as a count, but you start it off by copying the value from ECX. So just use ECX as the count, and EAX to cycle temporary values thru.



Which leads to my next point: when you make your recursive _quickSort calls, you assume that the value in EAX will not change during the first call, but it can since you're not saving and restoring the value during the call. This will make the second call work on the wrong bounds, resulting in either a longer execution (by sorting a larger area than necessary) or incorrect results (if the full area isn't sorted).



Since _partition is only used locally, it could be given a non-mangled name and parameters passed directly in the registers, rather than using the stack. (The same applies to _quickSort, but if you're going to use this code in a later project, or call it from another language, you might want to keep the passing of parameters on the stack.)



At the end of your while loop (between the endIf1 and endL2 labels), rather than unconditionally jumping to the top of the loop and checking the condition, check the condition and conditionally jump back to the top of the loop. This duplicates the condition check, but avoids an extra jump when the loop terminates. It is also a common optimization in some compiled languages (specifically C and C++).



Your use of negative conditions in conditional jump instructions is (for me) harder to read than the equivalent positive ones. JNL (jump not less) is the same as JGE (jump greater or equal), and JNLE (jump not less or equal) equal to JG (jump greater). Nothing wrong with what you have, but I'm used to the positive versions. Just stay consistent with your usage.






share|improve this answer













This is just a review of the code; I have not verified that the assembly code correctly implements the algorithm in the pseudocode.



Your use of some of the registers is a bit atypical. EAX is typically used as a temporary register or accumulator (so much so that some operations on it have shorter encodings than if those same operations are done with other registers). In your "swap" code, this would normally use EAX instead of ECX. You're using EAX as a count, but you start it off by copying the value from ECX. So just use ECX as the count, and EAX to cycle temporary values thru.



Which leads to my next point: when you make your recursive _quickSort calls, you assume that the value in EAX will not change during the first call, but it can since you're not saving and restoring the value during the call. This will make the second call work on the wrong bounds, resulting in either a longer execution (by sorting a larger area than necessary) or incorrect results (if the full area isn't sorted).



Since _partition is only used locally, it could be given a non-mangled name and parameters passed directly in the registers, rather than using the stack. (The same applies to _quickSort, but if you're going to use this code in a later project, or call it from another language, you might want to keep the passing of parameters on the stack.)



At the end of your while loop (between the endIf1 and endL2 labels), rather than unconditionally jumping to the top of the loop and checking the condition, check the condition and conditionally jump back to the top of the loop. This duplicates the condition check, but avoids an extra jump when the loop terminates. It is also a common optimization in some compiled languages (specifically C and C++).



Your use of negative conditions in conditional jump instructions is (for me) harder to read than the equivalent positive ones. JNL (jump not less) is the same as JGE (jump greater or equal), and JNLE (jump not less or equal) equal to JG (jump greater). Nothing wrong with what you have, but I'm used to the positive versions. Just stay consistent with your usage.







share|improve this answer













share|improve this answer



share|improve this answer











answered Apr 7 at 21:21









1201ProgramAlarm

2,4752618




2,4752618











  • @ Regiter use. I've been choosing registers almost arbitrarily! LOL it seems like a bit too much fuss to worry about what roles each reg is supposed to play. I see what you mean about ecx (p), though. It could have been the counter instead of assigning it's value to another counter variable. I didn't notice that. : )
    – William
    Apr 7 at 23:08










  • @ EAX in _quicksort. Wow! That's a huge bug I didn't notice. But mysteriously, it works. For the past 30 minutes I've been trying to figure out why the program works. lol I think it's because _quicksort first sets the value of eax (with it's partition call) before it uses it to make other calls. The result is that it's not affected by any changes to eax made by the calling function, and in turn, it doesn't have any affect on it's calls to itself - the net result, nothing changes, no bug. It worked out without having to fix it. lol. I'll save and restore eax anyway to stick to convention.
    – William
    Apr 7 at 23:23










  • @Calling covention in partition. This is a good point, i never thought of that. We're supposed to stick to std_call convention, but you're right. If it's a subroutine that's only used by this function, there's no reason to stick to any specific convention. No need to even bother with a stack frame. Good observation.
    – William
    Apr 7 at 23:34










  • @Duplicate coditional jmp. Hmmm. This never crossed my mind cause that's not even possible in the control structures in high level languages. That's like a combination of while and do-while. But is saving a jump instruction really more efficient than duplicating cmp instructions?
    – William
    Apr 7 at 23:49
















  • @ Regiter use. I've been choosing registers almost arbitrarily! LOL it seems like a bit too much fuss to worry about what roles each reg is supposed to play. I see what you mean about ecx (p), though. It could have been the counter instead of assigning it's value to another counter variable. I didn't notice that. : )
    – William
    Apr 7 at 23:08










  • @ EAX in _quicksort. Wow! That's a huge bug I didn't notice. But mysteriously, it works. For the past 30 minutes I've been trying to figure out why the program works. lol I think it's because _quicksort first sets the value of eax (with it's partition call) before it uses it to make other calls. The result is that it's not affected by any changes to eax made by the calling function, and in turn, it doesn't have any affect on it's calls to itself - the net result, nothing changes, no bug. It worked out without having to fix it. lol. I'll save and restore eax anyway to stick to convention.
    – William
    Apr 7 at 23:23










  • @Calling covention in partition. This is a good point, i never thought of that. We're supposed to stick to std_call convention, but you're right. If it's a subroutine that's only used by this function, there's no reason to stick to any specific convention. No need to even bother with a stack frame. Good observation.
    – William
    Apr 7 at 23:34










  • @Duplicate coditional jmp. Hmmm. This never crossed my mind cause that's not even possible in the control structures in high level languages. That's like a combination of while and do-while. But is saving a jump instruction really more efficient than duplicating cmp instructions?
    – William
    Apr 7 at 23:49















@ Regiter use. I've been choosing registers almost arbitrarily! LOL it seems like a bit too much fuss to worry about what roles each reg is supposed to play. I see what you mean about ecx (p), though. It could have been the counter instead of assigning it's value to another counter variable. I didn't notice that. : )
– William
Apr 7 at 23:08




@ Regiter use. I've been choosing registers almost arbitrarily! LOL it seems like a bit too much fuss to worry about what roles each reg is supposed to play. I see what you mean about ecx (p), though. It could have been the counter instead of assigning it's value to another counter variable. I didn't notice that. : )
– William
Apr 7 at 23:08












@ EAX in _quicksort. Wow! That's a huge bug I didn't notice. But mysteriously, it works. For the past 30 minutes I've been trying to figure out why the program works. lol I think it's because _quicksort first sets the value of eax (with it's partition call) before it uses it to make other calls. The result is that it's not affected by any changes to eax made by the calling function, and in turn, it doesn't have any affect on it's calls to itself - the net result, nothing changes, no bug. It worked out without having to fix it. lol. I'll save and restore eax anyway to stick to convention.
– William
Apr 7 at 23:23




@ EAX in _quicksort. Wow! That's a huge bug I didn't notice. But mysteriously, it works. For the past 30 minutes I've been trying to figure out why the program works. lol I think it's because _quicksort first sets the value of eax (with it's partition call) before it uses it to make other calls. The result is that it's not affected by any changes to eax made by the calling function, and in turn, it doesn't have any affect on it's calls to itself - the net result, nothing changes, no bug. It worked out without having to fix it. lol. I'll save and restore eax anyway to stick to convention.
– William
Apr 7 at 23:23












@Calling covention in partition. This is a good point, i never thought of that. We're supposed to stick to std_call convention, but you're right. If it's a subroutine that's only used by this function, there's no reason to stick to any specific convention. No need to even bother with a stack frame. Good observation.
– William
Apr 7 at 23:34




@Calling covention in partition. This is a good point, i never thought of that. We're supposed to stick to std_call convention, but you're right. If it's a subroutine that's only used by this function, there's no reason to stick to any specific convention. No need to even bother with a stack frame. Good observation.
– William
Apr 7 at 23:34












@Duplicate coditional jmp. Hmmm. This never crossed my mind cause that's not even possible in the control structures in high level languages. That's like a combination of while and do-while. But is saving a jump instruction really more efficient than duplicating cmp instructions?
– William
Apr 7 at 23:49




@Duplicate coditional jmp. Hmmm. This never crossed my mind cause that's not even possible in the control structures in high level languages. That's like a combination of while and do-while. But is saving a jump instruction really more efficient than duplicating cmp instructions?
– William
Apr 7 at 23:49












 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f191465%2fquick-sort-in-x86-assembly-masm%23new-answer', 'question_page');

);

Post as a guest













































































Popular posts from this blog

Python Lists

Aion

JavaScript Array Iteration Methods