feat(lang,stdlib): 64-bit long type + 64-bit Hash variants (issue #17)
Some checks are pending
docs / build-and-deploy (push) Waiting to run
Some checks are pending
docs / build-and-deploy (push) Waiting to run
Add `long`, a 64-bit signed integer primitive (i64), threaded through codegen: llty; int<->long coercion (coerce_code/to_long) at let/assign/ return/call-args; i64 arithmetic + comparison promotion in emit_bin; unary negate/~; print via %lld and str()/interpolation via @fn_long_str. Editor vocabulary (syntax header, TextMate grammar, formatter, LSP) synced; check-vocabulary green. Numeric literals stay i32 — build large values by widening (documented on the type page). Complete the Hash.* namespace (issue #17) with both 32- and 64-bit algorithms: Hash.of/fnv1a/crc32/mix/combine (32-bit) and Hash.of64/fnv1a_64/mix64 (64-bit, returning long). Deterministic and C-free; CRC-32 (poly 0xEDB88320) and FNV vectors verified against reference implementations. Tests: selfhost/tests/{hash,long}.ludic. Docs: docs/language/hash/*, type-long.md. Seed reseeded; C-free bootstrap fixpoint holds; 23 selfhost + 45 regression + 29 tooling checks green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
a38195128f
commit
2002e977d9
27 changed files with 14007 additions and 12105 deletions
|
|
@ -24,6 +24,8 @@ var g_uses_strslice: bool = false # `s[a..b]` was emitted -> emit the substring
|
|||
var g_uses_mathrt: bool = false # Math.sqrt/sin/cos/tan was emitted -> emit the math runtime prelude
|
||||
var g_uses_textrt: bool = false # Text.upper/lower/trim/repeat/pad was emitted -> emit the text builders
|
||||
var g_uses_textrt2: bool = false # Text.split/join/replace was emitted -> emit the string/slice builders
|
||||
var g_uses_hashrt: bool = false # Hash.of/fnv1a/crc32 was emitted -> emit the byte-stream hashers
|
||||
var g_uses_longstr: bool = false # str(long) / interpolating a long was emitted -> emit fn_long_str
|
||||
|
||||
# loop targets for break/continue (innermost last)
|
||||
var brk_lbl: []ptr
|
||||
|
|
@ -70,6 +72,7 @@ fn lbl(pfx: ptr) -> ptr { let r = (pfx + itoa(ll_lbl)); ll_lbl = ll_lbl + 1; r
|
|||
# slice) is a pointer; void is void.
|
||||
fn llty(t: ptr) -> ptr {
|
||||
if (t == "int") or (t == "bool") or (t == "fixed") or (t == "entity") { return "i32" } # entity = an i32 handle (self())
|
||||
if (t == "long") { return "i64" } # a 64-bit signed integer
|
||||
if (t == "byte") { return "i8" } # a single byte (p[i] on a raw ptr)
|
||||
if (t == "words") or (t == "fixeds") or (t == "ptrs") { return "ptr" } # typed buffers
|
||||
if (t == "void") { return "void" }
|
||||
|
|
|
|||
|
|
@ -98,10 +98,12 @@ fn emit_program() -> void {
|
|||
if g_uses_loopback { emit_loopback() } # built-in transport, after all net_send/net_poll uses are seen
|
||||
if g_uses_str { emit_str_prelude() } # @fn_str_eq / @fn_str_concat, after all uses are seen
|
||||
if g_uses_intstr { emit_int_str() } # @fn_int_str, for str(int) in interpolation
|
||||
if g_uses_longstr { emit_long_str() } # @fn_long_str, for str(long) / long interpolation
|
||||
if g_uses_strslice { emit_str_slice() } # @fn_str_slice, for s[a..b]
|
||||
if g_uses_mathrt { emit_math_prelude() } # @fn_fx_sqrt / @fn_fx_sin + the sine table
|
||||
if g_uses_textrt { emit_text_prelude() } # @fn_str_upper/lower/trim/repeat/pad builders
|
||||
if g_uses_textrt2 { emit_text2_prelude() } # @fn_str_replace/join/split builders
|
||||
if g_uses_hashrt { emit_hash_prelude() } # @fn_hash_fnv1a / @fn_hash_crc32 byte hashers
|
||||
}
|
||||
|
||||
# Flush the emitted IR. With a null path it goes to stdout (the pipe the shell
|
||||
|
|
|
|||
|
|
@ -56,6 +56,24 @@ fn to_fixed(v: Val) -> ptr {
|
|||
return emit_bind(`shl i32 {v.code}, 16`)
|
||||
}
|
||||
|
||||
# coerce a value's code to the LLVM type of `target`, for the only cross-width
|
||||
# pair the language has: int (i32) <-> long (i64). int widens with sext, long
|
||||
# narrows with trunc; everything else (same width, or ptr) passes through.
|
||||
fn coerce_code(v: Val, target: ptr) -> ptr {
|
||||
let lt = llty(target)
|
||||
let vt = llty(v.ty)
|
||||
if (lt == vt) { return v.code }
|
||||
if (lt == "i64") and (vt == "i32") { return emit_bind(`sext i32 {v.code} to i64`) }
|
||||
if (lt == "i32") and (vt == "i64") { return emit_bind(`trunc i64 {v.code} to i32`) }
|
||||
return v.code
|
||||
}
|
||||
|
||||
# widen an int value to i64 (a long passes through) — the long analogue of to_fixed
|
||||
fn to_long(v: Val) -> ptr {
|
||||
if (llty(v.ty) == "i64") { return v.code }
|
||||
return emit_bind(`sext i32 {v.code} to i64`)
|
||||
}
|
||||
|
||||
# string operators: `a + b` concatenates, `a == b` / `a != b` compare by content.
|
||||
# Both call the @fn_str_* prelude (emitted once per program that uses them).
|
||||
fn emit_str_op(op: ptr, a: Val, b: Val) -> Val {
|
||||
|
|
@ -85,14 +103,22 @@ fn emit_bin(e: Node) -> Val {
|
|||
if ((e.s == ("==")) or (e.s == ("!="))) and not isnull { return emit_str_op(e.s, a, b) }
|
||||
}
|
||||
let fx = (a.ty == "fixed") or (b.ty == "fixed")
|
||||
# a 64-bit operand (and no fixed/ptr involved) promotes the whole expression to
|
||||
# i64: the other side widens with sext, and the result stays `long`.
|
||||
let lng = ((llty(a.ty) == "i64") or (llty(b.ty) == "i64")) and not fx and not ptrish
|
||||
if is_cmp(e.s) {
|
||||
var ac = a.code; var bc = b.code
|
||||
var ct = "i32"
|
||||
if fx { ac = to_fixed(a); bc = to_fixed(b) }
|
||||
else { if (llty(a.ty) == "ptr") or (llty(b.ty) == "ptr") { ct = "ptr" } } # `p == null`, str/record identity
|
||||
else { if lng { ct = "i64"; ac = to_long(a); bc = to_long(b) }
|
||||
else { if (llty(a.ty) == "ptr") or (llty(b.ty) == "ptr") { ct = "ptr" } } } # `p == null`, str/record identity
|
||||
let c = emit_bind(`icmp {cmp_code(e.s)} {ct} {ac}, {bc}`)
|
||||
return val(emit_bind(`zext i1 {c} to i32`), "bool")
|
||||
}
|
||||
if lng {
|
||||
let al = to_long(a); let bl = to_long(b)
|
||||
return val(emit_bind(`{arith_code(e.s)} i64 {al}, {bl}`), "long")
|
||||
}
|
||||
if fx {
|
||||
let af = to_fixed(a); let bf = to_fixed(b)
|
||||
if (e.s == ("*")) {
|
||||
|
|
@ -154,6 +180,13 @@ fn param_labels(fn: Node) -> []ptr {
|
|||
return out
|
||||
}
|
||||
|
||||
fn param_types(fn: Node) -> []ptr {
|
||||
let out = new []ptr
|
||||
var i = 0
|
||||
while i < len(fn.kids) { if fn.kids[i].kind == N_PARAM { push(out, fn.kids[i].ty) }; i = i + 1 }
|
||||
return out
|
||||
}
|
||||
|
||||
# ---- namespaced builtins: Screen.* / Random.* / Input.* --------------------
|
||||
# The game-facing API reads as `subject.action(...)`. Each method maps to a bare
|
||||
# runtime builtin plus the parameter labels callers may use as named arguments;
|
||||
|
|
@ -194,6 +227,10 @@ fn emit_ns_call(ns: ptr, meth: ptr, e: Node) -> Val {
|
|||
if is_time_ns(meth) { return emit_time_ns(meth, e) }
|
||||
perr(`unknown builtin Time.{meth}`)
|
||||
}
|
||||
if (ns == "Hash") {
|
||||
if is_hash_ns(meth) { return emit_hash_ns(meth, e) }
|
||||
perr(`unknown builtin Hash.{meth}`)
|
||||
}
|
||||
var bare: ptr = null
|
||||
let labels = new []ptr
|
||||
if (ns == "Screen") {
|
||||
|
|
@ -374,16 +411,18 @@ fn emit_call(e: Node) -> Val {
|
|||
}
|
||||
if (name == "len") { return emit_len(e) }
|
||||
if (name == "push") { return emit_push(e) }
|
||||
if (name == "str") { # str(x): int/bool/fixed -> text, a string passes through
|
||||
if (name == "str") { # str(x): int/bool/fixed/long -> text, a string passes through
|
||||
let a = emit_expr(e.kids[0])
|
||||
if (llty(a.ty) == "ptr") { return a }
|
||||
if (llty(a.ty) == "i64") { g_uses_longstr = true; return val(emit_bind(`call ptr @fn_long_str(i64 {a.code})`), "str") }
|
||||
g_uses_intstr = true
|
||||
return val(emit_bind(`call ptr @fn_int_str(i32 {a.code})`), "str")
|
||||
}
|
||||
if (name == "print") { # print(x): a value + newline (int or string)
|
||||
if (name == "print") { # print(x): a value + newline (string, long, or int)
|
||||
let a = emit_expr(e.kids[0])
|
||||
if (llty(a.ty) == "ptr") { emit(" call i32 (ptr, ...) @printf(ptr @.fmt_line, ptr " + `{a.code})\n`) }
|
||||
else { emit(" call i32 (ptr, ...) @printf(ptr @.fmt_int, i32 " + `{a.code})\n`) }
|
||||
else { if (llty(a.ty) == "i64") { emit(" call i32 (ptr, ...) @printf(ptr @.fmt_long, i64 " + `{a.code})\n`) }
|
||||
else { emit(" call i32 (ptr, ...) @printf(ptr @.fmt_int, i32 " + `{a.code})\n`) } }
|
||||
return val("0", "void")
|
||||
}
|
||||
if (name == "bytes") { # bytes(n): allocate n bytes -> a byte buffer
|
||||
|
|
@ -459,11 +498,18 @@ fn emit_call(e: Node) -> Val {
|
|||
cname = rtname
|
||||
}
|
||||
reorder_named(e, param_labels(fn2))
|
||||
# evaluate args first (their IR is emitted before the call instruction)
|
||||
# evaluate args first (their IR is emitted before the call instruction), coercing
|
||||
# each to the parameter's declared type so an int passed for a `long` widens.
|
||||
let ptys = param_types(fn2)
|
||||
let args = new []ptr
|
||||
let atys = new []ptr
|
||||
var i = 0
|
||||
while i < len(e.kids) { let v = emit_expr(e.kids[i]); push(args, v.code); push(atys, v.ty); i = i + 1 }
|
||||
while i < len(e.kids) {
|
||||
let v = emit_expr(e.kids[i])
|
||||
var pty = v.ty
|
||||
if (i < len(ptys)) { pty = ptys[i] }
|
||||
push(args, coerce_code(v, pty)); push(atys, pty); i = i + 1
|
||||
}
|
||||
let rl = llty(fn2.ty)
|
||||
emit(" ")
|
||||
var rreg = "0"
|
||||
|
|
@ -540,6 +586,10 @@ fn emit_expr(e: Node) -> Val {
|
|||
if e.kind == E_BIN { return emit_bin(e) }
|
||||
if e.kind == E_UN {
|
||||
let a = emit_expr(e.a)
|
||||
if (llty(a.ty) == "i64") { # negate / bit-flip a long, staying 64-bit
|
||||
if (e.s == ("-")) { return val(emit_bind(`sub i64 0, {a.code}`), "long") }
|
||||
if (e.s == "~") { return val(emit_bind(`xor i64 {a.code}, -1`), "long") }
|
||||
}
|
||||
if (e.s == ("-")) { return val(emit_bind(`sub i32 0, {a.code}`), "int") }
|
||||
if (e.s == "~") { return val(emit_bind(`xor i32 {a.code}, -1`), "int") }
|
||||
let c = emit_bind(`icmp eq i32 {a.code}, 0`)
|
||||
|
|
|
|||
195
selfhost/emit_hash.ludic
Normal file
195
selfhost/emit_hash.ludic
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
# emit_hash.ludic — the Hash.* namespace: fast, non-cryptographic hashing for
|
||||
# map keys, content IDs, deterministic seeds and checksums. Everything is plain
|
||||
# 32-bit integer IR with defined constants and byte order, so a given input
|
||||
# hashes to the same value on every platform and every run — which is what makes
|
||||
# it safe for procedural generation and lockstep networking. NOT for passwords
|
||||
# or tamper-proofing; point users at the Crypto library for that.
|
||||
#
|
||||
# Hash.of(s) fast default string hash (currently FNV-1a 32)
|
||||
# Hash.fnv1a(s) FNV-1a 32-bit, named explicitly
|
||||
# Hash.crc32(s) CRC-32 (IEEE 802.3) checksum, for corruption detection
|
||||
# Hash.mix(x) fmix32 avalanche of a single int (turn a counter into a seed)
|
||||
# Hash.combine(...) fold several ints into one (e.g. world_seed, cx, cy)
|
||||
|
||||
fn is_hash_ns(meth: ptr) -> bool {
|
||||
if (meth == "of") or (meth == "fnv1a") or (meth == "crc32") { return true }
|
||||
if (meth == "mix") or (meth == "combine") { return true }
|
||||
if (meth == "of64") or (meth == "fnv1a_64") or (meth == "mix64") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
# fmix32 (MurmurHash3 finalizer) of a single i32 -> code of an i32. A strong
|
||||
# avalanche: flips ~half the output bits for any one input bit. Used on its own
|
||||
# (Hash.mix) and nowhere else — combine has its own mixing step.
|
||||
fn hash_mix_code(x: ptr) -> ptr {
|
||||
let a = emit_bind(`lshr i32 {x}, 16`)
|
||||
let b = emit_bind(`xor i32 {x}, {a}`)
|
||||
let c = emit_bind(`mul i32 {b}, -2048144789`) # * 0x85ebca6b
|
||||
let d = emit_bind(`lshr i32 {c}, 13`)
|
||||
let e = emit_bind(`xor i32 {c}, {d}`)
|
||||
let f = emit_bind(`mul i32 {e}, -1028477387`) # * 0xc2b2ae35
|
||||
let g = emit_bind(`lshr i32 {f}, 16`)
|
||||
return emit_bind(`xor i32 {f}, {g}`)
|
||||
}
|
||||
|
||||
# fmix64 (MurmurHash3 64-bit finalizer) of a single i64 -> code of an i64. The
|
||||
# 64-bit twin of hash_mix_code: shift by 33 and multiply by the two 64-bit
|
||||
# constants. Backs Hash.mix64.
|
||||
fn hash_mix64_code(x: ptr) -> ptr {
|
||||
let a = emit_bind(`lshr i64 {x}, 33`)
|
||||
let b = emit_bind(`xor i64 {x}, {a}`)
|
||||
let c = emit_bind(`mul i64 {b}, -49064778989728563`) # * 0xff51afd7ed558ccd
|
||||
let d = emit_bind(`lshr i64 {c}, 33`)
|
||||
let e = emit_bind(`xor i64 {c}, {d}`)
|
||||
let f = emit_bind(`mul i64 {e}, -4265267296055464877`) # * 0xc4ceb9fe1a85ec53
|
||||
let g = emit_bind(`lshr i64 {f}, 33`)
|
||||
return emit_bind(`xor i64 {f}, {g}`)
|
||||
}
|
||||
|
||||
fn emit_hash_ns(meth: ptr, e: Node) -> Val {
|
||||
if (meth == "of") or (meth == "fnv1a") { # FNV-1a 32-bit over the bytes
|
||||
g_uses_hashrt = true
|
||||
let s = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call i32 @fn_hash_fnv1a(ptr {s.code})`), "int")
|
||||
}
|
||||
if (meth == "of64") or (meth == "fnv1a_64") { # FNV-1a 64-bit -> a `long`
|
||||
g_uses_hashrt = true
|
||||
let s = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call i64 @fn_hash_fnv1a_64(ptr {s.code})`), "long")
|
||||
}
|
||||
if (meth == "mix64") { # fmix64 avalanche of one long
|
||||
let x = emit_expr(e.kids[0])
|
||||
return val(hash_mix64_code(to_long(x)), "long")
|
||||
}
|
||||
if (meth == "crc32") { # CRC-32 (IEEE) checksum
|
||||
g_uses_hashrt = true
|
||||
let s = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call i32 @fn_hash_crc32(ptr {s.code})`), "int")
|
||||
}
|
||||
if (meth == "mix") { # fmix32 avalanche of one int
|
||||
let x = emit_expr(e.kids[0])
|
||||
return val(hash_mix_code(x.code), "int")
|
||||
}
|
||||
# combine(a, b, ...) -> fold the boost hash_combine step over every argument:
|
||||
# seed = seed ^ (v + 0x9e3779b9 + (seed << 6) + (seed >> 2))
|
||||
# order-sensitive and deterministic; seed starts at 0 so a single argument is
|
||||
# still well-mixed with the golden-ratio constant.
|
||||
var seed: ptr = "0"
|
||||
var i = 0
|
||||
while i < len(e.kids) {
|
||||
let v = emit_expr(e.kids[i])
|
||||
let g = emit_bind(`add i32 {v.code}, -1640531527`) # + 0x9e3779b9
|
||||
let sl = emit_bind(`shl i32 {seed}, 6`)
|
||||
let sr = emit_bind(`lshr i32 {seed}, 2`)
|
||||
let t1 = emit_bind(`add i32 {g}, {sl}`)
|
||||
let t2 = emit_bind(`add i32 {t1}, {sr}`)
|
||||
seed = emit_bind(`xor i32 {seed}, {t2}`)
|
||||
i = i + 1
|
||||
}
|
||||
return val(seed, "int")
|
||||
}
|
||||
|
||||
# emit_hash_prelude — the byte-stream hashers, emitted once per program that uses
|
||||
# Hash.of/fnv1a/crc32 (g_uses_hashrt). Both walk a null-terminated string a byte
|
||||
# at a time with pure integer IR: FNV-1a with the standard 32-bit offset basis /
|
||||
# prime, and a bitwise CRC-32 with the reflected poly 0xEDB88320. No libc, no
|
||||
# allocation, bit-identical on every target.
|
||||
fn emit_hash_prelude() -> void {
|
||||
emith("define i32 @fn_hash_fnv1a(ptr %s) {\n")
|
||||
emith("entry:\n")
|
||||
emith(" %hp = alloca i32\n")
|
||||
emith(" store i32 -2128831035, ptr %hp\n") # 0x811c9dc5 offset basis
|
||||
emith(" %ip = alloca i64\n")
|
||||
emith(" store i64 0, ptr %ip\n")
|
||||
emith(" br label %cond\n")
|
||||
emith("cond:\n")
|
||||
emith(" %i = load i64, ptr %ip\n")
|
||||
emith(" %p = getelementptr i8, ptr %s, i64 %i\n")
|
||||
emith(" %c = load i8, ptr %p\n")
|
||||
emith(" %z = icmp eq i8 %c, 0\n")
|
||||
emith(" br i1 %z, label %done, label %body\n")
|
||||
emith("body:\n")
|
||||
emith(" %ce = zext i8 %c to i32\n")
|
||||
emith(" %h = load i32, ptr %hp\n")
|
||||
emith(" %x = xor i32 %h, %ce\n")
|
||||
emith(" %m = mul i32 %x, 16777619\n") # * 0x01000193 prime
|
||||
emith(" store i32 %m, ptr %hp\n")
|
||||
emith(" %i1 = add i64 %i, 1\n")
|
||||
emith(" store i64 %i1, ptr %ip\n")
|
||||
emith(" br label %cond\n")
|
||||
emith("done:\n")
|
||||
emith(" %hr = load i32, ptr %hp\n")
|
||||
emith(" ret i32 %hr\n")
|
||||
emith("}\n")
|
||||
emith("define i64 @fn_hash_fnv1a_64(ptr %s) {\n") # 64-bit FNV-1a, same shape, i64
|
||||
emith("entry:\n")
|
||||
emith(" %hp = alloca i64\n")
|
||||
emith(" store i64 -3750763034362895579, ptr %hp\n") # 0xcbf29ce484222325 offset basis
|
||||
emith(" %ip = alloca i64\n")
|
||||
emith(" store i64 0, ptr %ip\n")
|
||||
emith(" br label %cond\n")
|
||||
emith("cond:\n")
|
||||
emith(" %i = load i64, ptr %ip\n")
|
||||
emith(" %p = getelementptr i8, ptr %s, i64 %i\n")
|
||||
emith(" %c = load i8, ptr %p\n")
|
||||
emith(" %z = icmp eq i8 %c, 0\n")
|
||||
emith(" br i1 %z, label %done, label %body\n")
|
||||
emith("body:\n")
|
||||
emith(" %ce = zext i8 %c to i64\n")
|
||||
emith(" %h = load i64, ptr %hp\n")
|
||||
emith(" %x = xor i64 %h, %ce\n")
|
||||
emith(" %m = mul i64 %x, 1099511628211\n") # * 0x100000001b3 prime
|
||||
emith(" store i64 %m, ptr %hp\n")
|
||||
emith(" %i1 = add i64 %i, 1\n")
|
||||
emith(" store i64 %i1, ptr %ip\n")
|
||||
emith(" br label %cond\n")
|
||||
emith("done:\n")
|
||||
emith(" %hr = load i64, ptr %hp\n")
|
||||
emith(" ret i64 %hr\n")
|
||||
emith("}\n")
|
||||
emith("define i32 @fn_hash_crc32(ptr %s) {\n")
|
||||
emith("entry:\n")
|
||||
emith(" %cp = alloca i32\n")
|
||||
emith(" store i32 -1, ptr %cp\n") # init 0xFFFFFFFF
|
||||
emith(" %ip = alloca i64\n")
|
||||
emith(" store i64 0, ptr %ip\n")
|
||||
emith(" %kp = alloca i32\n")
|
||||
emith(" br label %cond\n")
|
||||
emith("cond:\n")
|
||||
emith(" %i = load i64, ptr %ip\n")
|
||||
emith(" %p = getelementptr i8, ptr %s, i64 %i\n")
|
||||
emith(" %ch = load i8, ptr %p\n")
|
||||
emith(" %z = icmp eq i8 %ch, 0\n")
|
||||
emith(" br i1 %z, label %done, label %body\n")
|
||||
emith("body:\n")
|
||||
emith(" %ce = zext i8 %ch to i32\n")
|
||||
emith(" %c0 = load i32, ptr %cp\n")
|
||||
emith(" %cx = xor i32 %c0, %ce\n") # fold byte into low 8 bits
|
||||
emith(" store i32 %cx, ptr %cp\n")
|
||||
emith(" store i32 0, ptr %kp\n")
|
||||
emith(" br label %bit\n")
|
||||
emith("bit:\n")
|
||||
emith(" %k = load i32, ptr %kp\n")
|
||||
emith(" %kd = icmp slt i32 %k, 8\n")
|
||||
emith(" br i1 %kd, label %bitbody, label %bitdone\n")
|
||||
emith("bitbody:\n")
|
||||
emith(" %c1 = load i32, ptr %cp\n")
|
||||
emith(" %lb = and i32 %c1, 1\n")
|
||||
emith(" %ls = lshr i32 %c1, 1\n")
|
||||
emith(" %ism = icmp eq i32 %lb, 1\n")
|
||||
emith(" %px = select i1 %ism, i32 -306674912, i32 0\n") # ^ 0xEDB88320 when LSB set
|
||||
emith(" %c2 = xor i32 %ls, %px\n")
|
||||
emith(" store i32 %c2, ptr %cp\n")
|
||||
emith(" %k1 = add i32 %k, 1\n")
|
||||
emith(" store i32 %k1, ptr %kp\n")
|
||||
emith(" br label %bit\n")
|
||||
emith("bitdone:\n")
|
||||
emith(" %i1 = add i64 %i, 1\n")
|
||||
emith(" store i64 %i1, ptr %ip\n")
|
||||
emith(" br label %cond\n")
|
||||
emith("done:\n")
|
||||
emith(" %cf = load i32, ptr %cp\n")
|
||||
emith(" %r = xor i32 %cf, -1\n") # final XOR 0xFFFFFFFF
|
||||
emith(" ret i32 %r\n")
|
||||
emith("}\n")
|
||||
}
|
||||
|
|
@ -66,6 +66,7 @@ fn emit_header() -> void {
|
|||
emith("@__stderrp = external global ptr\n")
|
||||
emith("@__stdoutp = external global ptr\n")
|
||||
emith("@.fmt_int = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\"\n")
|
||||
emith("@.fmt_long = private unnamed_addr constant [6 x i8] c\"%lld\\0A\\00\"\n")
|
||||
emith("@.fmt_line = private unnamed_addr constant [4 x i8] c\"%s\\0A\\00\"\n")
|
||||
emith("@L_argc = internal global i32 0\n")
|
||||
emith("@L_argv = internal global ptr null\n")
|
||||
|
|
@ -192,6 +193,29 @@ fn emit_int_str() -> void {
|
|||
emith(" %rpos = add i32 %fpos, 1\n %res = getelementptr inbounds i8, ptr %buf, i32 %rpos\n ret ptr %res\n}\n")
|
||||
}
|
||||
|
||||
# the i64 twin of fn_int_str: a signed 64-bit integer -> decimal text. Emitted
|
||||
# once per program that stringifies a `long` (g_uses_longstr). A 64-bit value is
|
||||
# at most 20 digits plus sign and NUL, so the 24-byte scratch buffer still fits.
|
||||
fn emit_long_str() -> void {
|
||||
emith("define ptr @fn_long_str(i64 %n0) {\n")
|
||||
emith("entry:\n %buf = call ptr @malloc(i64 24)\n")
|
||||
emith(" %isneg = icmp slt i64 %n0, 0\n %neg = sub i64 0, %n0\n")
|
||||
emith(" %n = select i1 %isneg, i64 %neg, i64 %n0\n")
|
||||
emith(" %term = getelementptr inbounds i8, ptr %buf, i32 23\n store i8 0, ptr %term\n")
|
||||
emith(" %iszero = icmp eq i64 %n0, 0\n br i1 %iszero, label %zc, label %dl\n")
|
||||
emith("zc:\n %zp = getelementptr inbounds i8, ptr %buf, i32 22\n store i8 48, ptr %zp\n ret ptr %zp\n")
|
||||
emith("dl:\n br label %dloop\n")
|
||||
emith("dloop:\n %pos = phi i32 [ 22, %dl ], [ %pos2, %dbody ]\n %cur = phi i64 [ %n, %dl ], [ %cur2, %dbody ]\n")
|
||||
emith(" %done = icmp eq i64 %cur, 0\n br i1 %done, label %sign, label %dbody\n")
|
||||
emith("dbody:\n %d = urem i64 %cur, 10\n %d32 = trunc i64 %d to i32\n %ch = add i32 %d32, 48\n %ch8 = trunc i32 %ch to i8\n")
|
||||
emith(" %pp = getelementptr inbounds i8, ptr %buf, i32 %pos\n store i8 %ch8, ptr %pp\n")
|
||||
emith(" %cur2 = udiv i64 %cur, 10\n %pos2 = sub i32 %pos, 1\n br label %dloop\n")
|
||||
emith("sign:\n br i1 %isneg, label %addneg, label %fin\n")
|
||||
emith("addneg:\n %sp = getelementptr inbounds i8, ptr %buf, i32 %pos\n store i8 45, ptr %sp\n %posn = sub i32 %pos, 1\n br label %fin\n")
|
||||
emith("fin:\n %fpos = phi i32 [ %pos, %sign ], [ %posn, %addneg ]\n")
|
||||
emith(" %rpos = add i32 %fpos, 1\n %res = getelementptr inbounds i8, ptr %buf, i32 %rpos\n ret ptr %res\n}\n")
|
||||
}
|
||||
|
||||
# s[a..b] -> a fresh NUL-terminated copy of the bytes [a, b), emitted (once) into
|
||||
# any program that slices a string. Mallocs (b-a)+1, copies, terminates.
|
||||
fn emit_str_slice() -> void {
|
||||
|
|
|
|||
|
|
@ -40,16 +40,16 @@ fn emit_assign(st: Node) -> void {
|
|||
}
|
||||
let lt = llty(ty)
|
||||
let rv = emit_expr(st.b)
|
||||
var v = rv.code
|
||||
var v = coerce_code(rv, ty)
|
||||
if (lt == "i8") { v = emit_bind(`trunc i32 {v} to i8`) } # narrow to a byte for p[i] = v
|
||||
if not (st.s == "=") {
|
||||
let cur = emit_bind(`load {lt}, ptr {addr}`)
|
||||
var opc = "add"
|
||||
if (st.s == ("-=")) { opc = "sub" }
|
||||
if (st.s == ("*=")) { opc = "mul" }
|
||||
if (st.s == ("/=")) { opc = "sdiv" }
|
||||
v = emit_bind(`{opc} i32 {cur}, {v}`)
|
||||
v = emit_bind(`{opc} {lt} {cur}, {v}`)
|
||||
}
|
||||
if (lt == "i8") { v = emit_bind(`trunc i32 {v} to i8`) } # narrow to a byte for p[i] = v
|
||||
store_at(lt, v, addr)
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ fn emit_for(st: Node) -> void {
|
|||
fn emit_return(st: Node) -> void {
|
||||
if (st.a != null) {
|
||||
let v = emit_expr(st.a)
|
||||
store_at(llty(ret_ty), v.code, "%retval")
|
||||
store_at(llty(ret_ty), coerce_code(v, ret_ty), "%retval")
|
||||
}
|
||||
emit(" br label %ret\n")
|
||||
g_term = true
|
||||
|
|
@ -231,7 +231,7 @@ fn emit_stmt(st: Node) -> void {
|
|||
var z = "0"
|
||||
if (llty(ty) == "ptr") { z = "null" }
|
||||
store_at(llty(ty), z, slot)
|
||||
} else { let v = emit_expr(st.a); store_at(llty(ty), v.code, slot) }
|
||||
} else { let v = emit_expr(st.a); store_at(llty(ty), coerce_code(v, ty), slot) }
|
||||
loc_push(st.s, slot, ty)
|
||||
loc_set_mut(st.ival)
|
||||
return
|
||||
|
|
|
|||
25479
selfhost/ludicc.seed.ll
25479
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
35
selfhost/tests/hash.ludic
Normal file
35
selfhost/tests/hash.ludic
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
program T {
|
||||
entry {
|
||||
# ---- Hash.* — deterministic non-cryptographic hashing ----
|
||||
# FNV-1a 32-bit (printed as a signed i32).
|
||||
print(Hash.fnv1a("")) # 0x811c9dc5 -> -2128831035
|
||||
print(Hash.fnv1a("a")) # -468965076
|
||||
print(Hash.fnv1a("grass_tile")) # 114400290
|
||||
print(Hash.of("grass_tile")) # Hash.of is the FNV-1a default -> same
|
||||
|
||||
# CRC-32 (IEEE) — known-answer check vectors.
|
||||
print(Hash.crc32("")) # 0
|
||||
print(Hash.crc32("123456789")) # 0xCBF43926 -> -873187034
|
||||
print(Hash.crc32("The quick brown fox jumps over the lazy dog")) # 1095738169
|
||||
|
||||
# fmix32 avalanche of a single int.
|
||||
print(Hash.mix(0)) # 0
|
||||
print(Hash.mix(1)) # 1364076727
|
||||
print(Hash.mix(-1)) # -2114883783
|
||||
|
||||
# combine folds several ints into one seed: deterministic + order-sensitive.
|
||||
print(Hash.combine(123, 456)) # -845898438
|
||||
print(Hash.combine(123, 456)) # same input -> same hash
|
||||
print(Hash.combine(1, 2, 3)) # -78065325
|
||||
print(Hash.combine(3, 2, 1)) # -78057399 (order matters)
|
||||
|
||||
# ---- 64-bit variants (return `long`) ----
|
||||
print(Hash.fnv1a_64("")) # 0xcbf29ce484222325 -> -3750763034362895579
|
||||
print(Hash.fnv1a_64("a")) # 0xaf63dc4c8601ec8c -> -5808556873153909620
|
||||
print(Hash.fnv1a_64("grass_tile")) # -4100651535478758590
|
||||
print(Hash.of64("grass_tile")) # Hash.of64 is the 64-bit default -> same
|
||||
print(Hash.mix64(0)) # 0
|
||||
print(Hash.mix64(1)) # -5451962507482445012
|
||||
print(Hash.mix64(-1)) # 7256831767414464289
|
||||
}
|
||||
}
|
||||
23
selfhost/tests/long.ludic
Normal file
23
selfhost/tests/long.ludic
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
program T {
|
||||
fn wide(a: long, b: int) -> long { return a * b } # int arg widens to i64
|
||||
|
||||
entry {
|
||||
let big: long = 1000000 # int literal widens to a long
|
||||
let sq = big * big # i64 multiply -> 10^12, no overflow
|
||||
print(sq) # 1000000000000
|
||||
print(sq + 1) # 1000000000001
|
||||
var acc: long = 0
|
||||
acc += sq
|
||||
acc += 5 # compound assign in i64
|
||||
print(acc) # 1000000000005
|
||||
print(wide(big, 3000000)) # 3000000000000 (> i32 max)
|
||||
let x: long = big * 3
|
||||
let y = big * 2 # inferred long
|
||||
print(x > y) # 1
|
||||
print(y < x) # 1
|
||||
print(x == x) # 1
|
||||
print(0 - sq) # -1000000000000 (unary negate stays 64-bit)
|
||||
print(big) # 1000000
|
||||
print(Text.length(str(sq))) # 13 — str(long) renders all 13 digits
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue