ludic/selfhost/emit_intrin.ludic
Orkuncakilkaya fcada23eba Phase 7a: bitwise operators & | ^ << >> ~ (retire band/bor/shl/…)
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>
2026-08-28 00:45:10 +03:00

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")
}