1

I am working on a 32-bit architecture where int64_t is defined only with the most recent version of the compiler (software emulation). Since we did not entirely upgraded to the last compiler, I would like to manage 64-bit integers with a union and define basic arithmetic operations.

I wrote this:

typedef union _int64_u {
    int64_t  int64;
    int64_32 int32;
} int64_u;

typedef struct _int64_32 {
    int32_t hi;
    int32_t lo;
}  

I would like to clarify the following points:

  • What is the naming standards for such definition (part and types)?
  • Is this solution correct?

Hereafter, an example of the add and sub functions:

#pragma inline
#pragma always_inline
int64_u int64_sub(int64_u x, int64_u y)
{
    int64_u n;
    asm("%0 = %1 - %2;"
            : "=d" (n.int32.lo)
            : "d"  (x.int32.lo), "d" (y.int32.lo));
    asm("%0 = %1 - %2 + CI - 1;"
            : "=d" (n.int32.hi)
            : "d"  (x.int32.hi), "d" (y.int32.hi));
    return n;
}

#pragma inline
#pragma always_inline
int64_u int64_add(int64_u x, int64_u y)
{
    int64_u n;
    asm("%0 = %1 + %2;"
            : "=d" (n.int32.lo)
            : "d"  (x.int32.lo), "d" (y.int32.lo));
    asm("%0 = %1 + %2 + CI;"
            : "=d" (n.int32.hi)
            : "d"  (x.int32.hi), "d" (y.int32.hi));
    return n;
} 
phuclv
  • 27,258
  • 11
  • 104
  • 360
nowox
  • 19,233
  • 18
  • 91
  • 202

2 Answers2

1

First I should note that int64_t is a C99 feature, but older C89 compilers often already have support for double-word operations via some extension types like long long or __int64. Check if it's the case of your old compiler, if not then check if your compiler has an extension to get the carry flag, like __builtin_addc() or __builtin_add_overflow(). If all failed go to the next step

Now %0 = %1 + %2; is not an assembly instruction in any architecture I know, but it looks more readable than the traditional mnemonic syntax. However you don't even need to use assembly for multiword additions/subtractions like this. It's very simple to do directly in C since

  • basic operations in 2's complement don't depend on the signness of the type, and
  • if an overflow occurs then the result will be smaller than the operands (in unsigned) which we can use to get the carry bit

Regarding the implementation, since your old compiler has no 64-bit type, there's no need to declare the union, and you can't do that either because int64_t wasn't declared before. You can just access the whole thing as a struct.

#if COMPILER_VERSION <= SOME_VERSION

typedef UINT64_T {
    uint32_t h;
    uint32_t l;
} uint64_t;

uint64_t add(uint64_t x, uint64_t y)
{
    uint64_t z;
    z.l = x.l + y.l;               // add the low parts
    z.h = x.h + y.h + (z.l < x.l); // add the high parts and carry
    return z;
}

// ...

#else
uint64_t add(uint64_t x, uint64_t y)
{
    return x + y;
}
#endif

t = add(2, 3);

If you need a signed type then a small change is needed

typedef INT64_T {
    int32_t h;
    uint32_t l;
} int64_t;

The add/sub/mul functions are still the same as the unsigned version

A smart modern compiler will recognize the z.l < x.l pattern and turn into add/adc pair in architectures that have them, so there's no comparison and/or branch there. If not then unfortunately you still need to fall back to inline assembly

See also

phuclv
  • 27,258
  • 11
  • 104
  • 360
  • 1
    Take a look at the SHARC assembly. `%0 = %1 + %2;` is a assembly macro declaration – nowox Mar 03 '15 at 12:55
  • I don't think I can declare my own `int64_t` because the suffix `_t` is POSIX reserved. – nowox Mar 03 '15 at 12:57
  • I tried `(z.l < x.l)` but my compiler is not smart enough. It does a test. – nowox Mar 03 '15 at 13:08
  • Where did you read it is ok to declare a `int64_t` where the storage in not 64-bit ? – nowox Mar 03 '15 at 13:09
  • That code is buggy because it assumes unsigned comparisons (and would also need `-fwrapv` to be safe). Adding `0...0` and `1...1` (-1) will give the comparison (-1 < 0) for example, adding an extra 1 to the high part. Doing `((uint32_t)z.l < (uint32_t)x.l)` instead might be sufficient to make it work. Could prolly be cleaned up a bit. – Ulfalizer Mar 03 '15 at 13:49
  • @Ulfalizer fixed that. @coin Where did you read that 2 `uint32_t`s aren't 64 bits? And this is a custom type, not a standard one. If you don't want forward compatibility and portability you can choose another name – phuclv Mar 03 '15 at 14:15
  • If you need signed type, change to `int32_t h; uint32_t l;`. If you need the bit layout the same as `int64_t` in newer versions, endianess should be take into account – phuclv Mar 03 '15 at 14:22
0

Maybe I'm misunderstanding the limitations, but why not do something like the following?

#ifdef HAS_INT64_T

typedef int64_t int64;

inline int64 int64_sub(int64 x, int64 y) {
    return x - y;
}

...

#else /* !HAS_INT64_T */

typedef struct int64 { int32_t hi, lo; } int64;

inline int64 int64_sub(int64 x, int64 y) {
    *ASM*
}

...

#endif /* !HAS_INT64_T */

You'd have to be careful with endianess and the like, of course.

If you mean that your compiler support int64_ts but not certain operations on them, then you could either use a union as you suggested or define some simple conversion functions. I'd call the struct for the int32_t components parts or the like.

(It's also worth noting that having int32_t and int64_t overlap and writing to one and reading from the other is not strict-aliasing-safe, though it will tend to work in practice.)

Ulfalizer
  • 4,274
  • 1
  • 18
  • 27
  • Because with the legacy version of the compiler I don't have access to the `int64_t`. – nowox Mar 03 '15 at 12:36
  • @coin Wouldn't that also prevent you from putting an `int64_t` into a union though? If it's workable, it seems safer to not use `int64_t` at all on the old version of the compiler, and simply create an equivalent, like in this example. You'd compile with `-DHAS_INT64_T` for the new compiler, and without it for the old (or use whatever handy macros your compiler provides for determining the version). `struct`s can be passed and returned by value btw. – Ulfalizer Mar 03 '15 at 12:40