Bindings now signal mutability the way Rust/Swift do, instead of `let` meaning
"local" and `var` meaning "module-level":
- `let x = e` -> immutable binding; a later `x = …` is a compile error
(`cannot assign to immutable 'x' … use var`).
- `var x = e` -> mutable binding, at local OR module scope (position decides
scope; the keyword decides mutability).
- `const` -> unchanged (compile-time).
Immutability is of the *binding*, not the object: `let n = new Node; n.kind = 1`
is fine (mutation through the reference); only rebinding `n` is rejected. The
check lives in emit_assign — a direct `name =` whose target is a `let` local
(loc_mut == 0) errors; field/index targets and `var`/param/loop bindings are
unaffected.
Delivered as three reseeds so the self-hosting compiler never had to compile
source its own rules would reject:
A) add `var` as a local statement + per-local mutability tracking (loc_mut),
no enforcement;
B) migrate every reassigned `let` -> `var` across the compiler, runtime and
examples (337 declarations), driven by a per-function, string/comment-aware
scan (binding targets only, never `x.f =` / `x[i] =`);
C) turn on the check. bootstrap-cfree (compiler vs its own source) and every
golden build (which splices the runtime) then proved zero reassigned `let`
was missed anywhere.
Also folded in: removed leftover debug instrumentation in block() (a `cur=` /
print_int(777…) trace on the separator-error path) and fixed parse.ludic's stale
header comment (no more `struct`). Reseeded (21711 lines); C-free fixpoint holds;
goldens identical; 17/17; vocab + doc-fences clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
142 lines
6.8 KiB
Text
142 lines
6.8 KiB
Text
# emit_intrin.ludic — the low-level intrinsics the self-host source uses, each
|
|
# lowered to the same libc/inline IR the C backend emits. Returns a Val via
|
|
# g_intrin_val and sets g_intrin_ok when `name` was an intrinsic.
|
|
|
|
var g_intrin_ok: bool = false
|
|
|
|
# is `name` a low-level intrinsic? A pure name check, so it can gate dispatch
|
|
# without evaluating arguments (which could clobber shared state).
|
|
fn is_intrinsic(name: ptr) -> bool {
|
|
if streq(name,"ptr_null") or streq(name,"mem_alloc") or streq(name,"mem_realloc") { return true }
|
|
if streq(name,"peek8") or streq(name,"poke8") or streq(name,"ptr_is_null") { return true }
|
|
if streq(name,"file_open") or streq(name,"file_read") or streq(name,"file_write") { return true }
|
|
if streq(name,"file_seek") or streq(name,"file_tell") or streq(name,"file_close") { return true }
|
|
if streq(name,"print_str") or streq(name,"print_int") { return true }
|
|
if streq(name,"os_argc") or streq(name,"os_arg") or streq(name,"os_exit") or streq(name,"file_stderr") { return true }
|
|
if streq(name,"os_system") or streq(name,"os_getenv") { return true }
|
|
if streq(name,"shl") or streq(name,"shr") or streq(name,"band") or streq(name,"bor") or streq(name,"bxor") or streq(name,"bnot") { return true }
|
|
if streq(name,"peek32") or streq(name,"poke32") { return true }
|
|
return false
|
|
}
|
|
# emit " <r> = <rest>\n" and return r
|
|
fn emit_bind(rest: ptr) -> ptr { let r = nreg(); emit(" "); emit(r); emit(" = "); emit(rest); emit("\n"); return r }
|
|
|
|
fn arg_code(e: Node, i: int) -> ptr { let v = emit_expr(e.kids[i]); return v.code }
|
|
|
|
fn emit_intrinsic(name: ptr, e: Node) -> Val {
|
|
g_intrin_ok = true
|
|
if streq(name, "ptr_null") { return val("null", "ptr") }
|
|
if streq(name, "mem_alloc") {
|
|
let n = arg_code(e, 0)
|
|
let w = emit_bind(sconcat("zext i32 ", sconcat(n, " to i64")))
|
|
return val(emit_bind(sconcat("call ptr @malloc(i64 ", sconcat(w, ")"))), "ptr")
|
|
}
|
|
if streq(name, "mem_realloc") {
|
|
let p = arg_code(e, 0); let n = arg_code(e, 1)
|
|
let w = emit_bind(sconcat("zext i32 ", sconcat(n, " to i64")))
|
|
return val(emit_bind(sconcat("call ptr @realloc(ptr ", sconcat(p, sconcat(", i64 ", sconcat(w, ")"))))), "ptr")
|
|
}
|
|
if streq(name, "peek8") {
|
|
let p = arg_code(e, 0); let i = arg_code(e, 1)
|
|
let a = emit_gep_i8(p, i)
|
|
let b = emit_bind(sconcat("load i8, ptr ", a))
|
|
return val(emit_bind(sconcat("zext i8 ", sconcat(b, " to i32"))), "int")
|
|
}
|
|
if streq(name, "poke8") {
|
|
let p = arg_code(e, 0); let i = arg_code(e, 1); let v = arg_code(e, 2)
|
|
let a = emit_gep_i8(p, i)
|
|
let t = emit_bind(sconcat("trunc i32 ", sconcat(v, " to i8")))
|
|
emit(" store i8 "); emit(t); emit(", ptr "); emit(a); emit("\n")
|
|
return val("0", "void")
|
|
}
|
|
if streq(name, "ptr_is_null") {
|
|
let p = arg_code(e, 0)
|
|
let c = emit_bind(sconcat("icmp eq ptr ", sconcat(p, ", null")))
|
|
return val(emit_bind(sconcat("zext i1 ", sconcat(c, " to i32"))), "bool")
|
|
}
|
|
if streq(name, "file_open") {
|
|
let p = arg_code(e, 0); let m = arg_code(e, 1)
|
|
return val(emit_bind(sconcat("call ptr @fopen(ptr ", sconcat(p, sconcat(", ptr ", sconcat(m, ")"))))), "ptr")
|
|
}
|
|
if streq(name, "file_read") or streq(name, "file_write") {
|
|
let f = arg_code(e, 0); let b = arg_code(e, 1); let n = arg_code(e, 2)
|
|
let w = emit_bind(sconcat("zext i32 ", sconcat(n, " to i64")))
|
|
var fn2 = "@fread"
|
|
if streq(name, "file_write") { fn2 = "@fwrite" }
|
|
let r = emit_bind(sconcat("call i64 ", sconcat(fn2, sconcat("(ptr ", sconcat(b, sconcat(", i64 1, i64 ", sconcat(w, sconcat(", ptr ", sconcat(f, ")")))))))))
|
|
return val(emit_bind(sconcat("trunc i64 ", sconcat(r, " to i32"))), "int")
|
|
}
|
|
if streq(name, "file_seek") {
|
|
let f = arg_code(e, 0); let off = arg_code(e, 1); let wh = arg_code(e, 2)
|
|
let o = emit_bind(sconcat("sext i32 ", sconcat(off, " to i64")))
|
|
return val(emit_bind(sconcat("call i32 @fseek(ptr ", sconcat(f, sconcat(", i64 ", sconcat(o, sconcat(", i32 ", sconcat(wh, ")"))))))), "int")
|
|
}
|
|
if streq(name, "file_tell") {
|
|
let f = arg_code(e, 0)
|
|
let r = emit_bind(sconcat("call i64 @ftell(ptr ", sconcat(f, ")")))
|
|
return val(emit_bind(sconcat("trunc i64 ", sconcat(r, " to i32"))), "int")
|
|
}
|
|
if streq(name, "file_close") {
|
|
let f = arg_code(e, 0)
|
|
emit(" call i32 @fclose(ptr "); emit(f); emit(")\n")
|
|
return val("0", "void")
|
|
}
|
|
if streq(name, "print_str") {
|
|
let s = arg_code(e, 0)
|
|
emit(" call i32 (ptr, ...) @printf(ptr @.fmt_str, ptr "); emit(s); emit(")\n")
|
|
return val("0", "void")
|
|
}
|
|
if streq(name, "print_int") {
|
|
let n = arg_code(e, 0)
|
|
emit(" call i32 (ptr, ...) @printf(ptr @.fmt_int, i32 "); emit(n); emit(")\n")
|
|
return val("0", "void")
|
|
}
|
|
if streq(name, "os_argc") { return val(emit_bind("load i32, ptr @L_argc"), "int") }
|
|
if streq(name, "os_arg") {
|
|
let i = arg_code(e, 0)
|
|
let v = emit_bind("load ptr, ptr @L_argv")
|
|
let q = emit_bind(sconcat("getelementptr ptr, ptr ", sconcat(v, sconcat(", i32 ", i))))
|
|
return val(emit_bind(sconcat("load ptr, ptr ", q)), "str")
|
|
}
|
|
if streq(name, "os_exit") {
|
|
let n = arg_code(e, 0)
|
|
emit(" call void @exit(i32 "); emit(n); emit(")\n")
|
|
emit(" unreachable\n")
|
|
g_term = true
|
|
return val("0", "void")
|
|
}
|
|
if streq(name, "file_stderr") { return val(emit_bind("load ptr, ptr @__stderrp"), "ptr") }
|
|
if streq(name, "os_system") {
|
|
let c = arg_code(e, 0)
|
|
return val(emit_bind(sconcat("call i32 @system(ptr ", sconcat(c, ")"))), "int")
|
|
}
|
|
if streq(name, "os_getenv") {
|
|
let n = arg_code(e, 0)
|
|
return val(emit_bind(sconcat("call ptr @getenv(ptr ", sconcat(n, ")"))), "str")
|
|
}
|
|
if streq(name, "bnot") { let a = arg_code(e, 0); return val(emit_bind(sconcat("xor i32 ", sconcat(a, ", -1"))), "int") }
|
|
if streq(name,"shl") or streq(name,"shr") or streq(name,"band") or streq(name,"bor") or streq(name,"bxor") {
|
|
let a = arg_code(e, 0); let b = arg_code(e, 1)
|
|
var opc = "shl"
|
|
if streq(name,"shr") { opc = "lshr" }
|
|
if streq(name,"band") { opc = "and" }
|
|
if streq(name,"bor") { opc = "or" }
|
|
if streq(name,"bxor") { opc = "xor" }
|
|
return val(emit_bind(sconcat(opc, sconcat(" i32 ", sconcat(a, sconcat(", ", b))))), "int")
|
|
}
|
|
if streq(name, "peek32") {
|
|
let p = arg_code(e, 0); let i = arg_code(e, 1)
|
|
let g = nreg()
|
|
emit(" "); emit(g); emit(" = getelementptr inbounds i32, ptr "); emit(p); emit(", i32 "); emit(i); emit("\n")
|
|
return val(emit_bind(sconcat("load i32, ptr ", g)), "int")
|
|
}
|
|
if streq(name, "poke32") {
|
|
let p = arg_code(e, 0); let i = arg_code(e, 1); let v = arg_code(e, 2)
|
|
let g = nreg()
|
|
emit(" "); emit(g); emit(" = getelementptr inbounds i32, ptr "); emit(p); emit(", i32 "); emit(i); emit("\n")
|
|
emit(" store i32 "); emit(v); emit(", ptr "); emit(g); emit("\n")
|
|
return val("0", "void")
|
|
}
|
|
g_intrin_ok = false
|
|
return val("0", "void")
|
|
}
|