Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How does include works in C and clang #29

Open
chsasank opened this issue Jun 13, 2024 · 4 comments
Open

How does include works in C and clang #29

chsasank opened this issue Jun 13, 2024 · 4 comments

Comments

@chsasank
Copy link
Owner

Want to see how includes work in Clang and how we can directly include C libraries in c-lisp.

@GlowingScrewdriver
Copy link
Contributor

Typically, C libraries are distributed as shared object binaries and header files. The shared objects contain compiled functions, and the headers contain forward-declarations for the functions in the shared objects.

In any conformant C compiler, the #include directive is replaced by the contents of the included file, and then compilation is carried out as usual. The linkage to the actual function bodies is handled by the linker, and neither the front-end nor the IR has anything to do with it.

This is supported by the fact that C allows multiple declarations (prototype alone) for the same function, but only one definition (prototype + body).

@GlowingScrewdriver
Copy link
Contributor

For example, consider the following program, pgm.c:

#include "pgm.h"

int main () {
    fn1();
}

with the following header file, pgm.h:

int fn1 () {
    return 10;
}

This compiles to the exact same code (can be verified with a diff) as a single file with everything inline:

int fn1 () {
    return 10;
}

int main () {
    fn1();
}

And the emitted LLVM is:

; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @fn1() #0 {
  ret i32 10
}

; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @fn() #0 {
  %1 = call i32 @fn1()
  ret void
}

@GlowingScrewdriver
Copy link
Contributor

Further, the only opportunity for type-checking lies with the frontend. The following code compiles with no error, not even a warning:

//#include <stdio.h>
int putchar ();

int main () {
    putchar();
}

However, if the putchar declaration is removed and stdio.h included, we get an error since putchar expects one argument:

#include <stdio.h>
//int putchar ();

int main () {
    putchar();
}
now.c:5:13: error: too few arguments to function call, single argument '__c' was not specified
    putchar();

Note that in the first example, putchar is actually being linked to the definition in the standard C library; if the function didn't exist in the shared object, an error would be thrown by the linker:

//#include <stdio.h>
int putcharr ();

int main () {
    putcharr();
}
/usr/bin/ld: /tmp/now-9b7b65.o: in function `main':
now.c:(.text+0x7): undefined reference to `putcharr'

This supports 2 facts:

  • Library headers only need to contain forward declarations, provided compiled shared objects containing function definitions are accessible to the linker.
  • For dynamically linked libraries, type-checking must be done by the frontend.

@chsasank
Copy link
Owner Author

We need to parse C headers ourselves for linking then! We can use SWIG for parsing or we can use clang json output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants