I've always loved learning new things. So, at the start of 2024, I decided to tackle my seemingly endless list of MOOCs, books, and study materials, with a particular focus on the OSSU curriculum.
For those unfamiliar, OSSU (Open Source Society University) is a community-driven initiative that organizes free online university courses into structured, college-style curricula. Their most popular track is Computer Science, with additional offerings in Data Science, Mathematics, and Bioinformatics.
A few years ago, I turned to OSSU to deepen my understanding of the mathematical foundations of Computer Science, complementing my previously applied studies. These days, I use "OSSU" as a synonym for all my continued learning in Computer Science, even if some of it falls outside the official curriculum.
This post reflects on what I've accomplished in 2024 and what I'm currently pursuing, along with personal reviews and lessons learned.
Nand2Tetris is by far one of the best online courses I've ever taken!
In my university studies, I took courses like "Computer Architecture", "Assembly Languages" and "Object-Oriented Programming" but never fully grasped how they were interconnected. I found myself grappling with questions like:
That's where this course comes to the rescue. The best way to truly understand how computers work is to build one from scratch!
This course guides you through 12 chapters and hands-on projects to construct a basic hardware platform and a modern software hierarchy entirely from the ground up. Starting with just a NAND logic gate, you'll build an 16-bit computer complete with RAM, ROM, and a CPU composed of registers, an ALU, and a program counter.
On this hardware platform, you'll ascend the software hierarchy step by step: creating an assembler, designing an intermediate stack-based representation, and finally, building a full-fledged compiler for a high-level language reminiscent of C# or Java. This compiler allows you to write and run complex applications—yes, even a game like Tetris—on the very computer you've constructed. It's an enlightening, hands-on journey that demystifies how all the layers of computing come together.
Shimon Schocken and Noam Nisan, professors at the Hebrew University of Jerusalem, are exceptional instructors. Unlike many screencast-based courses, Nand2Tetris places the teachers on camera, making the learning experience more engaging and personal. While it may seem like a minor detail, actually seeing the instructors preserves non-verbal communication and conveys their infectious passion and genuine enthusiasm for the subject. This approach fosters a stronger connection with the material, transforming the experience into more than just listening to a disembodied voice over static black-and-white PowerPoint slides.
With the course's motto, “You build everything yourself”, the idea of constructing an entire computer may seem like a momentous task. Nothing can be glossed over or skipped; you must build every component from the ground up, leaving no room for mystery. Yet, what's remarkable is that I never felt overwhelmed or lost—Shimon and Noam provide just the right amount of guidance to help you understand and complete each challenge.
Your progress is further reinforced by the emulators they've developed, which allow you to test your creations at every step. Whether it's the Hardware Emulator for verifying your logic chips, the CPU Emulator for checking your assembler, or the VM Emulator for testing your compiler and I/O libraries, these tools ensure your progress is measurable and satisfying.
Even though you will probably never need to build a computer from scratch in your career, understanding how the code you write translates into hardware operations is invaluable. This knowledge not only makes you a better programmer but one who writes more efficient, optimized, and purposeful code.
After mastering the foundational knowledge provided by Nand2Tetris, I was eager to dive deeper into the complexities of programming languages by taking Stanford's MOOC on Compilers. While Nand2Tetris introduces you to building a simple compiler as part of its broader curriculum, it doesn't go into full detail on all aspects of compiler design. The high-level language used in Nand2Tetris is intentionally simple—avoiding complexities like operator precedence, error detection, and allows utilizing an easy parsing algorithm.
After completing Nand2Tetris, I was left with several unanswered questions:
Stanford's compilers course promises to address all these questions and more.
Unlike Nand2Tetris, which emphasizes "You build everything yourself", this course shifts focus to exploring the mathematical theory behind compilers and examining real-world, industry-standard tools.
This course introduced me to Regular Languages and their implementation using DFAs and NFAs (Deterministic/Non-Deterministic Finite Automata). It also delved into Context-Free Grammars (CFGs), a concept touched on in Nand2Tetris but never explicitly named or explained. Here, we explored the mathematical foundations of CFGs, including concepts like ambiguity, left- and right-recursion, and how to choose the best parsing algorithm (Top-Down or Bottom-Up) for different grammars. The course also introduced tools like Flex and Bison for automated Tokenizer and Parser generation, making grammar parsing less of a hassle.
While not all compilers rely on an explicit intermediate language, the course made it clear that many compilers use intermediate representations internally. This includes popular forms like three-address code (TAC) and industry standards such as LLVM IR, which utilize register-based representations rather than a stack-based approach. This intermediate representation simplifies program optimizations like constant folding and dead-code elemination by introducing a theoretical machine with an infinite number of registers. Does this mean that stacks are completely absent in register-based systems? Not quite. While the intermediate representations like TAC and LLVM IR primarily rely on registers, it's still common for the code generation phase to use a stack to handle function calls and returns. So, while stacks aren't explicitly visible in TAC or LLVM IR, stacks still play a role in generating the final target machine code, albeit with a more limited function.
A fascinating part of the course is its focus on code generation. Unlike stack-based systems that rely on push and pop operations to manipulate data, register-based systems directly manipulate values in registers. The challenge, however, is that the code often uses more registers than are available on the target machine. This is where Register Interference Graphs and Graph Coloring come into play. These techniques show that, as long as two variables are not "live" at the same time, they can share a register, effectively allowing for more 'virtual' registers than physical ones. However, even after applying this optimization, there may still not be enough registers. In this case, some variables must be "spilled" to memory. The tricky part lies in determining which variables could be spilled with the least performence impact.
Finally, this course provides an in-depth treatment of garbage collection, exploring algorithms like Mark-and-Sweep, Stop-and-Copy, and Reference Counting—essential techniques for managing memory in modern compilers.
It's worth noting that everything that made the Nand2Tetris course so engaging in terms of screencasting is absent here. Instead, we're back to an instructor who primarily lectures as a disembodied voice over black-and-white PowerPoint slides. That's not to say the instructor isn't knowledgeable—on the contrary, he demonstrates an impressive command of the subject. However, the course's format makes it less engaging and significantly more reliant on your determination and self-discipline. While Nand2Tetris felt like an immersive and hands-on journey, Stanford's compilers course is very much a traditional university-level course.
This course is heavily theory-focused. There are optional assignments that guide you through building a compiler for the COOL (Classroom Object-Oriented Language), but these assignments are not the course's main focus. To earn a passing grade, you must complete quizzes, a midterm, and a final exam, all of which are challenging and demand considerable effort and time to solve. Unlike Coursera's unlimited attempts, this course allows only three attempts per quiz question and a single attempt for the midterm and final exam, adding an extra layer of pressure.
Taking this course was a truly enriching experience, but it wasn't without its challenges. Many concepts required significant effort to fully grasp, and I often had to spend extra time letting them sink in. Two topics, in particular, stand out in my memory as especially challenging yet rewarding to understand.
The first was the rigorous definition of NFAs with epsilon-arrows. These epsilon transitions, which allow the automaton to move between states without consuming any input, initially felt abstract and unintuitive. However, their inclusion is vital for understanding how NFAs can represent more complex patterns and be systematically converted into DFAs.
The second was the Shift-Reduce parsing algorithm used in bottom-up parsing. This algorithm involves a delicate interplay between shifting tokens onto a stack and reducing them into higher-level grammar constructs. Understanding when to shift, when to reduce, and how conflicts like shift/reduce or reduce/reduce are resolved required a lot of mental effort, but it provided an invaluable foundation for understanding bottom-up parsers in general.
The course's rough outline is a follows:
Now, it's perfectly fine to introduce code generation before optimization— in fact, it's preferable for this course since the project requires building a compiler without the need for optimization.
The issue arises because the code generation module introduces a stack-machine system, while the optimization module introduces a register-based system. Unfortunately, this distinction is not clearly explained. As a result, the final module on register allocation & garbage collection caused confusion for me, as it feels like a second code generation module for the register-based system. However, you're constantly trying to connect it to the earlier stack-based code generation module. A clearer explanation of these two different systems would have been helpful, and perhaps the final module could even be renamed "Code Generation 2" to make this distinction clearer.
"Essential for understanding the modern world. With a sweeping narrative... Chris Miller tells how our chip-powered world has been shaped by constant battles - among innovators and technologies, among companies, amoung countries, and now, of critical importance, in the great power competition between the United States and China." - Daniel Yergin Author of The Prize, The Quest, and The New Map
After completing Nand2Tetris, I gained a solid technical understanding of how computers work, right down to the level of transistors. However, what I lacked was an understanding of the origin story behind these magnificent devices and the intricate global supply chain that powers their creation. Like many, I knew about giants such as Intel, AMD, and Nvidia. What I didn't fully appreciate was just how vast and interconnected the chip fabrication supply chain is, and the critical role a few key players—like TSMC and ASML—play in this ecosystem.
Chris Miller's Chip War fills that gap spectacularly, weaving a rich narrative that spans the technological, financial, and geopolitical history of semiconductors. The book explains how computing power depends fundamentally on a few choke points—tools, chemicals, and software—produced by a small number of companies. Miller highlights just how concentrated this industry is: Taiwan's TSMC provides 37% of the world's new computing power annually, two South Korean companies dominate memory chip production, and ASML builds 100% of the extreme ultraviolet lithography machines required for cutting-edge chips.
"The disruptions of the pandemic provide just a glimpse of what a single well-placed earthquake could do to the global economy." - Chris Miller
Perhaps it's my Dutch roots, but one of the most captivating parts of the book is Miller's discussion of extreme ultraviolet (EUV) lithography. While he doesn't delve deeply into the science, his explanation of the immense cost and the breakthroughs required to create ever-smaller transistors is captivating.
Beyond the technical marvels, Chip War serves as an eye-opener to the fragile geopolitical ecosystem surrounding the chip market and its importance in shaping global power dynamics.
"Ghost in the Wires reads like a contemporary über-geeky thriller... For those interested in computer history, Ghost in the Wires is a nostalgia trip to the quaint old days before hacking (and hackers) turned so malicious and financially motivated." - J.D. Biersdorfer, New York Times Book Review
I stumbled upon Ghost in the Wires in the second-hand section of my local bookstore, not knowing what an extraordinary story awaited me. Written by Kevin Mitnick himself, this memoir chronicles the life of one of the most infamous hackers in U.S. history. Before picking up this book, I hadn't heard of Mitnick, and for those who might also be unfamiliar, Kevin Mitnick (August 6, 1963 - July 16, 2023) was an American computer security consultant, author, and convicted hacker. His high-profile arrest in 1995 and the subsequent five years he spent in prison made headlines around the world.
While I had some knowledge of early computing, Mitnick's vivid technical descriptions and gripping narrative hooked me from the start. What truly set this book apart, though, was his mastery of social engineering. Long before the term was widely recognized, Mitnick perfected the art of manipulating trust, using charm, confidence, and insider jargon to extract anything he needed from people. His exploits are as thrilling as they are eye-opening. If you're intrigued by the early days of computing or fascinated by the human element of hacking, this book is an absolute must-read.
Reading this book is such a monumental task that I've decided to break it down chapter by chapter. I'll dive deep into each section, focus intensely on the material, and then set it aside for a few months while I work on other projects before returning to it.
Currently, I'm working through Chapter 4 on Number Theory, and it's been a game-changer for my understanding of divisors and prime numbers. While I was already familiar with concepts like Euclid's algorithm, studying them in depth on my own has given me a much clearer understanding. There's no way I could summarize all the insights from this chapter in just one paragraph—it truly deserves a full blog post, which I plan to write later this year.
However, there's one mental struggle I've been grappling with in number theory that I think I've finally overcome, and I'd like to share that here. I'd always heard definitions like “5 is prime because it can only be divided by 5 and 1,” but this never quite clicked for me. The idea of a number being divisible only by itself and 1 felt abstract, and I couldn't form a clear mental image of it. Then, I shifted my thinking from division to multiplication. Instead of "5 is prime because it can only be divided by 5 and 1", I began thinking, "5 is prime because it can only be written as 5 * 1 using positive integers." This simple change made divisors and prime numbers far more intuitive for me, as I started seeing numbers as being made up of building blocks.
And yes, I understand why mathematicians don't simply replace the word "divisors" with the word "factor", as it can be ambiguous. For example, 3 can be written as "√3 * √3" or "1/3 * 9", with both √3 and 1/3 being factors. However, calling them “divisors” instead of factors feels disconnected from the concept. In hindsight, calling them something like "natural factors" might have helped me grasp the idea earlier—making the connection to them being a factor, and then a subset of them being "prime factors".
This shift in perspective has allowed me to approach number theory with greater intuition, moving beyond rote definitions to a deeper understanding of why these properties hold true.