We are running a survey to help us improve the experience for all of our members. If you see the survey appear, please take the time to tell us about your experience if you can.
I often use MAKEWORD and MAKEDWORD macros, which are defined to construct longer types from multiple shorter ones. e.g. -
#define MAKEWORD( h, l ) \ ( ( ( WORD )( h ) << 8 ) | ( BYTE )( l ) )
which constructs a word from two bytes.
When used with constants, the calculations are all carried out at compile time and their efficiency doesn't matter. When used with data variables, code is generated the compiler's optimisation comes into play. The 8 bit shifts can be resolved to simple mov operations.
Keil does a good job optimising the MAKEWORD macro above, but does a lot less well with a similar macro to construct an unsigned long from two words or four bytes.
The following function works much better, by doing nothing! The trick is to define the word parameters so that they are passed in the same registers as a ULONG is returned, in the correct byte order. The function can the simply return, since the return value is the parameters.
This requires that the low word is passed as the first parameter, which means it is loaded to [R6|R7]. The high word is passed as the second parameter, in [R4|R5]. A ULONG is returned in [R4|R5|R6|R7], so all is as required on entry.
The line -
$reguse _make_dword ()
is important. It informs the compiler that no registers are disturbed. Without this, the calling function may generate extra code to preserve registers.
Code saved depends on too many variables to give an exact figure, but I get back around 20 bytes for a single use within a 'C' module. Your mileage may vary.
The code should also be much faster, although I haven't tested this.
A51?MAKE_DWORD segment code rseg A51?MAKE_DWORD ;/ DWORD make_dword( WORD lo, WORD hi ); ;/____________________________________________________________________________ $reguse _make_dword () public _make_dword _make_dword: ret
Simply define a union type that contains a 16/32 bit integer and 2/4 unsigned chars.
typedef union { unsigned int val16; struct { unsigned char msb; unsigned char lsb; } byte; } u16_union; ... u16_union testvar; unsigned char msb, lsb; ... testvar.byte.msb = msb; testvar.byte.lsb = lsb;
Doesn't require assembly, and the compiler will not perform any shifts.
Yes, I tried that and it works, but it's not as efficient, because the union must reside in external memory. Well it doesn't have to actually, but Keil seems to treat it that way.
What I want is code that simply loads the registers and my suggested technique achieves that.
There may be a way to write a macro which achieves a similar outcome. I tried several obvious variants, hoping that they would fall into the purview of the compiler's optimisation schemes, all to no avail.
If you examine at the code produced for the following function -
DWORD make_dword( WORD lo, WORD hi ) { return ( ( DWORD )hi << 16 ) | lo; }
You will find that C51 goes to a lot of trouble to leave registers R4-R7 with the entry values. This was the simplest implementation I could think of to demonstrate that C51 doesn't optimise 32 bit shift and or operations.
BTW, I was in a rush when I posted an forgot to provide some definitions -
typedef unsigned char BYTE; typedef unsigned short WORD; typedef unsigned long DWORD; ; Signature for A51 function. extern DWORD make_dword( WORD lo, WORD hi );
I use plenty of these unions and all of them are in internal memory.
Sorry, I should have said non-register storage. You can use (internal) DATA memory, if you have enough available (I don't).
If the union is declared as in your example, I have never seen C51 assign it to a register and manipulate the registers directly. I'm not saying definitively that it can't happen, but I've never seen it.
If the union is declared as in your example, I have never seen C51 assign it to a register and manipulate the registers directly.
Considering that internal-to-internal memory moves are, in most cases, just as fast as internal-to-register moves on a '51, doing these assignments by moving the data to registers, manipulating it and moving it back to memory would result in larger and slower code than directly moving the bytes around in memory.
Unless you want to have really tight control about the register contents, but in that case you should be using assembly anyway.
I think you just said what I just said - if you have enough DATA memory, you can declare your union there and it won't be much slower (though more code will probably be generated). I don't have enough DATA memory.
Not necessarily. I've put this in a library and wrapped it with a compiler aware macro and it's easily used by non-asm people on my team.
I think you just said what I just said - if you have enough DATA memory, you can declare your union there and it won't be much slower (though more code will probably be generated).
The union method is probably going to be faster and result in smaller code.
I don't have enough DATA memory.
What do you do with the result of your MAKEWORD() macro if you don't have enough space to store it ? (you are storing it in a variable in memory, right ? Otherwise, using the macro would not make much sense, as ints and longs are constructs of HLLs like C)
The union solution does not need any additional memory.
unsigned char a, b; unsigned int c; a = 0xBE; b = 0xEF; ((unsigned char *) &c))[0] = a; // MSB ((unsigned char *) &c))[1] = b; // LSB
As I said above, I tried the union method, and my technique definitely generates less code if the union is declared in XDATA. I haven't try declaring it in DATA memory, because that's just not tennable in my application.
Most frequently I'm using the result directly (i.e. it's an intermediate, which doesn't need to be stored), or I'm passing it to another function. Writing it anywhere is redundant and writing to XDATA is expensive.
This will work, but except in simple cases, where c is assigned to a register, this will cause non-register variables to be updated. If these are in XDATA, that's quite a bit of code.
Ok, I see how you're going to apply your solution. With a bit of function pointer casting, it can even be done in pure C:
void do_nothing(void) { return; } unsigned long do_stuff(unsigned long in) { return(~in); } int main(void) { unsigned int a, b; unsigned long d; a = 0xDEAD; b = 0xBEEF; d = do_stuff(((unsigned long (*) (unsigned int, unsigned int)) &do_nothing) (b, a)); }
I've compiled several ways of getting the resulting variable to a place in xdata, and the union came out on top (fastest/shortest way is to have both the ints and the long as unions, and do a byte-wise transfer with incrementing addresses. If the order is reversed or the transfer is done wordwise, longer code will be generated).
I like the function cast idea!
The hardware I'm working with uses bank switching, but has no common XDATA memory - each bank has separate physical XDATA memory. This means that all inter-bank calls must use register parameters only. So I have a lot of code which combines parameters into a longer type.
Example - some of the following functions's parameters will be copied to XDATA.
extern void rectangle( BYTE x0, BYTE y0, BYTE x1, BYTE y1 );
But this is ok, since DWORD will be passed in regs.
extern void i_rectangle( DWORD x0_y0_x1_y1 ); #define rectangle( x0, y0, x1, y1 ) \ i_rectangle( make_dword( MAKEWORD( x0, y0 ), MAKEWORD( x1, y1 ) ) )
With the union implementation I had something like -
extern void i_rectangle( DWORD x0_y0_x1_y1 ); #define rectangle( x0, y0, x1, y1 ) \ { \ union \ { \ struct \ { \ BYTE b3; \ BYTE b2; \ BYTE b1; \ BYTE b0; \ } \ as_byte; \ DWORD as_dword; \ } \ params; \ \ params.as_byte.b3 = x0; \ params.as_byte.b2 = y0; \ params.as_byte.b1 = x1; \ params.as_byte.b0 = y1; \ \ i_rectangle( params.as_dword ); \ }
This resulted in less code than my original MAKEDWORD() macro. But the make_dword "function" is even more efficient.
It lies somewhere on the fine line between genius and insanity.
> With a bit of function pointer casting, it can even be > done in pure C:
... well, that depends on what you call "pure". Calling a function through a pointer casted to any other signature than its own causes undefined behaviour. I.e. you've just built a nasty surprise generator.
Calling a function through a pointer casted to any other signature than its own causes undefined behaviour.
Undefined by the C standard. Well defined by the compiler manufacturer. And the original solution of the OP already depends on the Keil C51 calling conventions.