3 ms·
Profiles are resolved before code generation, not via conditionals. Each top-level item (function, block, impl, import) can be annotated with a profile (userlan
by JhonPork 9mo ago
Profiles are resolved before code generation, not via conditionals.
Each top-level item (function, block, impl, import) can be annotated with a profile (userland, kernel, baremetal). During parsing, everything is collected into the AST as usual.
During IR lowering, the compiler is invoked with exactly one active profile. At that point:
Nodes whose profile does not match are not lowered to IR at all
They are dropped during IR validation, not guarded or compiled
The resulting IR literally has no trace of the other profiles
So this is not like #ifdef or runtime flags. The non-selected code never reaches:
borrow checking
optimization
codegen
linking
From LLVM’s point of view, it’s as if the other code never existed.
That’s why there’s no runtime overhead: no branches, no checks, no dead code elimination required. The IR is profile-pure by construction.
This also lets the compiler enforce different rules per profile:
userland: heap allowed, panics allowed
kernel: no heap, no panic, stricter aliasing
baremetal: raw pointers, UB allowed
Invalid combinations simply fail IR validation.
Happy to clarify further once the repo is public.
- platinumrad 9mo agoCode that is #ifdef'd out doesn't even make it into the AST so traces of it aren't going to be found in the IR either. I think I'm missing something really basic. The idea of three different subsets/dialects is interesting, but I would expect all three to be usable at the same time, like how unsafe blocks can be used in the performance-critical sections of a larger Rust program.
- JhonPork 9mo agoGood question the distinction is intentional. Falcon’s profiles are not meant to be “dialects you mix freely” like Rust’s unsafe blocks. They represent different execution contracts, not different safety levels inside the same runtime. In Rust, unsafe still lives inside one program with: - one allocator - one runtime model - one ABI - one set of linking assumptions In Falcon, each profile defines a different world: - userland assumes a runtime: heap, panics, rich abstractions - kernel assumes no runtime: no heap, no panics, stricter aliasing - baremetal assumes no OS at all: raw pointers, direct memory, UB allowed Mixing them at runtime would force the strongest constraints everywhere, or require dynamic checks — which defeats the goal. Instead, Falcon treats profiles as compile-time execution modes, not scoped escape hatches. The reason non-selected profiles are erased before IR is so: - LLVM never sees incompatible assumptions - no dead-code elimination or guards are needed - profile-specific rules can be enforced structurally, not heuristically You still share logic by compiling the same source multiple times: falcon build app.fc --profile=userland falcon build app.fc --profile=kernel falcon build app.fc --profile=baremetal Or: falcon build app.fc --profiles=all This produces multiple artifacts from one codebase, each valid by construction for its environment. So the comparison isn’t “why not Rust unsafe blocks”, but: “Should fundamentally different execution contracts coexist at runtime, or be selected at compile time?” Falcon chooses the latter to avoid hidden coupling and runtime cost.
- platinumrad 9mo agoWould it be accurate to think of it as a better way of doing the following? ``` int add(int a, int b) { #ifndef FREESTANDING printf("adding %d and %d\n", a, b); # return a + b; } ``` > You still share logic by compiling the same source multiple times What's the benefit of this approach over sharing code via libraries?
- JhonPork 9mo agoIt’s similar at a surface level, but the key difference is when the choice is made and what is being selected. #ifdef and libraries still live inside a single execution contract — the compiler parses and reasons about everything, and constraints are mostly conventional. In Falcon, the profile selects a different execution contract before IR exists. Non-matching code is not typechecked or lowered at all, and different rules (heap, panics, aliasing, UB) are enforced structurally. Libraries share implementation; profiles share intent. The same source is treated as a specification from which multiple valid-by-construction artifacts are derived, rather than a single program patched with conditionals.