Tag Archives: macro

story about left shift

I was tinkering with Ofront, making changes here and there in order to run it on x86_64.
By doing this I met a strange warning issued by gcc when it was compiling Console.c file.

Console.c: In function ‘Console_Int’:
Console.c:57:2: warning: left shift count >= width of type [enabled by default]

So, Console.c is generated from Console.Mod. And the Int function of Console module writes integer to the console.
This is how it looks:

  PROCEDURE Int*(i, n: LONGINT);
    VAR s: ARRAY 32 OF CHAR; i1, k: LONGINT;
  BEGIN
    IF i = SYSTEM.LSH(LONG(LONG(1)), SIZE(LONGINT)*8 - 1) THEN
      IF SIZE(LONGINT) = 8 THEN s := "8085774586302733229"; k := 19
      ELSE s := "8463847412"; k := 10
      END
    ELSE
      i1 := ABS(i);
      s[0] := CHR(i1 MOD 10 + ORD("0")); i1 := i1 DIV 10; k := 1;
      WHILE i1 > 0 DO s[k] := CHR(i1 MOD 10 + ORD("0")); i1 := i1 DIV 10; INC(k) END
    END ;
    IF i < 0 THEN s[k] := "-"; INC(k) END ;
    WHILE n > k DO Char(" "); DEC(n) END ;
    WHILE k > 0 DO  DEC(k); Char(s[k]) END
  END Int;

The error is issued in the line:

    IF i = SYSTEM.LSH(LONG(LONG(1)), SIZE(LONGINT)*8 - 1) THEN

which is translated as following in case of x86_64:

if (i == __LSHL(1, 63, LONGINT)) {

On x86 it gets translated as:

if (i == __LSHL(1, 31, LONGINT)) {

Okay, and it does not issue an error on 32 bit platform, Why?

Probably, need to look into how the _LSHL macro is written. That’s in SYSTEM.h

#define __LSHL(x, n, t) ((t)((unsigned)(x)<<(n)))

Okay, what do we see. It casts the number as unsigned, then shifts it. Obviously, the cast is necessary in order to use the sign bit.

Now let's swerve to the question why do they to do this?
And what special in this "8085774586302733229" number? Or in this: "8463847412"? If you fed this number to the Worlfram alpha saas, it does not give a clue.
The clue gives the lower part of the script, it writes the number from right to left.

WHILE k > 0 DO  DEC(k); Char(s[k]) END

Let's reverse this number and look at it as binary.

noch@SAL9000 ~ $ echo 'obase=2; 2147483648' | bc
10000000000000000000000000000000

wow. This is it. We would get it by shifting 1 to the left.
But why do they shift it?
What is LONGINT on 32 bit platform? It is a number in range from -2^31 .. 2^31 -1.
And the number 2147483648 is 2^31.
So they do it in order to be able to print the number 2^31! Only for that!
Obviously, in case of x86_64 that's the number 9,223,372,036,854,775,808

Okay, let's tinker further. We want to be able to print it, why not?

So why C compiler (I use gcc) does not issue
the warning in case of 32 bits but issues in case of 64?

Because of the macro, probably. Let's write a simple C text to check.

#include "stdio.h"
#define __LSHL(x, n, t) ((t)((unsigned)(x)<<(n)))

int main()
{
   long i;
   printf("%lu\n",sizeof(long));

   i=__LSHL(1, sizeof(long)*8-1, long);

   printf ("%lu\n",i);
return 0;

}

Then,

$ gcc -o test0 test0.c
test0.c: In function ‘main’:
test0.c:9:4: warning: left shift count >= width of type [enabled by default]

The problem is in the cast. If we do (unsigned) then it casts it to unsigned int, not unsigned long.
Let's change the macro to

#define __LSHL(x, n, t) ((t)((unsigned t)(x)<<(n)))

Now it compiles without warnings and run.

$ gcc -o test0 test0.c
$ ./test0
8
9223372036854775808

However, compiler fails to compile it when we make the same change in SYSTEM.h

Console.c: In function ‘Console_Int’:
Console.c:56:11: error: expected ‘)’ before ‘LONGINT’

Why?
Let's emulate the case exactly. We need to add LONGINT typedef

#include "stdio.h"
typedef long LONGINT;
#define __LSHL(x, n, t) ((t)((unsigned t)(x)<<(n)))

int main()
{
   long i;
   printf("%lu\n",sizeof(LONGINT));

   i=__LSHL(1, sizeof(LONGINT)*8-1, LONGINT);

   printf ("%lu\n",i);
return 0;

}

It does not compile!

noch@SAL9000 /tmp $ gcc -o test1 test1.c
test1.c: In function ‘main’:
test1.c:10:6: error: expected ‘)’ before ‘LONGINT’

We clearly see that in the macro there are three arguments which _LSHL expects but for some strange reason gcc expects the closing parentheses after the second argument.
However we can compile it with g++ - it understands this typedef issue

noch@SAL9000 /tmp $ cp test1.c test1.cpp
noch@SAL9000 /tmp $ g++ -o test1 test1.cpp
noch@SAL9000 /tmp $ ./test1
8
9223372036854775808
noch@SAL9000 /tmp $

Okay. What can be done here?
We can do

typedef unsigned long ULONGINT;

and then change macro like this:

#define __LSHL(x, n, t) ((t)((ULONGINT)(x)<<(n)))

So the file looks like

#include "stdio.h"
typedef long LONGINT;
typedef unsigned long ULONGINT;
#define __LSHL(x, n, t) ((t)((ULONGINT)(x)<<(n)))
//#define __LSHL(x, n, t) ((t)((unsigned t)(x)<<(n)))

int main()
{
   long i;
   printf("%lu\n",sizeof(LONGINT));

   i=__LSHL(1, sizeof(LONGINT)*8-1, LONGINT);

   printf ("%lu\n",i);
return 0;

}

now, and it compiles

$ gcc -o test test.c
$ ./test
8
9223372036854775808

But this is not what we want.
The good solution would be to replace a macro with a function, which can be debugged, and which won't give such an error. But it is not possible to pass a type as an argument in plain C without a macro. It may be possible by using templates, in C++, but we use C.
May be there is a need to change Ofront in a way it would generate a different function call.
Another solution is to use define instead of typedef.

#include "stdio.h"
//typedef long LONGINT;
#define LONGINT long

//typedef unsigned long ULONGINT;
//#define __LSHL(x, n, t) ((t)((ULONGINT)(x)<<(n)))
#define __LSHL(x, n, t) ((t)((unsigned t)(x)<<(n)))

int main()
{
   long i;
   printf("%lu\n",sizeof(LONGINT));

   i=__LSHL(1, sizeof(LONGINT)*8-1, LONGINT);

   printf ("%lu\n",i);
return 0;

}

Test runs

$ gcc -o test test.c
$ ./test
8
9223372036854775808

For now the solution is to replace typedef with define in SYSTEM.h and use (unsigned t) cast in the macro.

und so weiter