Live data from Hacker News

9cc: A Small C Compiler

github.com

41–50 of 70 posts

Re: 9cc: A Small C Compiler

#41
post #23

Regarding your Makefile; you should still pass CFLAGS through to the compiler when linking, not only LDFLAGS. Suppose CFLAGS contains -m32 (supported by an x86-64-targetted GCC to do 32 bit). You compile the .o files with that, but then link without it, which fails trying to make a 64 bit executable out of 32 bit .o's. Some crazy distros pass a --sysroot in CFLAGS; if you don't have that, your build finds the wrong l…

Where would one go to find more of this conventional Makefile wisdom? I've had so many issues trying to use make the "right" way (flexible, clean, terse, etc.). I feel like one of the best ways to acquire this wisdom is to post a project with lots of mistakes and let people tear it apart.

I bought what seemed to be overstock or maybe it was just being remaindered in a 50%-off Uni Bookshop sale about 20 years ago. It's a tiny (80 page) little book called "Managing Projects with make" by Steve Talbot and printed by O'Reilly and Associates.

Most of what I know about 'make' I learned from this book.

Re: 9cc: A Small C Compiler

#42
post #33

https://github.com/rui314/9cc/blob/882e4b2dd8/main.c#L7 int main(int argc, char **argv) { ... Vector *tokens = tokenize(path, true); Program *prog = parse(tokens); sema(prog); gen_ir(prog); if (dump_ir1) dump_ir(prog->funcs); optimize(prog); liveness(prog); alloc_regs(prog); if (dump_ir2) dump_ir(prog->funcs); gen_x86(prog); return 0; } This is wonderful.

And also wonderfully devoid of error-handling, too. It's the most common way for beautiful-looking C code to look beautiful.

A compiler is in the happy position where there is little point in continuing to run after encountering an error, so it can bail right out with exit(2) after reporting the error to the user. This means that the contract on parse(), for example, can be that if it returns, it has succeeded.

Re: 9cc: A Small C Compiler

#43
post #29

Earlier quoted context omitted.

Quote from 6.7.6.3.14 of the C++11 spec ( http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf ): > An identifier list declares only the identifiers of the parameters of the function. An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters. The empty list in a function declarator that is not part of a definition of that function specifies…

That is of course the C11 (draft) spec, not C++. Interesting find there. The wording is also in the C99 draft; it is not new. It is in fact saying that the empty list in a definition is a special case and does declare that the function takes no parameters. To "specify" here can be understood to mean as inserting information about type into the declaration scope; that which a declaration does. So that's a bit of a bug…

My understanding is that, unless you have a function prototype, you will lose type info about the parameters:

"The empty list in a function declarator that is not part of a definition of that function specifies that no information about the number or types of the parameters is supplied."

i.e. the most important is the end of that sentence, so GCC behavior should be right.

Re: 9cc: A Small C Compiler

#44
post #33

https://github.com/rui314/9cc/blob/882e4b2dd8/main.c#L7 int main(int argc, char **argv) { ... Vector *tokens = tokenize(path, true); Program *prog = parse(tokens); sema(prog); gen_ir(prog); if (dump_ir1) dump_ir(prog->funcs); optimize(prog); liveness(prog); alloc_regs(prog); if (dump_ir2) dump_ir(prog->funcs); gen_x86(prog); return 0; } This is wonderful.

And also wonderfully devoid of error-handling, too. It's the most common way for beautiful-looking C code to look beautiful.

Unless the programmer uses the null object pattern in which case everything is hunky dory. For example, an empty "Program" would probably do the job.

Re: 9cc: A Small C Compiler

#45
post #42
post #33

Earlier quoted context omitted.

And also wonderfully devoid of error-handling, too. It's the most common way for beautiful-looking C code to look beautiful.

A compiler is in the happy position where there is little point in continuing to run after encountering an error, so it can bail right out with exit(2) after reporting the error to the user. This means that the contract on parse(), for example, can be that if it returns, it has succeeded.

Except of course LLVM has proven the value in not assuming this pattern & building your compiler as a library of which the executable entrypoint is but one frontend.

Re: 9cc: A Small C Compiler

#46
post #2

It lists a goal as "compiling real-world programs such as the linux kernel" The last time I investigated the linux kernel had so many gcc-isms that it was probably true that if you could compile the linux kernel, you could probably compile any program targeted to gcc.

Other "alternative" compilers like tcc have pulled it off, supposedly without patching the kernel, so it is doable.

Re: 9cc: A Small C Compiler

#47

>no memory management is the memory management policy in 9cc. We allocate memory using malloc() but never call free(). >I know that people find the policy odd, but this is actually a reasonable design choice for short-lived programs such as compilers. I'm strongly disagree at this point. Memory management is important even for short-lived programs. It would bring burden to the OS if you invoke this kind of "short-liv…

Sometimes this is true, sometimes not. It depends on how many blocks of memory you allocate and for what purposes, I think. For short-lived programs it can make sense that some things are never freed (they will be automatically freed when the program terminates), but still there might be some things which should be managed anyways. It also depends on the program, and on other things.

Re: 9cc: A Small C Compiler

#48

Earlier quoted context omitted.

> Memory management is important even for short-lived programs. It would bring burden to the OS if you invoke this kind of "short-lived, memory-management-free" programs multiple times. All the memory is freed when the process exits. Why does it matter if you run the program multiple times?

It will put more pressure on the allocator when running, doing that a lot will likely have some kind of cumulative consequence down the line. I know from experience [0] that even reusing allocated memory rather than bouncing it back to malloc can have dramatic effects. [0] https://gitlab.com/sifoo/snigl/blob/master/src/snigl/pool.h

Malloc doesn't interact with the kernel at all. The kernel sees pages, not the data structures that malloc manages. The kernel doesn't even know whether you free()'d the memory by the time the process exits.

There is exactly zero difference from the operating system's perspective between freeing and not freeing the memory before program termination (except that one might have a higher peak memory usage).

The classic implementations of several standard Unix utilities deliberately never deallocated memory for performance reasons. If there was a problem with this practice, I would expect Bell Labs--of all places--to know not to do it.

Re: 9cc: A Small C Compiler

#50
post #42

Earlier quoted context omitted.

A compiler is in the happy position where there is little point in continuing to run after encountering an error, so it can bail right out with exit(2) after reporting the error to the user. This means that the contract on parse(), for example, can be that if it returns, it has succeeded.

Except of course LLVM has proven the value in not assuming this pattern & building your compiler as a library of which the executable entrypoint is but one frontend.

Exceptions are the exit() of libraries. Or if you're using plain C, setjmp/longjmp might be a good idea, depending on what you're doing.
Post reply on HN