Operator * and + produces wrong result in digital mars - c

I am trying to calculate a number which produce Longest Collatz sequence. But here is a strange problem. 3n+1 become 38654705674 when n is 3. I do not see an error. here is the full code:
/* 6.c -- calculates Longest Collatz sequence */
#include <stdio.h>
long long get_collatz_length(long long);
int main(void)
{
long long i;
long long current, current_count, count;
current_count = 1;
current = 1;
for(i=2;i<1000000;i++)
{
// works fine when i is 2 the next line take eternity when i is 3;
count = get_collatz_length(i);
if(current_count <= count)
{
current = i;
current_count = count;
}
}
printf("%lld %lld\n", current, current_count);
return 0;
}
long long get_collatz_length(long long num)
{
long long count;
count = 1;
while(num != 1)
{
printf("%lld\n", num);
if(num%2)
{
num = num*3+1; // here it is;
}
else
{
num/=2;
}
count++;
}
puts("");
return count;
}

It's seems to be bug in dmc compiler, that fails to handle long long type correctly. Here is narrowed test-case:
#include <stdio.h>
int main(void)
{
long long num = 3LL;
/*printf("%lld\n", num);*/
num = num * 3LL;
char *t = (char *) &num;
for (int i = 0; i < 8; i++)
printf("%x\t", t[i]);
putchar('\n');
/*printf("%lld\n", num);*/
return 0;
}
It produces (little endian, so 0x900000009 == 38 654 705 673):
9 0 0 0 9 0 0 0
From dissasembly it looks that it stores 64-bit integer as two 32-bit registers:
.data:0x000000be 6bd203 imul edx,edx,0x3
.data:0x000000c1 6bc803 imul ecx,eax,0x3
.data:0x000000c4 03ca add ecx,edx
.data:0x000000c6 ba03000000 mov edx,0x3
.data:0x000000cb f7e2 mul edx
.data:0x000000cd 03d1 add edx,ecx
.data:0x000000cf 31c0 xor eax,eax
I additionaly tested it with objconv tool, that just confirms my initial diagnose:
#include <stdio.h>
void mul(void)
{
long long a;
long long c;
a = 5LL;
c = a * 3LL;
printf("%llx\n", c);
}
int main(void)
{
mul();
return 0;
}
disassembly (single section):
>objconv.exe -fmasm ..\dm\bin\check.obj
_mul PROC NEAR
mov eax, 5 ; 0000 _ B8, 00000005
cdq ; 0005 _ 99
imul edx, edx, 3 ; 0006 _ 6B. D2, 03
imul ecx, eax, 3 ; 0009 _ 6B. C8, 03
add ecx, edx ; 000C _ 03. CA
mov edx, 3 ; 000E _ BA, 00000003
mul edx ; 0013 _ F7. E2
add edx, ecx ; 0015 _ 03. D1
push edx ; 0017 _ 52
push eax ; 0018 _ 50
push offset FLAT:?_001 ; 0019 _ 68, 00000000(segrel)
call _printf ; 001E _ E8, 00000000(rel)
add esp, 12 ; 0023 _ 83. C4, 0C
ret ; 0026 _ C3
_mul ENDP
Note that mul edx operates implicitely on eax. The result is stored in both registers, higher part (in this case 0) in stored in edx, while lower in eax.

Related

Convert 32 bit GDI bitmap to Grayscale by manipulating bmBits

