variable args implementation

Asked by xol

HI,
I would like to get some inside about va_ functions implementation.

Are they thread safe?
How to pass va_list to underlying function and how efficient is it?
I need to implement some assertion function that should call sprintf but for some reason it stuck in call to sprintf.

Thanks.

Question information

Language:
English Edit question
Status:
Answered
For:
GNU Arm Embedded Toolchain Edit question
Assignee:
No assignee Edit question
Last query:
Last reply:
Revision history for this message
Thomas Preud'homme (thomas-preudhomme) said :
#1

Hi,

On ARM at least (but I think most architecture work in a similar way) the va_list stores a pointer to where the argument of the variadic function are saved on the stack. The va_arg functions works by updating this pointer to point to the next argument on the stack. Therefore this is not thread safe. If several threads call va_arg in parallel the pointer could not be updated correctly as it's not updated in an atomic way. You can also not access it once the function returns since it's on the stack, so never save the va_list in a global variable.

I hope I answered your question correctly. If not, let me know and I'll improve my answer.

Best regards.

Revision history for this message
Joey Ye (jinyun-ye) said :
#2

I have different view. As the arguments and pointers are all in stack, they
are thread safe because supposely each threads got allocated their own
stacks.

Your implementation of assertion would be like this:
assert(const char * fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  char buf[LEN];
  sprintf(buf, fmt, ap);
}

On Thu, Apr 24, 2014 at 5:32 PM, Thomas Preud'homme <
<email address hidden>> wrote:

> Question #247363 on GCC ARM Embedded changed:
> https://answers.launchpad.net/gcc-arm-embedded/+question/247363
>
> Status: Open => Answered
>
> Thomas Preud'homme proposed the following answer:
> Hi,
>
> On ARM at least (but I think most architecture work in a similar way)
> the va_list stores a pointer to where the argument of the variadic
> function are saved on the stack. The va_arg functions works by updating
> this pointer to point to the next argument on the stack. Therefore this
> is not thread safe. If several threads call va_arg in parallel the
> pointer could not be updated correctly as it's not updated in an atomic
> way. You can also not access it once the function returns since it's on
> the stack, so never save the va_list in a global variable.
>
> I hope I answered your question correctly. If not, let me know and I'll
> improve my answer.
>
> Best regards.
>
> --
> You received this question notification because you are an answer
> contact for GCC ARM Embedded.
>

Revision history for this message
xol (dimax-main) said :
#3

What about arguments passed in registers?
Where can I see ABI documentation for the ARM GCC compiler?

On Thu, Apr 24, 2014 at 2:02 PM, Joey Ye <
<email address hidden>> wrote:

> Your question #247363 on GCC ARM Embedded changed:
> https://answers.launchpad.net/gcc-arm-embedded/+question/247363
>
> Joey Ye proposed the following answer:
> I have different view. As the arguments and pointers are all in stack, they
> are thread safe because supposely each threads got allocated their own
> stacks.
>
> Your implementation of assertion would be like this:
> assert(const char * fmt, ...)
> {
> va_list ap;
> va_start (ap, fmt);
> char buf[LEN];
> sprintf(buf, fmt, ap);
> }
>
>
> On Thu, Apr 24, 2014 at 5:32 PM, Thomas Preud'homme <
> <email address hidden>> wrote:
>
> > Question #247363 on GCC ARM Embedded changed:
> > https://answers.launchpad.net/gcc-arm-embedded/+question/247363
> >
> > Status: Open => Answered
> >
> > Thomas Preud'homme proposed the following answer:
> > Hi,
> >
> > On ARM at least (but I think most architecture work in a similar way)
> > the va_list stores a pointer to where the argument of the variadic
> > function are saved on the stack. The va_arg functions works by updating
> > this pointer to point to the next argument on the stack. Therefore this
> > is not thread safe. If several threads call va_arg in parallel the
> > pointer could not be updated correctly as it's not updated in an atomic
> > way. You can also not access it once the function returns since it's on
> > the stack, so never save the va_list in a global variable.
> >
> > I hope I answered your question correctly. If not, let me know and I'll
> > improve my answer.
> >
> > Best regards.
> >
> > --
> > You received this question notification because you are an answer
> > contact for GCC ARM Embedded.
> >
>
> --
> If this answers your question, please go to the following page to let us
> know that it is solved:
>
> https://answers.launchpad.net/gcc-arm-embedded/+question/247363/+confirm?answer_id=1
>
> If you still need help, you can reply to this email or go to the
> following page to enter your feedback:
> https://answers.launchpad.net/gcc-arm-embedded/+question/247363
>
> You received this question notification because you asked the question.
>

Revision history for this message
Thomas Preud'homme (thomas-preudhomme) said :
#4

If passed in registers, the prologue of the function will push the parameter on the stack so that va_arg works correctly. As to the ABI, it's not defined by gcc but by the document called AAPCS (ARM Architecture Procedure Call Standard) for ARM. See section 7.1.4 in the document. You can find the document at [1].

[1] http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042e/IHI0042E_aapcs.pdf

