Build.schema_hash(): FNV-1a 64 of the program's own schema (the bytes ludic schema prints), worked out only when a program names it, 0 under ludicc --release, which ludic bundle now passes (Mac and Windows); reseeded

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-29 19:05:28 +03:00
parent b5bffe99fa
commit dacc0f7280
14 changed files with 75837 additions and 75090 deletions

View file

@ -290,6 +290,10 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
if is_path_ns(meth) { return emit_path_ns(meth, e) }
return emit_alias_or_fail("Path", meth, e)
}
if (ns == "Build") {
if is_build_ns(meth) { return emit_build_ns(meth, e) }
return emit_alias_or_fail("Build", meth, e)
}
if (ns == "Mime") {
if is_mime_ns(meth) { return emit_mime_ns(meth, e) }
return emit_alias_or_fail("Mime", meth, e)

View file

@ -16,6 +16,15 @@
var g_schema_out: pointer = null
function schema_write(path: pointer) -> bool {
let b = schema_buf()
let f = file_open(path, "wb")
if f == null { return false }
file_write(f, buf_str(b), b.len)
file_close(f)
return true
}
# the schema's bytes, exactly as --emit-schema writes them
function schema_buf() -> Buf {
let b = buf_new()
buf_puts(b, "{\n \"schema_version\": 1,\n \"entry\": ")
jq_put(b, g_mg_entry)
@ -28,11 +37,7 @@ function schema_write(path: pointer) -> bool {
buf_puts(b, "\n ],\n \"functions\": [")
sc_functions(b)
buf_puts(b, "\n ]\n}\n")
let f = file_open(path, "wb")
if f == null { return false }
file_write(f, buf_str(b), b.len)
file_close(f)
return true
return b
}
# ---- names and text ---------------------------------------------------------------------------

View file

@ -0,0 +1,46 @@
# emit_schema_hash.ludic — Build.schema_hash(): FNV-1a 64 of the program's own schema (the bytes
# `ludic schema` prints), so a tool talking to a running build can tell it was built from other
# data. Worked out once, only when the program asks for it, and 0 in a release (`--release`,
# ludic bundle). The compiler's int is 32 bits, so the hash is kept as four 16-bit limbs.
var g_release: bool = false # --release: what ships carries no schema hash
var g_uses_schema_hash: bool = false # the program names Build.schema_hash (parse.ludic)
var g_sh_lo: int = 0 # the hash's low and high 32 bits, once worked out
var g_sh_hi: int = 0
var g_sh_done: bool = false
# after the checker: the schema as --emit-schema would write it, hashed
function schema_hash_compute() -> void {
if g_release or not g_uses_schema_hash { return }
let b = schema_buf()
let s = buf_str(b)
var h0 = 8997 # 522109ce484222325, low limb first
var h1 = 33826
var h2 = 40164
var h3 = 52210
var i = 0
while i < b.len {
h0 = h0 ^ (s[i] & 255)
# times 256000001b3: limb 0 of the prime is 435, limb 2 is 256
var r0 = h0 * 435
var r1 = h1 * 435 + (r0 >> 16)
var r2 = h2 * 435 + h0 * 256 + (r1 >> 16)
var r3 = h3 * 435 + h1 * 256 + (r2 >> 16)
h0 = r0 & 65535
h1 = r1 & 65535
h2 = r2 & 65535
h3 = r3 & 65535
i += 1
}
g_sh_lo = (h1 << 16) | h0
g_sh_hi = (h3 << 16) | h2
g_sh_done = true
}
function is_build_ns(meth: pointer) -> bool { return meth == "schema_hash" }
function emit_build_ns(meth: pointer, e: Node) -> Val {
if not g_sh_done { return val("0", "long") }
let lo = emit_bind(`zext i32 {itoa(g_sh_lo)} to i64`)
let hi = emit_bind(`zext i32 {itoa(g_sh_hi)} to i64`)
let up = emit_bind(`shl i64 {hi}, 32`)
return val(emit_bind(`or i64 {up}, {lo}`), "long")
}

View file

@ -360,6 +360,7 @@ function p_postfix() -> Node {
if e.a.kind == E_ID and e.a.s == "Regex" { g_uses_regex = true } # splice the regex runtime on demand
if e.a.kind == E_ID and e.a.s == "Random" { g_uses_rng = true } # splice the seeded random numbers on demand
if e.a.kind == E_ID and e.a.s == "Fs" and (e.s == "read_bytes" or e.s == "write_bytes") { g_uses_bytes = true }
if e.a.kind == E_ID and e.a.s == "Build" and e.s == "schema_hash" { g_uses_schema_hash = true } # the schema hashed after the checker
if e.a.kind == E_ID and (e.a.s == "BigInt" or e.a.s == "Decimal") { g_uses_bignum = true } # splice the bignum runtime on demand
if e.a.kind == E_ID and (e.a.s == "Dict" or e.a.s == "Set") { g_uses_dict = true } # splice the hash-table runtime on demand
if e.a.kind == E_ID and (e.a.s == "Huge" or e.a.s == "Angle" or e.a.s == "Percent") { g_uses_numeric = true } # splice the huge/angle/percent runtime on demand

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -158,6 +158,7 @@ entry {
else if a == "--save-temps" { save = true }
else if a == "--run" { run = true }
else if a == "--check" { g_check_only = true }
else if a == "--release" { g_release = true } # what ships: Build.schema_hash() is 0 (emit_schema_hash.ludic)
else if a == "--diagnostics=json" { g_diag_json = true } # every error, as JSON on stdout (diag.ludic)
else if a == "--diagnostics=text" { g_diag_json = false }
else if a == "--emit-schema" { ai += 1; if ai < arg_count() { g_schema_out = arg(ai) } } # emit_schema.ludic
@ -248,6 +249,7 @@ entry {
if g_migrate { mg_finish() }
# --emit-schema FILE: the declarations, the registries and their entries as the checker resolved
# them (emit_schema.ludic); on its own it stops there, with -o or --check the build goes on
schema_hash_compute() # Build.schema_hash(), when the program asks and it is no release
if g_schema_out != null {
if not schema_write(g_schema_out) { die(`ludicc: cannot write {g_schema_out}\n`) }
if not g_check_only and not has_target and not emit_ir { diag_exit(0) }