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:
parent
b5bffe99fa
commit
dacc0f7280
14 changed files with 75837 additions and 75090 deletions
|
|
@ -680,6 +680,11 @@ Each list is sorted by name (then file and line), a registry's entries are in in
|
|||
paths are the ones the compiler read, so two runs over the same source write the same file. The
|
||||
engine's runtime is left out.
|
||||
|
||||
`Build.schema_hash()` is a `long`: FNV-1a 64 of exactly those bytes, for the program being built,
|
||||
worked out once and only when the program names it - so a tool talking to a running build can tell
|
||||
whether it was compiled from the data in front of it. It is 0 in a release (`ludicc --release`,
|
||||
which `ludic bundle` passes).
|
||||
|
||||
### Default parameters, and calls that name what they change
|
||||
|
||||
A parameter can have a default, and a call leaves out what it does not change - the last ones when
|
||||
|
|
|
|||
|
|
@ -14,3 +14,6 @@ so is `@Node` / `@Clip` naming a field that is not a glTF (`@Asset("gltf")`, or
|
|||
a listing `@OneOf` (`@OneOf(A, B)`, not a prefix `@OneOf(P_)`) naming a constant that does not exist, and `@Tint` naming no constant. A field may now carry several attributes. `ludicc --check
|
||||
--diagnostics=json` (`ludic build --check --diagnostics=json`) prints every error as one JSON array
|
||||
of `{file, line, col, severity, message}` on stdout; tokens and nodes now know their column.
|
||||
|
||||
`Build.schema_hash()` answers FNV-1a 64 of the program's own schema (the bytes `ludic schema` prints),
|
||||
computed only when a program names it, and 0 under `ludicc --release`, which `ludic bundle` now passes.
|
||||
|
|
|
|||
14
examples/lang/schema_hash.ludic
Normal file
14
examples/lang/schema_hash.ludic
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# schema_hash.ludic — Build.schema_hash(): FNV-1a 64 of the program's own schema (what `ludic schema`
|
||||
# prints), so a tool can tell which data a running build was compiled from; 0 in a release build
|
||||
# (`ludicc --release`, which ludic bundle passes).
|
||||
#
|
||||
# Running it prints: true
|
||||
program SchemaHash {
|
||||
property Tool {
|
||||
key: string = ""
|
||||
weight: float = 1.0
|
||||
}
|
||||
registry Tools of Tool as TL
|
||||
def Tools axe { weight: 2.5 }
|
||||
entry { print(`{Build.schema_hash() != long(0)}`) }
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 ---------------------------------------------------------------------------
|
||||
|
|
|
|||
46
selfhost/backend/emit_schema_hash.ludic
Normal file
46
selfhost/backend/emit_schema_hash.ludic
Normal 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")
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
74803
selfhost/ludicc.seed.ll
74803
selfhost/ludicc.seed.ll
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -49,11 +49,11 @@ function compile_app(src: pointer, out: pointer, mode: int, save: bool) -> bool
|
|||
var flags = "--windowed"
|
||||
if mode == 2 { flags = "--headless" }
|
||||
if save { flags = flags + " --save-temps" }
|
||||
return shq(`{ludicc()} {flags}{title_flag()}{unsafe_flag()} {src} -o {out}`)
|
||||
return shq(`{ludicc()} {flags}{title_flag()}{unsafe_flag()}{release_flag()} {src} -o {out}`)
|
||||
}
|
||||
|
||||
if mode == 2 {
|
||||
if not shq(`{ludicc()} --headless{title_flag()}{unsafe_flag()} {src} --emit-llvm -o {ll}`) { return false }
|
||||
if not shq(`{ludicc()} --headless{title_flag()}{unsafe_flag()}{release_flag()} {src} --emit-llvm -o {ll}`) { return false }
|
||||
let objs = split_objs(ll, out) # on every core when it can (split.ludic)
|
||||
var input = ll
|
||||
if objs != "" { input = objs }
|
||||
|
|
@ -65,7 +65,7 @@ function compile_app(src: pointer, out: pointer, mode: int, save: bool) -> bool
|
|||
return true
|
||||
}
|
||||
|
||||
if not shq(`{ludicc()} --windowed{title_flag()}{unsafe_flag()} {src} --emit-llvm -o {ll}`) { return false }
|
||||
if not shq(`{ludicc()} --windowed{title_flag()}{unsafe_flag()}{release_flag()} {src} --emit-llvm -o {ll}`) { return false }
|
||||
# audio.ll (#22) is always linked here — unused snd_* are dead-stripped; the
|
||||
# canonical `ludicc -o` path links it only when Audio.* is used.
|
||||
let cocoa = `{home}runtime/native/cocoa.ll`
|
||||
|
|
@ -85,6 +85,12 @@ function compile_app(src: pointer, out: pointer, mode: int, save: bool) -> bool
|
|||
# opens under that name rather than the `program` name; "" otherwise, and for a name the
|
||||
# shell line could not carry safely.
|
||||
# ` --unsafe` when the build said so: the project's own files may write `unsafe` (L7)
|
||||
# ludic bundle: what ships is a release, and Build.schema_hash() is 0 in it
|
||||
var g_release_build: bool = false
|
||||
function release_flag() -> pointer {
|
||||
if g_release_build { return " --release" }
|
||||
return ""
|
||||
}
|
||||
var g_unsafe_build: bool = false
|
||||
function unsafe_flag() -> pointer {
|
||||
if g_unsafe_build { return " --unsafe" }
|
||||
|
|
|
|||
|
|
@ -355,7 +355,7 @@ function cmd_bundle_windows() -> int {
|
|||
# 1. the executable - staged under a name with no spaces, as on macOS
|
||||
let res = windows_icon_res(manifest_app(m, "icon"))
|
||||
let staged = tmp_path("bundle_exe")
|
||||
var flags = "--windowed --gui"
|
||||
var flags = "--windowed --gui --release"
|
||||
if res != "" { flags = flags + ` --link "{res}"` }
|
||||
Os.set_env("LUDIC_OPT", "2") # a shipped game is -O2, whatever a developer's shell says
|
||||
if not shq(`{ludicc()} {flags}{title_flag()} {entry} --natives-out {staged}.natives -o {staged}`) { return 1 }
|
||||
|
|
@ -441,6 +441,7 @@ function cmd_bundle() -> int {
|
|||
# out of the path it is given.
|
||||
let exe = `{contents}/MacOS/{name}`
|
||||
let staged = `{tmp_dir()}/bundle_exe`
|
||||
g_release_build = true
|
||||
if not compile_app(entry, staged, 1, false) { return 1 }
|
||||
if not shq(`cp {staged} "{exe}"`) {
|
||||
err("ludic bundle: could not place the executable\n")
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ function selfhost_frags() -> []pointer {
|
|||
push(f, "selfhost/backend/emit_fnval.ludic")
|
||||
push(f, "selfhost/backend/emit_vis.ludic")
|
||||
push(f, "selfhost/backend/emit_schema.ludic")
|
||||
push(f, "selfhost/backend/emit_schema_hash.ludic")
|
||||
push(f, "selfhost/check/check_state.ludic")
|
||||
push(f, "selfhost/check/check_index.ludic")
|
||||
push(f, "selfhost/check/check_types.ludic")
|
||||
|
|
|
|||
|
|
@ -1232,6 +1232,7 @@ function cmd_dev_test() -> int {
|
|||
feat_case("lang/registry", "", "3 Crate true 2 fire_ring -1", "registry.ludic (L8: a registry filled by def declarations - index constants, a count, the key, a lookup)")
|
||||
reject_case("rejected/registry_unknown", "there is no registry Tables", "a def names a registry that exists")
|
||||
feat_case("lang/attributes", "", "2 1 Lantern 2", "attributes.ludic (editor attributes: @Ref, @OneOf, @Range, @Unit, @Asset, @Color, @AppendOnly, @ByKey - no effect on the program)")
|
||||
feat_case("lang/schema_hash", "", "true", "schema_hash.ludic (Build.schema_hash(): FNV-1a 64 of the program's own schema, 0 in a release)")
|
||||
reject_case("rejected/ref_unknown", "field Tool.seller: @Ref(Vendorz) names no registry", "@Ref names a registry that exists")
|
||||
reject_case("rejected/node_bad", "field Tool.grip: @Node(handle) names no field", "@Node names a field of its record")
|
||||
schema_case("lang/attributes", "{\"name\": \"weight\", \"type\": \"float\", \"default\": \"1.0\", \"file\": \"examples/lang/attributes.ludic\", \"line\": 24, \"col\": 33, \"attributes\": [{\"name\": \"Range\", \"args\": [0, 20.5]}, {\"name\": \"Unit\", \"args\": [\"kg\"]}]", "--emit-schema: a field's type, default, place and attributes")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue