user435739
user435739

Reputation:

using offsetof in assembly / C

I am trying to use offsetof in my assembly code

#define     offsetof(TYPE, MEMBER)   ((size_t) &((TYPE *)0)->MEMBER)

#define     DEFINE(sym, val)   asm volatile("\n->" #sym " %0 " #val : : "i" (val))

and say a structure is

struct mystruct {
int a;
int b;
int c;

}

In my assembly code I have to simply do SUB sp, sp, #-

How to declare the macro

Upvotes: 1

Views: 1449

Answers (2)

jorgbrown
jorgbrown

Reputation: 2051

You haven't said which processor you're generating code for... I assume it's a RISC machine because your subtract instruction has 3 operands, but you haven't said which one. I'm going to show you what this looks for x86 because I know the answer is correct.

I also assume by your use of asm volatile that you're using a compiler that follows gcc's standard.

Anyway, let's say you had a struct like this:

struct mystruct {
  unsigned char a;
  const char *b;
  int c;
};

int a_or_c(mystruct *str) {
  int a_val = str->a;
  return a_val & 1 ? str->c : (a_val >> 1);
}

The compiler doesn't generate particularly good code for that - it's seven instructions and we can do much better because we know that one instruction can do both the test for "& 1" and the shift right.

To specify a register, you use r for "register", as I'm sure you know. To specify an in/out register you use +r. To specify a constant like a struct offset, you use i for "immediate". Then the assembler syntax would be, for example:

int a_or_c_asm(mystruct *str) {
  int a_val = str->a;
  asm("shr $1,%0\n\t"
      "cmovcl %c2(%1), %0"
      : "+r"(a_val)
      : "r"(str), "i"(offsetof(mystruct, c))
      : "memory"      // tell the compiler that our code reads from memory
     );

  return a_val;
}

The trick here is you have to use %c2 rather than merely %2 to get the inline assembler to output a 2 rather than $2, because the x86 assembler uses a different syntax for offsets in addressing modes, than it does for immediate operands. A subtract instruction in x86 would look like this, for example:

  asm("subq %0, %%rsp"
      "... other instructions ..."
      : // no output operands
      : "i"(offsetof(mystruct, c)));
  // expands to   subq $16, %rsp   for x86-64

By your comment I assume you need ARM32 syntax. For that, your subtract instruction would look like this:

  asm("sub sp, %0"
      "... other instructions ..."
      : // no output operands
      : "i"(offsetof(mystruct, c)));
  // expands to  sub sp, #8    for ARM

(Obligatory Godbolt: compiles and assembles correctly for ARM.)
(Obligatory Godbolt: compiles and assembles correctly for x86.)

Please note that gcc assumes the stack pointer is the same at the end of any assembly block as it is at the start; the rest of your assembly block will have to include an instruction to restore sp to its original value.

For ARM32, my example would look like this - note the different syntax for using a register-plus-offset addressing mode in ARM32:

int a_or_c_asm(mystruct *str) {
  int a_val = str->a;
  asm("lsrs %0, #1\t\n"       // shift and set flags
      "ldrcs %0, [%1, %2]"    // load (predicated on Carry Set)
  : "+r"(a_val)
  : "r"(str), "i"(offsetof(mystruct, c))
  : "memory"  // we access memory that isn't a declared input.
  );
  return a_val;
}

Of course, this is all contrived to show how to pass a known constant value to gcc's inline asm syntax. A more common alternative would be to have a "m"(str->c) input operand that you use for ldrcs %0, %1., so that you don't have to use the offsetof macro.

Also, rather than using "memory", you could pass a dummy input operand to tell the compiler that the c field is an input, but you still actually form the addressing mode yourself; for more on this see How can I indicate that the memory *pointed* to by an inline ASM argument may be used?

Upvotes: 2

unwind
unwind

Reputation: 399823

I don't think this can work.

The offsetof operator is a compile-time thing, it's not evaluated by the preprocessor. That would be almost magical, since the preprocessor doesn't parse C, how could it compute structure offsets? Doing that requires a lot of machine-specific information, and is thus heavily into the compiler's area of responsibility. The preprocessor just massages text.

While typical documentation calls offsetof a macro, that doesn't mean it's evaluated by the preprocessor. It could just mean that it's a macro that evaluates into some compiler-specific magic.

For instance for gcc it can be defined like so:

#define offsetof(type, member)  __builtin_offsetof (type, member)

Here, __builtin_offsetof() is the magical compiler-specific function that really does the computation. Leaving a call to it where your assembler source needs a literal offset is of course not a solution.

Upvotes: 0

Related Questions