Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions bootstrap/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7381,7 +7381,13 @@ impl Codegen {
return;
}

self.write(&format!("const {}", node.name));
// A module-level `var` is MUTABLE. The parser records that in
// `extra_mutable`, and the Zig local path already branches on it
// (`var` vs `const`); this path never did, so a spec that assigns to a
// module-level name emitted `const` and Zig refused the assignment.
// gen-verilog got this right all along -- it emits a `reg`.
let kw = if node.extra_mutable { "var" } else { "const" };
self.write(&format!("{} {}", kw, node.name));

if !node.extra_type.is_empty() {
self.write(&format!(": {}", Self::t27_array_type_to_zig(&node.extra_type)));
Expand Down Expand Up @@ -17304,18 +17310,36 @@ impl CCodegen {
}

// Regular constant with expression value
// A module-level `var` is MUTABLE, and neither spelling below can hold
// an assignment: `#define counter 0` turns `counter = counter + 1`
// into `0 = (0 + 1)`, which is not C at all, and `static const` is
// rejected by the compiler. The parser records mutability in
// `extra_mutable`; this path never read it. gen-verilog has always
// emitted a `reg` here.
let c_type = if !node.extra_type.is_empty() {
Self::type_to_c(&node.extra_type).to_string()
} else {
"int".to_string()
};
if node.extra_mutable {
self.write(&format!("static {} {} = ", c_type, node.name));
if let Some(child) = node.children.first() {
self.gen_c_expr(child);
} else if !node.value.is_empty() {
self.write(&node.value);
} else {
self.write("0");
}
self.write_line(";");
return;
}
if !node.children.is_empty() {
let child = &node.children[0];
// Simple literal → #define
if child.kind == NodeKind::ExprLiteral {
self.write_line(&format!("#define {} {}", node.name, child.value));
} else {
// Complex expression → static const
let c_type = if !node.extra_type.is_empty() {
Self::type_to_c(&node.extra_type).to_string()
} else {
"int".to_string()
};
self.write(&format!("static const {} {} = ", c_type, node.name));
self.gen_c_expr(child);
self.write_line(";");
Expand Down
2 changes: 1 addition & 1 deletion bootstrap/stage0/FROZEN_HASH
Original file line number Diff line number Diff line change
@@ -1 +1 @@
eb54f8a9b83f5f1a7d0d2a4b216deb5f99f4d110471a6c724072df422b89fd2a
b4ed38b1d79afb67e4c2547042227175adc74b5a2c0597b60829c1968ae40661
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# NOW -- A module-level var was a constant in three backends (2026-08-28)

## A module-level var was a constant in three backends (Refs #2161)

- Refs #2161. The parser records mutability in extra_mutable and both backends already branch on it for function LOCALS -- Zig var/const, Rust let mut/let. The module-level paths never read it, so `var counter: u32 = 0` came out as `#define counter 0` in C, which turns `counter = counter + 1` into `0 = (0 + 1)` -- not C at all -- and as `const` in Zig, which refuses the assignment. gen-verilog emitted a reg and was right all along
- Measured over the 42 specs whose C output changes: compile errors 537 -> 277, specs with clean C 0 -> 19. specs/fpga/bpsk.t27, the BPSK modem merged in #1250, goes 7 errors to 0. A probe spec compiles and RUNS: two calls incrementing a module-level counter print 1 2
- gen-rust deliberately NOT fixed: Rust has no safe module-level mutable. static mut needs unsafe at every access, AtomicU32 changes the API to load/store, thread_local! changes the semantics -- three different generated interfaces, which is a datapath decision and not an oversight. Measured first that all 43 such specs already emit non-compiling Rust for other reasons, so nothing regresses by leaving it
- Mid-task the worktree stopped being a git repository -- a parallel session pruned it while I held uncommitted edits. The FILES survived; recovery was to copy compiler.rs aside, prune, re-add the worktree on the same branch, and restore. Worth noting because the first symptom was a scan that printed "0 changed": git ls-files had failed and the empty selection read as a clean result, which is the empty-scan lie one more time
Loading