I program only on windows (AutoHotkey) and mainly use its win32 api.
I want to convert a GDI Bitmap (32bit DIB) to grayscale.
I use GetObject() to obtain a BITMAP structure and pass bmBITS and it size to following function
int ToGrayscale(unsigned char * s, int n)
{
float b, g, r, y;
for (int i=0; i<n; i+=4)
{
b = (float) s[i+0];
g = (float) s[i+1];
r = (float) s[i+2];
y = (0.299 * r) + (0.587 * g) + (0.114 * b);
s[i+0] = s[i+1] = s[i+2] = (unsigned char)y;
}
return 1;
}
The above is the full code. I compile it to an .obj with Pelles C, extract the machine code from .obj
and call/use that machine code from AutoHotkey language.
The machine code when called gives me access violation error.
s[i+0] = s[i+1] = s[i+2] = 0;
works fine and fills the image to black, but I want to convert the pixels with y, the grayscale value.
What is the correct way of doing this?.
I'm not sure if I've provided enough info. Please ask/suggest and I will update it.
EDIT 1:
I added one more working function ToBlack() to the c file.
int ToBlack(unsigned char * s, int n)
{
for (int i=0; i<n; i+=4)
{
s[i+0] = s[i+1] = s[i+2] = 0;
}
return 1;
}
int ToGrayscale(unsigned char * s, int n)
{
float b, g, r, y;
for (int i=0; i<n; i+=4)
{
b = (float) s[i+0];
g = (float) s[i+1];
r = (float) s[i+2];
y = (0.299 * r) + (0.587 * g) + (0.114 * b);
s[i+0] = s[i+1] = s[i+2] = (unsigned char)y;
}
return 1;
}
The source bitmap is a 2x2 32bit opaque bitmap with single ARGB color FFAABBCC
The bmBITS size is 16 bytes and the hex representation of data is
CCBBAAFFCCBBAAFFCCBBAAFFCCBBAAFF
ToBlack() works fine and when I inspect bmBITS after running the machine code,
I get following results - correctly.
ToBlack(bmBITS, 16) Result: 000000FF000000FF000000FF000000FF
ToBlack(bmBITS, 12) Result: 000000FF000000FF000000FFCCBBAAFF
ToBlack(bmBITS, 8) Result: 000000FF000000FFCCBBAAFFCCBBAAFF
ToBlack(bmBITS, 4) Result: 000000FFCCBBAAFFCCBBAAFFCCBBAAFF
With ToGrayscale(bmBITS, 16) data is unchanged.
I'm guessing that the crash is occurring when y is being assigned.
I found the source for the Access violation error.
The machine code isn't portable.
For the following c code
int ToGrayscale(unsigned char * s, int n)
{
float b, g, r, y;
for (int i=0; i<n; i+=4)
{
b = (float) s[i+0];
g = (float) s[i+1];
r = (float) s[i+2];
y = (0.299 * r) + (0.587 * g) + (0.114 * b);
s[i+0] = s[i+1] = s[i+2] = (unsigned char)y;
}
return 1;
}
the .obj dump is as follows:
Dump of D:\AhkScripts\AHK-003\ToGrayscale.obj
File type: OBJ
_ToGrayscale:
[00000000] 55 push ebp
[00000001] 89E5 mov ebp,esp
[00000003] 83EC10 sub esp,10
[00000006] 56 push esi
[00000007] 8B5508 mov edx,dword ptr [ebp+8]
[0000000A] 8B4D0C mov ecx,dword ptr [ebp+C]
[0000000D] 85C9 test ecx,ecx
[0000000F] 7E6C jle 0000007D
[00000011] 31F6 xor esi,esi
[00000013] 0FB60432 movzx eax,byte ptr [edx+esi]
[00000017] 50 push eax
[00000018] DB0424 fild dword ptr [esp]
[0000001B] 58 pop eax
[0000001C] D95DFC fstp dword ptr [ebp-4]
[0000001F] 0FB6443201 movzx eax,byte ptr [edx+esi+1]
[00000024] 50 push eax
[00000025] DB0424 fild dword ptr [esp]
[00000028] 58 pop eax
[00000029] D95DF8 fstp dword ptr [ebp-8]
[0000002C] 0FB6443202 movzx eax,byte ptr [edx+esi+2]
[00000031] 50 push eax
[00000032] DB0424 fild dword ptr [esp]
[00000035] 58 pop eax
[00000036] D95DF4 fstp dword ptr [ebp-C]
[00000039] D945F4 fld dword ptr [ebp-C]
[0000003C] DC0D00000000 fmul qword ptr [#37]
[00000042] D945F8 fld dword ptr [ebp-8]
[00000045] DC0D00000000 fmul qword ptr [#38]
[0000004B] DEC1 faddp st(1),st
[0000004D] D945FC fld dword ptr [ebp-4]
[00000050] DC0D00000000 fmul qword ptr [#39]
[00000056] DEC1 faddp st(1),st
[00000058] D95DF0 fstp dword ptr [ebp-10]
[0000005B] D945F0 fld dword ptr [ebp-10]
[0000005E] E800000000 call ___ftouc
[00000063] 88443202 mov byte ptr [edx+esi+2],al
[00000067] D945F0 fld dword ptr [ebp-10]
[0000006A] E800000000 call ___ftouc
[0000006F] 88443201 mov byte ptr [edx+esi+1],al
[00000073] 880432 mov byte ptr [edx+esi],al
[00000076] 83C604 add esi,4
[00000079] 39CE cmp esi,ecx
[0000007B] 7C96 jl 00000013
[0000007D] B801000000 mov eax,1
[00000082] 5E pop esi
[00000083] 89EC mov esp,ebp
[00000085] 5D pop ebp
[00000086] C3 ret
SUMMARY
28 .drectve
18 .rdata
87 .text
There seems to be calls to a non-existent function named ___ftouc which I suppose
is Float to Unsigned Char, and it will be available only when linked.
(Please correct me if I'm wrong)
Solution? Do the the math in a way that wouldn't require type casting.
The following code works just fine in x86 / x64.
void ToGrayscale(unsigned char * s, int n)
{
for (int i=0; i<n; i+=4)
s[i+0] = s[i+1] = s[i+2] = ( (s[i+0]*114) + (s[i+1]*587) + (s[i+2]*299) ) / 1000;
}

how to call a function in assembly?

How would I go about designating a portion of my assembly code as a function and calling that function from within the _asm {} block? Let's say for example, I wanted the function to start at the for loop, "n" elements are filled with a value.
#include <stdio.h>
#include <string.h>
void printBytes(char *data, int length)
{
int x;
for (x = 0; x < length; x++)
{
if ((x & 0xF) == 0) printf("\n");
printf("%02X ", (unsigned char)data[x]);
}
printf("\n\n");
return;
} // printBytes
void function(){
char string[] = "The end is near!";
void* dst = &string;
unsigned char value = 0xA5;
int nCount = 5;
printf("The message is: %s\n", string);
printBytes(string, strlen(string));
__asm {
//type cast the str from void* to char*
mov eax, DWORD PTR [dst]
mov DWORD PTR [ebp-88], eax
//fill "n" elements/blocks with value
mov DWORD PTR [ebp-76], 0
jmp label_3
label_2:
mov eax, DWORD PTR[ebp-76]
add eax, 1
mov DWORD PTR [ebp-76], eax
label_3:
mov eax, DWORD PTR [ebp-76]
cmp eax, DWORD PTR [nCount]
jge EXIT
mov eax, DWORD PTR [ebp-88]
add eax, DWORD PTR [ebp-76]
mov cl, BYTE PTR [value]
mov BYTE PTR[eax], cl
jmp label_2
EXIT:
}// asm_memset */
printf("Now the message is: %s\n", string);
printBytes(string, strlen(string));
return;
}
int main() {
function();
printf("Press ENTER key to continue...");
getchar();
return(0);
}
Please note the solution to your problem is likely to be non-portable.
You will need to find the ABI for your architecture/system/compiler, and comply with its calling conventions.

Reviewing IF conditional code to save CPU cycles

I am reviewing the usage of if condition in my program, in there, I have lines like the following:
if(count > 4) count = 4;
Would it be a good idea to write the above if conditional statement as the following non-branched one?
count = 4*(count> 4) + count*(count<= 4);
I also have the following snippet there:
for (j=0, i=0; j<NCARD_PER_SUIT && i<CARDS_PER_PLAYER+CARDS_ON_BOARD; ++j) {
if (card_cfg.hearts & cfg_mask[j]) {
player_hand[i].card.face = j;
player_hand[i++].card.suit = HEART;
}
if (card_cfg.spades & cfg_mask[j]) {
player_hand[i].card.face = j;
player_hand[i++].card.suit = SPADE;
}
if (card_cfg.clubs & cfg_mask[j]) {
player_hand[i].card.face = j;
player_hand[i++].card.suit = CLUB;
}
if (card_cfg.diamonds & cfg_mask[j]) {
player_hand[i].card.face = j;
player_hand[i++].card.suit = DIAMOND;
}
}
and wondering if there is good (non-branched) way to write the above, any suggestions?
EDIT: Based on some feedback below, i compared the assembly instructions (using MSVS2015 for Windows 10) and got the following:
; 718 : count = 4*(count> 4) + count*(count<= 4);
xor ebx, ebx
cmp edx, 4
setle bl
xor ecx, ecx
imul ebx, edx
cmp edx, 4
mov edx, 4
cmovg ecx, edx
add ebx, ecx
And if revert back to if statement, i get the following, where no jump instruction and total number of instructions 2/3rd compare to the above:
; 718 : if( count >4) count = 4;
mov eax, DWORD PTR _i$6$[ebp]
cmp edx, edi
mov ebx, DWORD PTR _player$GSCopy$1$[ebp]
cmovg edx, edi
mov edi, DWORD PTR _count$1$[ebp]
mov DWORD PTR _count$4$[ebp], edx
EDIT #2: Based on the tip from the comments below, i went ahead and created a
union
typedef union {
struct cfg {
unsigned short hearts;
unsigned short spades;
unsigned short clubs;
unsigned short diamonds;
} suit;
unsigned long long allsuits;
} card_cfg_t;
And with help of this union, i was able to rewrite the second snippet of OP as follows, which seem sot save a lot (20% in my case) if I build it for 64-bit machine and takes more time (extra 40%) if i build it for 32-bit machine:
for (j=0, i=0; j<NCARD_PER_SUIT && i<CARDS_PER_PLAYER+CARDS_ON_BOARD; ++j) {
for (int k=0; k<4; ++k) {
present = (int)((card_cfg.allsuits & (cfg_mask[j] << 16*k)) != 0);
player_hand[i].card.face = j*present;
player_hand[i].card.suit = k;
i = i + present;
}
}
Those micro optimistion do not have too much sense but of you want to compare (you will see the difference between your one and mine - switch on optimisation - compiler is really good in it):
int count;
void foo()
{
count = 4*(count> 4) + count*(count <= 4);
}
void foo1()
{
count = count > 4 ? 4 : count;
}
void foo4()
{
if(count> 4) count = 4;
}
foo:
mov edx, DWORD PTR count[rip]
xor ecx, ecx
cmp edx, 4
setle al
setg cl
movzx eax, al
imul eax, edx
lea eax, [rax+rcx*4]
mov DWORD PTR count[rip], eax
ret
foo1:
cmp DWORD PTR count[rip], 4
mov eax, 4
cmovle eax, DWORD PTR count[rip]
mov DWORD PTR count[rip], eax
ret
foo4:
cmp DWORD PTR count[rip], 4
jle .L6
mov DWORD PTR count[rip], 4
.L6:
rep ret
The answer to the second loop must be something like:
pushcards(player, popcards(dealer));

How do you use the buffer? [intel based assembly]

I am having trouble figuring out how to use the buffer in my code. The code is supposed to loop through and count the number of each char is in the array. I haven't written the function for lower case and upper case chars yet but for the int part it outputs "0 - +0" every time no matter what combination of integers or letters I input. I'm not sure if this issue has to do with the buffer or if its from another part of my code. Any suggestions, tips, or explanations are welcome. I am compiling on Visual Studios 2015 with the kip irvine library.
.data
buffer BYTE 1064 DUP(?)
;sentence input
sentence dword ?
;enter gallons prompt
prompt1 BYTE "Enter the sentence you would like to count: ", 0
;start of int values
intEnd dword 57
;int you are on
intIndex dword 48
;upper char you are on
upcharIndex dword 65
;start of upper case char values
upcharEnd dword 90
;lower char you are on
lowCharIndex dword 97
;start of lower case char values
lowcharEnd dword 122
;prompt dash
dash BYTE " - "
;count vals
count dword 0
.code
main PROC
;shows the prompt
mov edx, OFFSET prompt1
call WriteString
mov edx, OFFSET buffer
mov ecx, SIZEOF buffer
;reads the sentence the user inputs
call ReadString
mov sentence, eax
mov ebx, OFFSET sentence
mov ecx, [LENGTHOF sentence]
checkint:
mov eax, intIndex
cmp eax, intEnd
je done
mov edx, count
L1:
call DumpRegs
cmp al, [ebx]
jne no_inc
cmp al, 00
je none
incre:
inc dl
no_inc:
add ebx, 8
jmp L1
none:
mov intIndex, eax
call WriteChar
call DumpRegs
mov count, edx
mov edx, OFFSET dash
call WriteString
call DumpRegs
mov eax, count
call WriteInt
mov eax,intIndex
inc eax
mov intIndex, eax
call DumpRegs
mov count, eax
jmp checkint
done:
RET
exit
main ENDP
END main
C++ code:
#include<iostream>
#include<string>
using namespace std;
int main()
{
int startint = 48;
int endint = 57;
int startupper = 65;
int endupper = 90;
int startlower = 97;
int endlower = 122;
int count = 0;
cout << "Enter the string you would like to count: ";
string sentence;
getline(cin, sentence);
cout << endl;
for (int i = startint; i < endint; i++)
{
for (char a : sentence)
{
if (a == i)
count++;
}
if(count !=0)
cout << (char)i << " - " << count<<endl;
count = 0;
}
for (int i = startupper; i < endupper; i++)
{
for (char a : sentence)
{
if (a == i)
count++;
}
if(count !=0)
cout << (char)i << " - " << count<<endl;
count = 0;
}
for (int i = startlower; i < endlower; i++)
{
for (char a : sentence)
{
if (a == i)
count++;
}
if(count !=0)
cout << (char)i << " - " << count<<endl;
count = 0;
}
return 0;
}
call ReadString
mov sentence, eax
mov ebx, OFFSET sentence
mov ecx, [LENGTHOF sentence]
The ReadString function returns the size of the input in the EAX register. Yet you use this as an address!
call ReadString
mov sentence, eax ;This is a length !
mov ebx, OFFSET buffer
mov ecx, sentence
cmp al, [ebx]
jne no_inc
cmp al, 00
je none
Check for the end of the string before doing anything else:
cmp byte ptr [ebx], 0
je none
cmp al, [ebx]
jne no_inc
Since count is a dword, increment EDX rather than DL.
Since strings use 1 byte per character, add just 1 to EBX in stead of 8.
checkint:
mov eax, intIndex
cmp eax, intEnd
je done
This way you'll miss the final iteration! Only jump to done when EAX is above intEnd:
checkint:
mov eax, intIndex
cmp eax, intEnd
ja done
I hope this helps to get the main code working...

Failure of -mavx optimization with gcc?

EDIT partial solution below (EDIT 2), but I have still one question (see at the end)
I am trying to compile the following C program with gcc-4.9.2, on Windows 7, 32 bits, running on a Pentium G3220 (according to Windows System Information). If I understand correctly, this processor does not have AVX extensions, so it's quite natural that something happens, I am just unsure about what exactly. Initially, I was playing with optimizations with gcc, and I tried -mavx rather "accidentally".
The following program computes permutations of numbers 0 ... n-1 (with n given as an argument) in lexicographic order, and also rank of each permutation (its position in this sequential order), and "unrank" (recover permutation from rank), and checks that all of these are correct. It should only print "OK" or "Error" in the end.
With gcc -O3, the program runs correctly with all integer input I checked (1 <= n <= 11).
With gcc -O3 -mavx, it runs correctly for 1 <= n <= 7, and for n >= 8, it prints nothing, and actually it does nothing (almost no delay before exiting). I get no message from the program or from Windows (I would have expected maybe a crash with an unknown instruction, but it didn't happen).
(On another computer with Windows 7 64 bits, on a core-i5, and the same gcc-4.9.2, the program seems to run fine with of without -mavx, when compiled either in 32 or 64 bits)
What I don't understand is why it runs correctly for some input values, and fails for other ones. Does anybody have some hint about this?
Here is the full program, followed by a shorter one with the same problem.
#include <stdlib.h>
#include <stdio.h>
#define SWAP(a,b) {int c; c = a; a = b; b = c;}
int next_perm(int n, int a[n]) {
int i, j, k;
for(i = n - 1; i > 0 && a[i - 1] > a[i]; i--);
for(j = i, k = n - 1; j < k; j++, k--) SWAP(a[j], a[k]);
if(i == 0) return 0;
for(j = i--; a[j] < a[i]; j++);
SWAP(a[i], a[j]);
return 1;
}
#undef SWAP
void copyvec(int n, int dst[n], int src[n]) {
int i;
for(i = 0; i < n; i++) {
dst[i] = src[i];
}
}
int eqvec(int n, int a[n], int b[n]) {
int i;
for(i = 0; i < n; i++) {
if(a[i] != b[i]) return 0;
}
return 1;
}
int rank(int n, int a[n]) {
int v[n], i, j, r;
v[n - 1] = 1;
for(j = n - 2; j >= 0; j--) v[j] = v[j + 1]*(n - 1 - j);
for(r = i = 0; ; i++) {
for(j = i; j < n; j++) {
if(a[j] > j) goto cont;
}
return r;
cont:
i = j;
r += v[i]*(a[i] - i);
for(j = i + 1; j < n; j++) {
if(a[j] < a[i]) a[j]++;
}
}
}
void unrank(int n, int a[n], int p) {
int v[n], i, j, r, s;
v[n - 1] = 1;
for(i = n - 2; i >= 0; i--) v[i] = v[i + 1]*(n - 1 - i);
p %= n*v[0];
for(i = 0; i < n; i++) a[i] = i;
for(i = 0; p > 0; i++) {
for(; v[i] > p; i++);
r = p/v[i];
p %= v[i];
for(s = a[j = i + r]; j >= i; j--) a[j] = a[j - 1];
a[i] = s;
}
}
int main(int argc, char **argv) {
int n, i, r, s = 0, q = 0;
int *a = NULL, *b = NULL, *c = NULL;
if(argc == 2 && (n = strtol(argv[1], NULL, 0)) > 0) {
a = malloc(n*sizeof(int));
b = malloc(n*sizeof(int));
c = malloc(n*sizeof(int));
if(!a || !b || !c) {
puts("Unable to allocate memory");
goto end;
} else {
for(i = 0; i < n; i++) a[i] = i;
do {
copyvec(n, b, a);
r = rank(n, b);
unrank(n, c, r);
q |= s++ != r || !eqvec(n, a, c);
} while(next_perm(n, a));
puts(q?"Error":"OK");
}
} else {
puts("perm n - Check all permutations of {0 ... n - 1}, with n > 0");
}
end:
if(a) free(a);
if(b) free(b);
if(c) free(c);
return 0;
}
EDIT
Following Brian Cain's comment, here is a shorter program with the same problem. I removed all checks on input value, all the rank/unrank stuff, and I replaced the malloc/free with an array of size 20 (only one now, since b and c are not used anymore). Now the program only computes the permutations with the while(next_perm(n, a)); loop, and does nothing with them. It should still print "OK" in the end, though, because the value of q does not change after the initial q=0.
#include <stdlib.h>
#include <stdio.h>
#define SWAP(a,b) {int c; c = a; a = b; b = c;}
int next_perm(int n, int a[n]) {
int i, j, k;
for(i = n - 1; i > 0 && a[i - 1] > a[i]; i--);
for(j = i, k = n - 1; j < k; j++, k--) SWAP(a[j], a[k]);
if(i == 0) return 0;
for(j = i--; a[j] < a[i]; j++);
SWAP(a[i], a[j]);
return 1;
}
#undef SWAP
int main(int argc, char **argv) {
int n, i, r, s = 0, q = 0, a[20];
n = strtol(argv[1], NULL, 0);
for(i = 0; i < n; i++) a[i] = i;
while(next_perm(n, a));
puts(q?"Error":"OK");
return 0;
}
EDIT 2: explanation of the assembly output
I add also the disassembly output of gcc (in Intel syntax), found with gcc -O3 -mavx -S -masm=intel and gcc-4.9.2 (see link above for the actual binary files of the compiler). However, it needs some work, because as is, gcc will inline the call to next_perm, and it's less readable. I also remove the CFI directives and alignment and actually all other directives, to improve readability:
_next_perm:
LFB0:
push ebp
push edi
push esi
push ebx
mov ecx, DWORD PTR [esp+20]
mov edx, DWORD PTR [esp+24]
lea eax, [ecx-1]
test eax, eax
jle L12
mov edi, DWORD PTR [edx-4+ecx*4]
cmp DWORD PTR [edx-8+ecx*4], edi
mov ecx, eax
jg L5
jmp L11
L28:
mov esi, DWORD PTR [edx+ecx*4]
cmp DWORD PTR [edx-4+ecx*4], esi
jle L27
L5:
sub ecx, 1
jne L28
L4:
mov ebx, ecx
L7:
mov esi, DWORD PTR [edx+ebx*4]
mov edi, DWORD PTR [edx+eax*4]
mov DWORD PTR [edx+ebx*4], edi
mov DWORD PTR [edx+eax*4], esi
add ebx, 1
sub eax, 1
cmp ebx, eax
jl L7
L2:
xor eax, eax
test ecx, ecx
je L23
L11:
sal ecx, 2
lea esi, [edx+ecx]
lea ebp, [edx-4+ecx]
mov ebx, DWORD PTR [esi]
mov edi, DWORD PTR [ebp+0]
cmp edi, ebx
jle L9
lea eax, [edx+4+ecx]
L10:
mov esi, eax
add eax, 4
mov ebx, DWORD PTR [eax-4]
cmp ebx, edi
jl L10
L9:
mov DWORD PTR [ebp+0], ebx
mov eax, 1
mov DWORD PTR [esi], edi
L23:
pop ebx
pop esi
pop edi
pop ebp
ret
L27:
cmp eax, ecx
jg L4
jmp L11
L12:
mov ecx, eax
jmp L2
The assembly output is the same with or without -mavx, apart from label numbers: there is no AVX instruction, which means the problem actually lies in main.
This can be checked by adding some puts in main:
int main(int argc, char **argv) {
int n, i, q = 0, a[20];
puts("X");
n = strtol(argv[1], NULL, 0);
puts("Y");
for(i = 0; i < n; i++) a[i] = i;
puts("Z");
while(next_perm(n, a));
puts(q?"Error":"OK");
return 0;
}
Then, the programs prints only X and Y when it fails, hence the problem comes from the AVX instructions used to build 'a' in the for loop between Y and Z.
Here is the assembly output of main, again without directives (LC2 points to "Y", and LC3 to "Z"). The only AVX instructions in the assembly ouptut of main are between those two puts, and they are used for the for loop that builds the initial 'a', that is the array {0, 1, ..., n-1}. What happens actually, is that AVX instructions are used to build several elements of 'a' at a time (4 I guess), and if the length of 'a' is not a multiple of 4, then there is an additional step (between L4 and L9), before calling the puts("Z") at L9, then the while(next_perm(n, a)); at L3. Thus, the problem is very simple: if n is small enough, then the AVX loop is actually not run, and there is no error. Here the maximum valid n is 4, but it varies between differents runs of gcc, it's a bit randomized it seems (I got 8 yesterday).
The LC0 and LC4 labels point to two arrays of 4 elements that are used by the AVX instructions: LC0 is {0,1,2,3}, and LC4 is {4,4,4,4}. No wonder why they are here, even without deep knowledge of AVX, it smells like an unrolled loop :-)
_main:
push ebp
mov ebp, esp
push edi
push esi
push ebx
and esp, -16
sub esp, 96
call ___main
mov DWORD PTR [esp], OFFSET FLAT:LC1
call _puts
mov eax, DWORD PTR [ebp+12]
mov DWORD PTR [esp+8], 0
mov DWORD PTR [esp+4], 0
mov eax, DWORD PTR [eax+4]
mov DWORD PTR [esp], eax
call _strtol
mov DWORD PTR [esp], OFFSET FLAT:LC2
mov ebx, eax
call _puts
test ebx, ebx
jle L17
lea edx, [ebx-4]
lea ecx, [ebx-1]
shr edx, 2
add edx, 1
cmp ecx, 3
lea eax, [0+edx*4]
jbe L10
vmovdqa xmm1, XMMWORD PTR LC4
lea esi, [esp+16]
xor ecx, ecx
vmovdqa xmm0, XMMWORD PTR LC0
L5:
mov edi, ecx
add ecx, 1
sal edi, 4
cmp edx, ecx
vmovaps XMMWORD PTR [esi+edi], xmm0
vpaddd xmm0, xmm0, xmm1
ja L5
cmp ebx, eax
je L9
L4:
lea edx, [eax+1]
mov DWORD PTR [esp+16+eax*4], eax
cmp ebx, edx
jle L9
mov DWORD PTR [esp+16+edx*4], edx
lea edx, [eax+2]
cmp ebx, edx
jle L9
add eax, 3
mov DWORD PTR [esp+16+edx*4], edx
cmp ebx, eax
jle L9
mov DWORD PTR [esp+16+eax*4], eax
L9:
mov DWORD PTR [esp], OFFSET FLAT:LC3
call _puts
L3:
mov DWORD PTR [esp+4], esi
mov DWORD PTR [esp], ebx
call _next_perm
test eax, eax
jne L3
mov DWORD PTR [esp], OFFSET FLAT:LC5
call _puts
lea esp, [ebp-12]
xor eax, eax
pop ebx
pop esi
pop edi
pop ebp
ret
L10:
xor eax, eax
lea esi, [esp+16]
jmp L4
L17:
lea esi, [esp+16]
jmp L9
Now, I understand what actually happens, but one question remains: why is there no error message whatsoever when the program tries to run an AVX instruction? It simply exits, or it's killed, but without any hint that something went wrong.
This code always results in:
where parameter = n
a[] = {0,0,2, 3, ...,n-2,n-1}
b[] = {n-1, n-1, ... , n-1}
c[] = {n-1, n-2, ... , 0}
when it reaches the above conditions,
then it exits with "OK"
the amount of time spent executing the code
climbs at an exponential rate
as the value of the parameter is increased

Resources