About my previous answer, Joey is right. I was imagining a complex scheme where 2 threads process argument of a single variadic function. You can consider these functions as thread safe. va_arg are not thread-safe for a given call to a variadic function but va_arg for different call (even if it's call to the same function by two different threads) are definitely safe.

Best regards.

Revision history for this message
xol (dimax-main) said :
#5

Thanks.

> If passed in registers, the prologue of the function will push the
> parameter on the stack so that va_arg works correctly.
Compiler must generates a special prolog for variadic functions in this
case? Is it you assumption or do you know it for sure? I did not see it in
mentioned document.

This is a trick I'm doing in my code.

trace.h:
void trace( char* str, ... );

trace.c:
#pragma weak trace = _trace
void trace( char* str, uint32_t arg1, uint32_t arg2, uint32_t arg3 )
{
...
}

I never call foo with more then 3 arguments after str. According to ABI
first 4 natural parameters are passed in r1,r2,r3,r4.
So this way I get efficient code that I require in trace function.
Do you see any problem with it?

On Fri, Apr 25, 2014 at 4:32 AM, Thomas Preud'homme <
<email address hidden>> wrote:

> Your question #247363 on GCC ARM Embedded changed:
> https://answers.launchpad.net/gcc-arm-embedded/+question/247363
>
> Status: Open => Answered
>
> Thomas Preud'homme proposed the following answer:
> If passed in registers, the prologue of the function will push the
> parameter on the stack so that va_arg works correctly. As to the ABI,
> it's not defined by gcc but by the document called AAPCS (ARM
> Architecture Procedure Call Standard) for ARM. See section 7.1.4 in the
> document. You can find the document at [1].
>
> [1]
>
> http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042e/IHI0042E_aapcs.pdf
>
> About my previous answer, Joey is right. I was imagining a complex
> scheme where 2 threads process argument of a single variadic function.
> You can consider these functions as thread safe. va_arg are not thread-
> safe for a given call to a variadic function but va_arg for different
> call (even if it's call to the same function by two different threads)
> are definitely safe.
>
> Best regards.
>
> --
> If this answers your question, please go to the following page to let us
> know that it is solved:
>
> https://answers.launchpad.net/gcc-arm-embedded/+question/247363/+confirm?answer_id=3
>
> If you still need help, you can reply to this email or go to the
> following page to enter your feedback:
> https://answers.launchpad.net/gcc-arm-embedded/+question/247363
>
> You received this question notification because you asked the question.
>

Revision history for this message
Thomas Preud'homme (thomas-preudhomme) said :
#6

Hi xol,

Compiler indeed generates a special prologue for variadic function. It's not an assumption of mine, you can check it out by yourself in function thumb1_expand_prologue in gcc/config/arm/arm.c in gcc sources: near the beginning of the function you'll find "if (cfun->machine->uses_anonymous_args)" and in the if "thumb1_emit_multi_reg_push". uses_anonymous_args denotes a variadic function.

Interesting trick you did there. So if you don't use more than 32-bit wide parameter it should indeed work. So I suppose no matter what is in str, you always passes it with all arguments (arg1, arg2 and arg3) down to sprintf, am I right? And sprintf does not return when you do this. I don't see any obvious reason that could cause this problem right now, I'll continue to think about it.

By the way, why not make trace a variadic function and just passes the va_list argument to vsprintf?

Revision history for this message
xol (dimax-main) said :
#7

Hi,
In my trace function I do not actually print trace message but store it in
buffer (that is later swapped to flash from some low priority idle taks).
I have no traces with more then 3 arguments (if I need I just call trace
twice but this is a rare case). Trace is stored in memory as pointer to
format string and 3 arguments (does not matter if they are in use or not).
Later I can print it on request.
I wanted trace function to be as small and as fast as possible. And this
trick does the job very well.

On Mon, Apr 28, 2014 at 5:32 AM, Thomas Preud'homme <
<email address hidden>> wrote:

> Your question #247363 on GCC ARM Embedded changed:
> https://answers.launchpad.net/gcc-arm-embedded/+question/247363
>
> Thomas Preud'homme posted a new comment:
> Hi xol,
>
> Compiler indeed generates a special prologue for variadic function. It's
> not an assumption of mine, you can check it out by yourself in function
> thumb1_expand_prologue in gcc/config/arm/arm.c in gcc sources: near the
> beginning of the function you'll find "if
> (cfun->machine->uses_anonymous_args)" and in the if
> "thumb1_emit_multi_reg_push". uses_anonymous_args denotes a variadic
> function.
>
> Interesting trick you did there. So if you don't use more than 32-bit
> wide parameter it should indeed work. So I suppose no matter what is in
> str, you always passes it with all arguments (arg1, arg2 and arg3) down
> to sprintf, am I right? And sprintf does not return when you do this. I
> don't see any obvious reason that could cause this problem right now,
> I'll continue to think about it.
>
> By the way, why not make trace a variadic function and just passes the
> va_list argument to vsprintf?
>
> --
> You received this question notification because you asked the question.
>

Revision history for this message
Joey Ye (jinyun-ye) said :
#8

Change status to answered

Can you help with this problem?

Provide an answer of your own, or ask xol for more information if necessary.

To post a message you must log in.