Hacker News
Show HN: Micron – Oberon with the Power of C
Micron is not my first langage design. I first built Oberon+, a modernized and unified Oberon, in order to under stand, by implementing it and by porting real systems to it, what Wirth’s design actually gives and where it fails. The systems ported to Oberon+ included the 2013 Project Oberon System and different versions of Oberon System 3. I then explored the opposite direction with Luon, a higher-level language for managed application progamming. Micron is the synthesis of this and my 30 years experience with C/C++ in systems programming.
The goal of Micron is to use the same language for boot code, device drivers, an operating system kernel, and ordinary applications, without requiring a garbage collector or routing machine access through a separate, unchecked system dialect (such as Oberon's SYSTEM.ADR/GET/PUT which operates on LONGINT). The language supports explicit memory layouts, signed and unsigned integer types, pointer operations, and direct calls to C without wrappers. One of its central design choices is language levels. Each module declars the level and thus the feature it needs, ranging from code that requires neither stack nor heap to garbage-collected objects. A module can import only modules at the same or a lower level. For example, a kernel module declared at level 1 cannot accidentally acquire a dependency on heap allocation through an import. Other features include generic modules, structural interfaces, and compile-time procedures. Much of what would require a preprocessor in C is expressed through typed language features.
There is a working compiler, interpreter, and IDE, along with a free textbook (http://software.rochus-keller.ch/micron-textbook.pdf). The book explains both how the language works and the reasons behind its design. Its systems-programming section walks through a port of Wirth and Gutknecht’s Project Oberon operating system to Micron, cross-compiled and linked into a single executabl that runs on an emulated RISC-V machine.
The examples also include ports of a Smalltalk-80 VM, Scheme 9, and the original P4 Pascal compiler. The compiler and interpreter implement the language specification; native backends and tooling are still work in progress, and the current native code generation prioritises simplicity over optimisation.
For more information see https://github.com/rochus-keller/Micron/blob/master/Readme.m....
andsoitis
|next
[-]
The examples subdirectory (https://github.com/rochus-keller/Micron/tree/master/examples) contains:
- a Smalltalk-80 implementation,
- the original 1976 P4 Pascal compiler,
- the Scheme 9 virtual machine,
- the Project Oberon System running on the host,
- and the Micron System described in the textbook, the migrated Project Oberon System natively running on RISC-V.*
berkeleynerd
|next
|previous
[-]
Rochus
|root
|parent
[-]
andsoitis
|next
|previous
[-]
I've always been super interested in Oberon (due to my Pascal / Object Pascal / Delphi heritage) and even tried things like A2 (https://en.wikipedia.org/wiki/A2_(operating_system)) but never fully got into the boat.
Micron makes me excited to try your latest incarnation.
Rochus
|root
|parent
[-]
HexDecOctBin
|next
|previous
[-]
Rochus
|root
|parent
|next
[-]
srean
|root
|parent
|previous
[-]
When compilation is that fast, it leads to a different kind of a conversation with the computer. Not necessarily always a better conversation but definitely different.
wahern
|root
|parent
|next
[-]
Such sophistication is also necessary for variable-length arrays, anyhow, especially in loops, so it's presumably no coincidence C99 got inline declarations, compound literals, and VLAs all at the same time.
C hasn't yet fully embraced and leveraged VLAs or the dependent typing features they brought along. That gave them a bad reputation (in crippled form they're all cost for little benefit), unfortunately causing languages like Rust to avoid them. If Rust (and LLVM) had fully embraced them from the beginning, alot of sharp edges wouldn't need to exist in Rust, especially around initialization and statically vs non-statically sized types. Premature optimization (in this case regarding baseline object semantics) is the root of all evil.
srean
|root
|parent
[-]
Once you are happy with the code one can use the optimizing compiler set at a preferred level of optimization.
As an aside, some simple Pascal compilers would not wait for building the full AST before emitting object code.
wahern
|root
|parent
[-]
This is why C++ evolves the way it does, and Rust as well. A little more semantic complexity deeper in the language and compiler architecture would unlock tremendously more potential. But because of how the history of C, Pascal, etc has shaped assumptions about primitive typing and object semantics, compilers are invariably architected in a way that higher-order language semantics have to be built in layers above the core compiler and by extension through a meta layer in these more modern languages. Variable length arrays are the prime example; they can only be modeled in C++ and Rust as library interfaces. To implement them as more fundamental primitive types you need a kind of dependent typing ability in the core semantics and compiler implementation. That then necessarily unlocks alot more possibilities for richer language semantics that would be impossible or handicapped otherwise. Alot of the nominally more sophisticated features of C++ and Rust are stilted for just this reason. Rust especially because while far more ambitious than C++, LLVM in many ways is fundamentally built to model what a 1990s C compiler would require, and a few but crucial choices in Rust were motivated by limitations in LLVM.
There are similar problems with how control flow is modeled and reified, but properly resolving them is hindered not only by compiler architecture, but also by OS and hardware abstractions, which like compilers largely coevolved to satisfy the needs of C. Imagine a world where both hardware and OS ubiquitously presumed stack management necessarily required a special register to delimit stack boundaries, independent of VM page size. In such a world we could have space optimal stackful coroutines, and they wouldn't need to be as stilted as in C++ or Rust. That alone would be amazing, but it would trivially open the door to every "bare metal" language having something akin to Goroutines for free, and with maybe some additional work possibly in a language independent manner.
srean
|root
|parent
[-]
Exactly why I prefer simplicity. Exactly what I don't want my language to become. Single pass compilable does not precluded multiple passes for optimization.
D compiles very fast and has a cleaner design than C++. The latter has been trying hard to be a D wannabe for years. D's is not single pass compiled though.
BTW Modula-2 had coroutines. If I remember correctly they were stackless ones.
wahern
|root
|parent
[-]
My point was part of C++'s complexity stems from the inability to push some of that complexity down the stack. A small amount of added complexity deeper in the broader ecosystem could eliminate much more complexity higher up the stack. Imagine if compilers and ABIs were architected to support efficient passing of discriminants, then option and union types could naturally become first-class and without any anxiety over performance or interoperability, rather than trying to implement them with more complex language facilities.
Languages evolve in a path dependent manner. If it's much more practical (in terms of time and effort) to implement a concept at a certain layer, that's where it'll be implemented. But unless your language is interpreted or emulated and your userbase values purity much greater than performance, the shape of the concept will reflect the capabilities and limitations of where and how it was implemented. The extra complexity of all those little accomodations can add up quickly, both in terms of implementation and language semantics.
It also works both ways. Moving a little complexity up the stack can substantially reduce overall complexity. But which direction is optimal varies. The problem is moving complexity down the stack isn't viewed as a realistic option these days, especially if you must take into consideration the viability of implementing it in core GCC or LLVM.
inkyoto
|root
|parent
|previous
[-]
Disabling optimisation, can also boost the compilation speed although it won't be as fast.
If, however, the comparison is against more modern languages, such as Rust, Swift, Haskell, ML etc, the actual compute effort goes into type inference, not the multi-stage compilation process, as proving type correctness is a computationally hard problem.
Teyott
|next
|previous
[-]
chasil
|next
|previous
[-]
Seymour Cray also designed the CDC 6600, which had a much more immediate impact on IBM et al.
jsrcout
|next
|previous
[-]
neverartful
|next
|previous
[-]
nigel_bree
|next
|previous
[-]
I'm so grateful that we have people like you and Nils M. Holm keeping lots of these old ideas about design fresh, you both inspire me to keep pushing myself in my own projects.
Panzerschrek
|next
|previous
[-]
Rochus
|root
|parent
[-]
Panzerschrek
|root
|parent
[-]
to what extent is it safer than C? Does it eliminate safety issues common to C like out of bounds memory access, use-after-free, double-free, race conditions? Or maybe some other kind of safety issue present in C isn't possible in Micron?
> unsafe parts are explicit
Does it have unsafe blocks or something similar?