This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Efficiently combining bytes into a long int

Hi,

I'm having problems assembling a set of 4 bytes into a long int. My approach has been this:

    ulong temp;
    temp = (temp << SHIFT_BYTE) | getByte();
    temp = (temp << SHIFT_BYTE) | getByte();
    temp = (temp << SHIFT_BYTE) | getByte();
    temp = (temp << SHIFT_BYTE) | getByte();
    return temp;

But this results in a ton of calls to the long int library shift and logical or routines, which really aren't necessary. My quick and dirty solution has been to code the function in assembly, and just put the bytes in the appropriate "temp+n" bytes that make up temp. I've tried fooling around with unions, but haven't had any luck.

Thanks,
Bob

  • What about this:

    union
      {
      unsigned long lvar;
      unsigned char bytes[4];
      }
    v;
    
    v.bytes[0]=getByte();  //MSB
    v.bytes[1]=getByte();
    v.bytes[2]=getByte();
    v.bytes[3]=getByte();  //LSB
    
    return (v.lvar);
    

    This generates the following:

    0013 120000      R     LCALL   getByte
    0016 8F00        R     MOV     v,R7
    0018 120000      R     LCALL   getByte
    001B 8F00        R     MOV     v+01H,R7
    001D 120000      R     LCALL   getByte
    0020 8F00        R     MOV     v+02H,R7
    0022 120000      R     LCALL   getByte
    0025 8F00        R     MOV     v+03H,R7
    

    Jon

  • Thanks Jon. I guess I should have done a bit more research before posting. There's a knowledge base article that's almost identical to your reply.

    It's too bad the compiler won't use the registers instead. I have global register optimization turned on, and the only registers by "getByte()" function uses are A, C and R7. Ideally, I'd like it to produce the assembly code:

    ; *** v assigned to registers R4-R7
    LCALL   getByte
    MOV     R4,AR7
    LCALL   getByte
    MOV     R5,AR7
    LCALL   getByte
    MOV     R6,AR7
    LCALL   getByte
    RET
    

    I've also never been able to figure out why Keil and other compiler manufacturers don't allow you to pass and return values in the accumulator. Tasking returns byte size variables in the accumulator, but you can't pass arguments in the accumulator in any compiler I'm aware of. If that were the case, I'd have my "getByte()" function (which is implemented in assembly) return it's value in the accumulator. It would make this particular function signficantly faster and smaller. I wouldn't waste an instruction copying the value from the accumulator to R7 in the getByte() function, and the top level function could be realized by the following assembly:

    LCALL   getByte
    MOV     R4,A
    LCALL   getByte
    MOV     R5,A
    LCALL   getByte
    MOV     R6,A
    LCALL   getByte
    MOV     R7,A
    RET
    

    Of course, if I were truely obsessed with optimization (which I nearly am), I'd just code a "getLong()" function in assembly that has the "getByte" code inlined to save the call and return overhead.

    Thanks,
    Bob

  • "I guess I should have done a bit more research before posting"

    yes - it's been discussed on this forum many times before!

    This is all standard 'C' stuff - the only Keil-specific detail is the byte ordering within a long.

    Note that your original approach using shifts is independent of the underlying byte order and is, therefore, portable;
    The union approach relies upon the byte ordering and is, therefore, non-portable.

    With suitable conditional-compilation directives you can, of course, create a portable union implementation.

  • I did read back a few months worth of forum discussion before posting, but I didn't search the knowledge base. Thanks for responding anyway.

    There's more Keil-specific stuff than just the byte ordering in a long. The original purpose of my post was to find out which C code the compiler converted into efficient assembly. An intelligent enough compiler should have produced the same code for both the union approach and the shift approach, in which case, I would have opted for the portable approach. In my pre-purchase testing of the Keil compiler, I had found that it generally was very good at efficient assembly code for constant math such as dividing or multiplying by constant that is a power of 2 (shifting). I guess I was just expecting too much. The Keil tools do generally do a very good job.

    Thanks,
    Bob