At least for Linux/Unix, there’s very little you can actually achieve without at least some of the libc linked into your own system. Unless you actually write your own system call interface functions - which is dependent on the processor architecture (using
int X [or
syscall] in x86, perhaps
trap Y in 68K,
swi on ARM, etc) - values for X may vary depending on OS too, and ABI (different registers/calling convention may exist for the same architecture) - and of course, it will be yet different on Windows, either way. Using standard library functions in the C library will give a reasonable semblance of working on most platforms for which there is a C compiler.
Sure, you can reduce the size of the application itself by a fair bit [when statically linked - my typical Pascal test executables are in the tens of kilobytes, because they dynamically link to libc], but I very much doubt you’ll gain much overall time from using something other than libc - but you’ll end up doing a lot of work.
Do, by all means, investigate this, and if you find some significant savings, report back. But in general, I’d say, you probably end up doing the same thing that libc does already. And most languages need some start-up code, so calling
_start at that point isn’t such a terrible thing. I use the C compiler as a linker, so I just call my language’s startup
main and be done with it. But you’ll probably find yourself implementing something like this:
either way, even if it’s not precisely called
main, and isn’t written in C.
Yes, it’s necessary to call
_start or equivalen, if you want to
stdout, to initialize those - but sooner or later, you’ll end up wanting to buffer I/O a little bit beyond calling the OS read/write operations (
write is pretty rubbish for implementing
printf or Pascal’s
writeln - because you get far too many user-kernel-user transitions, making it slow), and most likely, you don’t want to call
mmap to allocate memory directly either. So, the runtime library for language X will have to have some runtime library that sets things up in accordance to how it’s file and memory handling.
The key point here is “how much do you gain, and how much effort is it”. If it’s a very small gain, and a large amount of effort, is there something else that makes it meaningful to do? So far, I see no such case.
By all means, if you want to implement your own language, and write your own runtime to go with that, LLVM will allow that. But you will need to implement some reasonably efficient handling of file-I/O and memory allocation for each OS you want. And a reason why you won’t want to do ONE library that allows this for many languages is that different languages have different semantics for the DETAILS of how you do certain things (error handling could be throwing exceptions, returning error codes, aborting the execution - or some combination thereof based on compiler or language pragma, etc, etc).