Live data from Hacker News

Show HN: My C compiler compiled itself

github.com

101–110 of 130 posts

Re: Show HN: My C compiler compiled itself

#101
post #26

Earlier quoted context omitted.

It is so trivial, it takes you 10 minutes to learn. Less even, here is an attempt: foo.o: foo.c clang -c -o foo.o foo.c This builds a file called foo.o, if foo.c is newer than foo.o. Imagine that there is an identical bar.o as well, that builds it from bar.c. fooexec: foo.o bar.o clang -o fooexec foo.o bar.o This links together foo.o and bar.o into a file called fooexec, if at least one of foo.o or bar.o is newer (wh…

This is an excellent explanation of make! The only thing I will add is the slightly more abstract thought that make is meant to build files out of other files based on timestamps of the source files. And the target files can themselves become source files, which means that you can build dependency graphs. Lastly, it has “caching” built in: if bar.o is newer than bar.c, don’t rebuild it. If it doesn’t exist or is olde…

>Lastly, it has “caching” built in:

A nitpick, but I don't think that should really be called caching, although it kind of is, in a looser sense. It's just comparing the file timestamps of the .o and .c files, and building the .o from the .c, if the .c is newer, or if the .o doesn't exist.

Re: Show HN: My C compiler compiled itself

#102

Earlier quoted context omitted.

> Also, I'm curious - did you find yourself having to constrain your use of C in order to make sure that the compiler could compile itself? Or does it implement everything you would use naturally anyways? That would be the "bootstrapping" process. Nearly a half-century ago I took a compiler lab class where we were given a working, but slightly lame, compiler, and were tasked with adding new, less lame, language featu…

I guess the full bootstrapping process would start with writing a basic compiler in assembly?

Back in university, we took the steps of:

- Breadboarding some CPU components

- Breadboarding a very, very minimal CPU. More like a simple Adding + Multiplication machine with 4 registers or so, with a few premade components.

- Eventually moving up to microcoding a simulated CPU

- Eventually writing binary code to control the microcoded CPU

- At that point I kinda cheated and wrote my own assembler because I got sick of checking so many bits.

- This project then stopped at an assembly level.

- But then we implemented our own ML-variant interpreter and later on ML-variant compiler in OCaml

- And later on we had that ML-Compiler compile itself and extended it from there.

Pick your poison where to start. Just be aware that breadboarding stuff becomes very... messy very quickly[1]. And I do include Hardware in this, because you needed simpler CPUs to design more complex CPUs.

In practice, you want to pick the best combination of a familiar language that is as high level as it can be. But back in the day, that would've been assembly, binary. In the case of C, it went from BCPL, in which a compiler for B was written, in which a compiler for New B (NB) was written, which then turned into C.

1: https://www.youtube.com/watch?v=l7rce6IQDWs&t=78s

Re: Show HN: My C compiler compiled itself

#103
post #55

Earlier quoted context omitted.

thanks man! yes that makes sense. unfortunately 30cc is not able to compile that syntax, and will probably give type-checker errors when passing a void* to pointer of another type. but will implement it sometime soon!

I disagree with that advice, FWIW -- (void *) is the cause of a lot of bugs in C programs, and much stricter type checking was often the norm for compilers like CodeWarrior on platforms like classic Mac OS, where (a) if you got something wrong you'd corrupt app memory at best and the filesystem at worst and (b) many developers were used to Pascal, which was stricter. (Moving to GCC on Mac OS X and unexpectedly gettin…

It doesn't make it any safer because the cast is unchecked. It could be argued it's discouraged[1] for a reason that doesn't really apply to modern C versions where implicit-int isn't allowed anymore.

But while the cast is a matter of style (or C++ compatibility), sizeof(*ret) is definitely superior to sizeof(thing_t). The reason is that people sometimes change the type of ret but forget the change the size of the allocation, especially if ret is assigned to on a different line it's declared.

[1]: https://c-faq.com/malloc/mallocnocast.html

Re: Show HN: My C compiler compiled itself

#105
post #26

Earlier quoted context omitted.

It is so trivial, it takes you 10 minutes to learn. Less even, here is an attempt: foo.o: foo.c clang -c -o foo.o foo.c This builds a file called foo.o, if foo.c is newer than foo.o. Imagine that there is an identical bar.o as well, that builds it from bar.c. fooexec: foo.o bar.o clang -o fooexec foo.o bar.o This links together foo.o and bar.o into a file called fooexec, if at least one of foo.o or bar.o is newer (wh…

This is an amazingly good 2-minute introduction to makefiles. It would have been better without the last two sentences (and most of your comments on the topic). --- Something skipped over by your introduction (and most guides to makefiles) which very quickly either makes makefiles less short or much more complicated is header files. C files often depend on one or more header files that aren't system headers (this C c…

I did mention that in other comments, including the C preprocessor helping. make is completely language agnostic, so it cannot “guess” header dependencies. In make, you get the dependencies you specify (whether manual or generated), that’s what I wanted to show.

Re: Show HN: My C compiler compiled itself

#106
post #55

Earlier quoted context omitted.

thanks man! yes that makes sense. unfortunately 30cc is not able to compile that syntax, and will probably give type-checker errors when passing a void* to pointer of another type. but will implement it sometime soon!

If you're implementing C23 compatibility, you could do auto p = (thing_t *)malloc(sizeof(thing_t)); and you get both DRY and avoid (void *) casting.

> and you get both DRY and avoid (void *) casting.

While that line of code is DRY, it doesn't avoid casting "void *" to "thing_t *".

Re: Show HN: My C compiler compiled itself

#107
post #50
post #42

Earlier quoted context omitted.

> Do $@ and $ And yet, I still don't actually know what `$@` and `$ The problem is that "simple" is not the same as "intuitive" or "discoverable". Yes, I could probably put in the effort to go and find out these things, but why would you expect someone to be motivated to do that by someone telling you that it's "trivial" and it "pains" them as they explain things in a way that doesn't actually clarify things in a way…

One might ask how you learned C (or python, or anything else) in the first place, if you can't be bothered to learn what the very simple $ The rest is just... of course you can reimplement make, or a subset of it, in python, python is turing complete after all. But why? make isn't that arcane. I'm sorry it uses the weirdly looking $@ and $ This is trivial in the sense that it's a part of any build system worth its sa…

I don't disagree that the concepts are simple; I'm arguing that the way they're conveyed in makefiles optimizes for the wrong thing by prioritizing tenseness over conveying useful information. The issue isn't that they "look weird", but that they give context that can be used to build additional concepts off of. A sibling reply to my comment mentions that `$My point is that concepts being simple doesn't mean that the choice of how they're represented doesn't matter. When people aren't bothering to learn something you think seems "simple", it's a mistake to immediately assume that it's due to laziness or incompetence rather than first trying to understand their motivations. Maybe they are just lazy or incompetent, but it's also possible that there's legitimate confusion around something that wouldn't occur to you without seeing it the way a beginner sees it, and there's room for improving how things are documented or taught.

There's a pattern of thinking that seems pretty common in our industry where we focus so much on the technical details of a system that the actual human experience of using it gets overlooked. My problem with this line of thinking is that value a tool is only realized when people actually use it. As a hypothetical, imagine that some new technical issue becomes commonplace and two different tools get written to solve the problem; the first tool solves the problem perfectly and as quickly as possible every time but doesn't get used by anyone, and the second tool does only an adequate job of solving the problem and requires a bit of manual additional work from the user to fix things, and that tool gets used by almost everyone. I'd argue that the first tool is not actually successful in any meaningful way because it doesn't actually help anyone with the problem that it was designed to solve.

To be clear, I'm not trying to say that no one uses makefiles or that they don't provide any real world value. I'm trying to say that if you're actually trying to get more people to use makefiles because they solve problems that everyone has better than what they're currently doing to try to solve them, the most effective way would be to look at it from the perspective of the people who aren't already using it and figuring out what's stopping them. When you feel strongly that it's the best way to do things, anybody who isn't doing things that way must see things differently, and the best way to try to change that starts by figuring out what that difference is.

If you don't actually care about trying to convince people and mostly just want to vent, then this advice won't be relevant, but if your goal is actually to try to change the way people do things, the approach you use to explain things is going to matter just as much as your knowledge about the things you're explaining. Just like how a brilliant professor who does groundbreaking research might be terrible at teaching an intro course to undergrads, it's possible to be an expert at something but not be effective at teaching it, and I think failing to appreciate the difference often ends up leading to a lot of frustration that would otherwise be avoidable.

Re: Show HN: My C compiler compiled itself

#108
post #57

Earlier quoted context omitted.

This kind of condescending advice doesn't help anybody. Redundant casts, like redundant braces, white space, and a plethora of similar choices are stylistic more than anything else. If you really feel the need to give unsolicited coding style feedback maybe at least spend more than a few seconds looking at the code before you do so. Otherwise it's just rude.

Redundant casts are really, really good at hiding errors... (And few people know that sizeof is a unary operator so the parentheses are rarely necessary. Dropping them reduces the visual noise => improves readability.)

> sizeof is a unary operator so the parentheses are rarely necessary

I thought parentheses where still required for types, and are optional for variables. Even if that's not so, I still use them for types and avoid them for variables just to add one more cue about what's being sized.

Re: Show HN: My C compiler compiled itself

#109
post #92

Out of curiosity: would ./30cc_gcc (30cc complied by gcc-hosted 30cc) and ./30cc (30cc complied by self-hosted 30cc) be identical binary files, if 30cc was operating as expected?

the asms should be identical, so the binaries should be identical too, unless the linker is undeterministic

Re: Show HN: My C compiler compiled itself

#110
post #69
post #26

Earlier quoted context omitted.

It is so trivial, it takes you 10 minutes to learn. Less even, here is an attempt: foo.o: foo.c clang -c -o foo.o foo.c This builds a file called foo.o, if foo.c is newer than foo.o. Imagine that there is an identical bar.o as well, that builds it from bar.c. fooexec: foo.o bar.o clang -o fooexec foo.o bar.o This links together foo.o and bar.o into a file called fooexec, if at least one of foo.o or bar.o is newer (wh…

I've written a good amount of makefiles (and even one rather complex one.. before replacing it with a thing written in an actually sane language, bootstrapped by a stripped down version of the makefile), and I still mix up $@ $ or whatever else my brain comes up with. And this is coming from someone deep into array languages, which have entire sets of unicode glyphs. For how often one typically writes/looks at build…

Fair. But as said, in my experience, they remain useful and simple for a pretty long time. I usually always start out with a simple Makefile. Only sometimes do I start with or eventually switch to something else because of the projects’ needs, but for most stuff (even stuff where the project itself is complex, i.e. embedded kernel stuff where different units are pasted together and whatnot), relatively simple Makefiles do their job pretty well.

I also have a lot of phony targets. It really enjoy just being able to type:

   make restore kernel reset upload
To bring a device into a sane state, build the kernel, reset the device, and upload the built firmware.
Post reply on HN