13

I want to delve into the implementation of function "printf" in C on macOS. "printf" uses the <stdarg.h> header file. I open the <stdarg.h> file and find that va_list is just a macro.

So, I am really curious about how the __builtin_va_list is implemented? I know it is compiler-specific. Where can I find the definition of the __builtin_va_list? Should I download the source code of clang compiler?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
linyuwang
  • 321
  • 1
  • 3
  • 10
  • 1
    "Built-in" means that it is, indeed, implemented inside the compiler, using information that wouldn't be available to a normal function or macro. – Quentin Apr 09 '18 at 12:40
  • Those are just implementation details, the gist is it parses the format for tokens the. Pops the correct number of bytes off of the stack for each of those tokens... google variadic functions in C – Grady Player Apr 09 '18 at 12:41
  • Curious: on my Mac, I find `#ifndef _VA_LIST` —  `typedef __builtin_va_list va_list;` —  `#define _VA_LIST` (in file `/Library/Developer/CommandLineTools/usr/lib/clang/9.1.0/include/stdarg.h`) so `va_list` is a typedef, not a macro. However, that's a mere wording issue. AFAIK, you'd have to poke around the source of `clang` to find out how it is handled because it is a built-in type. – Jonathan Leffler Apr 09 '18 at 12:46
  • @JonathanLeffler... va_list has to be a type you pass it to va_list, `va_arg`, `va_end`... `__VA_ARGS__` is the macro that gets expanded in macro expansion. – Grady Player Apr 09 '18 at 13:31
  • @GradyPlayer: I'm not sure what relevance `__VA_ARGS__` has to this discussion. I don't mention it. It is a preprocessor feature and `va_list` is not primarily a preprocessor feature. I assume that the OP was claiming that there was `#define va_list __builtin_va_list` ("find that `va_list` is just a macro") whereas I find that it is a typedef. However, that is mostly a wording issue for the purposes of the question. – Jonathan Leffler Apr 09 '18 at 13:35
  • you are right, of course... I think I was confused mostly by: `"However, that's a mere wording issue"` – Grady Player Apr 09 '18 at 13:39

3 Answers3

8

So, I am really curious about how the __builtin_va_list is implemented?

__builtin_va_list is implemented inside the GCC compiler (or the Clang/LLVM one). So you should study the GCC compiler source code to understand details.

Look into gcc/builtins.def & gcc/builtins.c for more.

I am less familiar with Clang, which implements the same builtin.

But both GCC & Clang are open source or free software. They are complex beasts (several millions lines of code each), so you could need years of work to understand them.

Be aware that the ABI of your compiler matters. Look for example into X86 psABI for more details.

BTW, Grady Player commented:

Pops the correct number of bytes off of the stack for each of those tokens...

Unfortunately, today it is much more complex than that. On current processors and ABIs the calling conventions do use processor registers to pass some arguments (and the evil is in the details).

Should I download the source code of clang compiler?

Yes, and you also need to allocate several years of work to understand the details.

A few years ago, I did write some tutorial slides and links to external documentation regarding GCC implementation, see my GCC MELT documentation page (a bit rotten).

klutt
  • 30,332
  • 17
  • 55
  • 95
Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • 1
    the x86-64 abi does use registers but not for variadic functions. – Grady Player Apr 11 '18 at 16:45
  • Perhaps the carry bit might be used for them. I forgot details – Basile Starynkevitch Apr 11 '18 at 17:16
  • @GradyPlayer As far as I can tell, variadic functions are called with arguments in registers just like other functions; additionally, %al must be set to the number of vector registers used for float arguments. https://c9x.me/compile/doc/abi.html – Tau Dec 29 '22 at 20:33
2

In Clang 9, this is implemented in

clang\lib\AST\ASTContext.cpp

call graph:

getVaListTagDecl
=>getBuiltinVaListDecl
=>CreateVaListDecl
=>Create***BuiltinVaListDecl
for example:
=>CreateCharPtrBuiltinVaListDecl
=>CreateCharPtrNamedVaListDecl
=>buildImplicitTypedef

When there is __builtin_va_list in the preprocessed source, the compiler calls getVaListTagDecl to build a TypedefDecl AST node and insert it into the AST, the typedef doesn't exist in any source code, it is generated dynamically during build, as if there is such in the source:

typedef *** __builtin_va_list;
//for example
typedef char* __builtin_va_list;
jw_
  • 1,663
  • 18
  • 32
1

This answer, for clang, just show how I find implementation of a builtin function.

I'm interested in implementation of std::atomic<T>. If T is not a trivial type, clang use a lock to guard its atomicity. Look this answer first, I find a builtin function named __c11_atomic_store. The question is, how this builtin function implemented in clang?

Searching Builtin in clang codebase, find in clang/Basic/Builtins.def:

// Some of our atomics builtins are handled by AtomicExpr rather than
// as normal builtin CallExprs. This macro is used for such builtins.
#ifndef ATOMIC_BUILTIN
#define ATOMIC_BUILTIN(ID, TYPE, ATTRS) BUILTIN(ID, TYPE, ATTRS)
#endif

// C11 _Atomic operations for <stdatomic.h>.
ATOMIC_BUILTIN(__c11_atomic_init, "v.", "t")
ATOMIC_BUILTIN(__c11_atomic_load, "v.", "t")
ATOMIC_BUILTIN(__c11_atomic_store, "v.", "t")
ATOMIC_BUILTIN(__c11_atomic_exchange, "v.", "t")
...

The keyword are AtomicExpr and CallExpr. Then I check every caller of AtomicExpr's constructor, but doesn't find any useful information. So I guess, maybe in parse phase, if parser match an builtin function calling, it will construct an CallExpr to AST with builtin flag. In code generate phase, it will emit the implementation.

Check CodeGen, I find the answer in lib/CodeGen/CGBuiltin.cpp and CodeGen/CGAtomic.cpp.

You can check CodeGenFunction::EmitVAArg, I holp that would be useful for you.

merito
  • 465
  • 4
  • 15