First step of the "stop feeling like C" pass. Bitwise ops were functions (`band(x, MASK)`, `shl(a, 3)`); they are now real operators: band -> & bor -> | bxor -> ^ shl -> << shr -> >> bnot -> ~ Precedence is Go-style so the C footgun is gone: `<<`/`>>`/`&` bind like `*`, `|`/`^` like `+`, both tighter than comparison — `flags & MASK == 0` parses as `(flags & MASK) == 0`. `>>` is logical (lshr), matching the old `shr`. Mechanics: lexer tokenizes `<< >> & | ^ ~`; p_mul takes `<< >> &`, p_add takes `| ^`, p_unary takes `~`; emit_bin routes them through the existing int arith path (arith_code gains and/or/xor/shl/lshr) and E_UN handles `~`. The six intrinsics are deleted. Delivered as two reseeds: (A) add the operators keeping the intrinsics, (B) migrate every call site to operator form (85 lines across compiler + runtime, via a balanced-paren call->operator script; two multi-line big-endian reads in image/truetype done by hand) and delete the intrinsics. Vocabulary drops the six from LUDIC_INTRINSICS (ludic_syntax.h, grammar, LudicTokens.kt) and the grammar gains a bitwise-operator rule. LANGUAGE.md precedence table rewritten. Reseeded (21724 lines); C-free fixpoint holds; goldens byte-identical (the PNG and TrueType decoders lean on these ops); 17/17; vocab + doc-fences clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
132 lines
6.1 KiB
Text
132 lines
6.1 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,"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")
|
|
}
|
|
# bitwise ops are the operators & | ^ << >> ~ now (see emit_bin / p_mul).
|
|
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")
|
|
}
|