feat(stdlib): finish Crypto (CSPRNG + base64) and add Uuid.* library (#19 #16)
All checks were successful
docs / build-and-deploy (push) Successful in 2s
All checks were successful
docs / build-and-deploy (push) Successful in 2s
Crypto (#19): add the OS cryptographically-secure random surface (random_bytes/random_hex/random_u32, reading /dev/urandom) and a standard base64 encoder, completing the library alongside the existing SHA-256/ HMAC-SHA256/verify_hmac/hex/ct_equal. All pure integer IR, C-free. Uuid (#16): a new namespace for stable, collision-free IDs — v4 (random) and v7 (time-ordered) generation, plus parse/is_valid/to_text/equals/nil. UUIDs are canonical lowercase 36-char strings; v4 and v7's random tail draw from the crypto CSPRNG, so both carry the documented determinism caveat (mint at the edges, never inside lockstep simulation). Reuses the crypto prelude's fn_secure_bytes / fn_hex_encode. - examples/library/{crypto,uuid}.ludic: known-answer vectors (SHA-256, HMAC, base64 per RFC 4231/4648) and structural invariants (uuid version/variant bits, parse/equals), wired into `x test` (now 51 passed). - docs: per-symbol pages for every new method + a new Uuid section; inventory and impl-vs-docs coverage check pass. - seed regenerated; `x bootstrap-cfree` fixpoint holds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
a422ef4375
commit
2ddf830f0b
26 changed files with 12185 additions and 10720 deletions
|
|
@ -26,6 +26,7 @@ var g_uses_textrt: bool = false # Text.upper/lower/trim/repeat/pad was emitted
|
|||
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_cryptort: bool = false # Crypto.* was emitted -> emit the SHA-256 / HMAC runtime
|
||||
var g_uses_uuidrt: bool = false # Uuid.* was emitted -> emit the UUID runtime (needs the crypto CSPRNG)
|
||||
var g_uses_datert: bool = false # Date.*/DateTime.* was emitted -> emit the civil<->epoch conversions
|
||||
var g_uses_longstr: bool = false # string(long) / interpolating a long was emitted -> emit fn_long_str
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,16 @@
|
|||
# in constant time -> bool (the tamper check)
|
||||
# Crypto.hex(s) lowercase hex of the bytes of `s`
|
||||
# Crypto.ct_equal(a, b) constant-time string equality (for secrets/MACs)
|
||||
# Crypto.random_bytes(n) n bytes from the OS CSPRNG -> 2n-char hex string
|
||||
# Crypto.random_hex(n) alias for random_bytes (explicit about the return)
|
||||
# Crypto.random_u32() one CSPRNG-drawn 32-bit int (tokens, non-sim seeds)
|
||||
# Crypto.base64(s) standard base64 (RFC 4648) of the bytes of `s`
|
||||
#
|
||||
# The secure-random helpers read the operating system CSPRNG (/dev/urandom) and
|
||||
# are deliberately NON-deterministic — never seed the lockstep simulation RNG
|
||||
# from them (that is `Random.*`). They are for tokens, nonces, and UUIDs, whose
|
||||
# whole point is unpredictability. On a target without /dev/urandom (e.g. wasm)
|
||||
# the read yields zeroes; a real CSPRNG binding is left to the platform layer.
|
||||
#
|
||||
# This is a well-specified standard algorithm (FIPS 180-4 / RFC 2104), implemented
|
||||
# from scratch in plain integer IR: no libc crypto, no allocation-order or
|
||||
|
|
@ -26,6 +36,8 @@
|
|||
function is_crypto_ns(meth: pointer) -> bool {
|
||||
if (meth == "sha256") or (meth == "hmac_sha256") or (meth == "verify_hmac") { return true }
|
||||
if (meth == "hex") or (meth == "ct_equal") { return true }
|
||||
if (meth == "random_bytes") or (meth == "random_hex") or (meth == "random_u32") { return true }
|
||||
if (meth == "base64") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -47,6 +59,21 @@ function emit_crypto_ns(meth: pointer, e: Node) -> Val {
|
|||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
||||
return val(emit_bind(`call i32 @fn_ct_streq(ptr {a.code}, ptr {b.code})`), "bool")
|
||||
}
|
||||
# random_bytes(n) / random_hex(n): n bytes from the OS CSPRNG, returned as a
|
||||
# 2n-char lowercase hex string. A digest of raw bytes can contain NUL and a
|
||||
# `str` is NUL-terminated, so the secure-random surface is hex like the digests.
|
||||
if (meth == "random_bytes") or (meth == "random_hex") {
|
||||
let n = emit_expr(e.kids[0])
|
||||
let n64 = emit_bind(`sext i32 {n.code} to i64`)
|
||||
return val(emit_bind(`call ptr @fn_random_hex(i64 {n64})`), "string")
|
||||
}
|
||||
if (meth == "random_u32") { # one CSPRNG-drawn 32-bit int
|
||||
return val(emit_bind(`call i32 @fn_random_u32()`), "int")
|
||||
}
|
||||
if (meth == "base64") { # standard base64 (RFC 4648) of a string's bytes
|
||||
let s = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call ptr @fn_base64(ptr {s.code})`), "string")
|
||||
}
|
||||
# verify_hmac(key, msg, mac): recompute HMAC-SHA256(key, msg) and compare it to
|
||||
# the supplied hex `mac` in constant time. This is the safe way to check a MAC —
|
||||
# `==` would leak, byte by byte, how much of a forged MAC was correct.
|
||||
|
|
@ -253,4 +280,72 @@ function emit_crypto_prelude() -> void {
|
|||
emith("c:\n %i = load i64, ptr %ip\n %lt = icmp ult i64 %i, %la\n br i1 %lt, label %bdy, label %d\n")
|
||||
emith("bdy:\n %pa = getelementptr i8, ptr %a, i64 %i\n %va = load i8, ptr %pa\n %pb = getelementptr i8, ptr %b, i64 %i\n %vb = load i8, ptr %pb\n %x = xor i8 %va, %vb\n %xz = zext i8 %x to i32\n %ac = load i32, ptr %accp\n %ao = or i32 %ac, %xz\n store i32 %ao, ptr %accp\n %in = add i64 %i, 1\n store i64 %in, ptr %ip\n br label %c\n")
|
||||
emith("d:\n %finv = load i32, ptr %accp\n %z = icmp eq i32 %finv, 0\n %r = zext i1 %z to i32\n ret i32 %r\n}\n")
|
||||
|
||||
emit_secure_rand_prelude()
|
||||
}
|
||||
|
||||
# emit_secure_rand_prelude — the OS-CSPRNG surface shared by Crypto.random_* and
|
||||
# the Uuid.* library: read raw bytes from /dev/urandom, and a base64 encoder.
|
||||
# Emitted as part of the crypto prelude (both Crypto.* and Uuid.* set
|
||||
# g_uses_cryptort), so hex_encode above is always in scope here.
|
||||
function emit_secure_rand_prelude() -> void {
|
||||
emith("@.ludic_urandom = private unnamed_addr constant [13 x i8] c\"/dev/urandom\\00\"\n")
|
||||
emith("@.ludic_rbmode = private unnamed_addr constant [3 x i8] c\"rb\\00\"\n")
|
||||
emith("@.ludic_b64tab = private unnamed_addr constant [64 x i8] c\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"\n")
|
||||
|
||||
# fill %n bytes at %out from the OS CSPRNG. If /dev/urandom cannot be opened the
|
||||
# buffer is zeroed (documented degraded mode — e.g. wasm), never left uninit.
|
||||
emith("define void @fn_secure_bytes(ptr %out, i64 %n) {\n")
|
||||
emith("entry:\n call ptr @memset(ptr %out, i32 0, i64 %n)\n")
|
||||
emith(" %fp = call ptr @fopen(ptr @.ludic_urandom, ptr @.ludic_rbmode)\n")
|
||||
emith(" %isnull = icmp eq ptr %fp, null\n br i1 %isnull, label %fail, label %ok\n")
|
||||
emith("ok:\n %rd = call i64 @fread(ptr %out, i64 1, i64 %n, ptr %fp)\n %cl = call i32 @fclose(ptr %fp)\n ret void\n")
|
||||
emith("fail:\n ret void\n}\n")
|
||||
|
||||
# %n secure bytes -> a fresh 2n-char lowercase hex string
|
||||
emith("define ptr @fn_random_hex(i64 %n) {\n")
|
||||
emith("entry:\n %buf = call ptr @malloc(i64 %n)\n call void @fn_secure_bytes(ptr %buf, i64 %n)\n")
|
||||
emith(" %hex = call ptr @fn_hex_encode(ptr %buf, i64 %n)\n call void @free(ptr %buf)\n ret ptr %hex\n}\n")
|
||||
|
||||
# one CSPRNG-drawn i32 (little-endian assembly of four secure bytes)
|
||||
emith("define i32 @fn_random_u32() {\n")
|
||||
emith("entry:\n %b = alloca [4 x i8]\n %bp = getelementptr [4 x i8], ptr %b, i64 0, i64 0\n call void @fn_secure_bytes(ptr %bp, i64 4)\n")
|
||||
emith(" %p0 = getelementptr i8, ptr %bp, i64 0\n %c0 = load i8, ptr %p0\n %z0 = zext i8 %c0 to i32\n")
|
||||
emith(" %p1 = getelementptr i8, ptr %bp, i64 1\n %c1 = load i8, ptr %p1\n %z1 = zext i8 %c1 to i32\n %s1 = shl i32 %z1, 8\n")
|
||||
emith(" %p2 = getelementptr i8, ptr %bp, i64 2\n %c2 = load i8, ptr %p2\n %z2 = zext i8 %c2 to i32\n %s2 = shl i32 %z2, 16\n")
|
||||
emith(" %p3 = getelementptr i8, ptr %bp, i64 3\n %c3 = load i8, ptr %p3\n %z3 = zext i8 %c3 to i32\n %s3 = shl i32 %z3, 24\n")
|
||||
emith(" %o1 = or i32 %z0, %s1\n %o2 = or i32 %o1, %s2\n %o3 = or i32 %o2, %s3\n ret i32 %o3\n}\n")
|
||||
|
||||
# standard base64 (RFC 4648, '+' '/' alphabet, '=' padding). The input is copied
|
||||
# into a zero-padded buffer rounded up to a multiple of 3, so the 3-byte group
|
||||
# loop never reads past the string; trailing '=' are written per the remainder.
|
||||
emith("define ptr @fn_base64(ptr %s) {\n")
|
||||
emith("entry:\n %n = call i64 @strlen(ptr %s)\n")
|
||||
emith(" %n2 = add i64 %n, 2\n %grp = udiv i64 %n2, 3\n %bufn = mul i64 %grp, 3\n")
|
||||
emith(" %olen = mul i64 %grp, 4\n %olen1 = add i64 %olen, 1\n %out = call ptr @malloc(i64 %olen1)\n")
|
||||
emith(" %inbuf = call ptr @malloc(i64 %bufn)\n call ptr @memset(ptr %inbuf, i32 0, i64 %bufn)\n call ptr @memcpy(ptr %inbuf, ptr %s, i64 %n)\n")
|
||||
emith(" %gp = alloca i64\n store i64 0, ptr %gp\n br label %cond\n")
|
||||
emith("cond:\n %gi = load i64, ptr %gp\n %lt = icmp ult i64 %gi, %grp\n br i1 %lt, label %body, label %pad\n")
|
||||
emith("body:\n %i3 = mul i64 %gi, 3\n")
|
||||
emith(" %ip0 = getelementptr i8, ptr %inbuf, i64 %i3\n %bv0 = load i8, ptr %ip0\n %b0 = zext i8 %bv0 to i32\n")
|
||||
emith(" %i3a = add i64 %i3, 1\n %ip1 = getelementptr i8, ptr %inbuf, i64 %i3a\n %bv1 = load i8, ptr %ip1\n %b1 = zext i8 %bv1 to i32\n")
|
||||
emith(" %i3b = add i64 %i3, 2\n %ip2 = getelementptr i8, ptr %inbuf, i64 %i3b\n %bv2 = load i8, ptr %ip2\n %b2 = zext i8 %bv2 to i32\n")
|
||||
emith(" %e0 = lshr i32 %b0, 2\n")
|
||||
emith(" %b0l = and i32 %b0, 3\n %b0s = shl i32 %b0l, 4\n %b1h = lshr i32 %b1, 4\n %e1 = or i32 %b0s, %b1h\n")
|
||||
emith(" %b1l = and i32 %b1, 15\n %b1s = shl i32 %b1l, 2\n %b2h = lshr i32 %b2, 6\n %e2 = or i32 %b1s, %b2h\n")
|
||||
emith(" %e3 = and i32 %b2, 63\n")
|
||||
emith(" %o4 = mul i64 %gi, 4\n")
|
||||
emith(" %e0z = zext i32 %e0 to i64\n %e1z = zext i32 %e1 to i64\n %e2z = zext i32 %e2 to i64\n %e3z = zext i32 %e3 to i64\n")
|
||||
emith(" %g0 = getelementptr [64 x i8], ptr @.ludic_b64tab, i64 0, i64 %e0z\n %ch0 = load i8, ptr %g0\n %op0 = getelementptr i8, ptr %out, i64 %o4\n store i8 %ch0, ptr %op0\n")
|
||||
emith(" %o4a = add i64 %o4, 1\n %g1 = getelementptr [64 x i8], ptr @.ludic_b64tab, i64 0, i64 %e1z\n %ch1 = load i8, ptr %g1\n %op1 = getelementptr i8, ptr %out, i64 %o4a\n store i8 %ch1, ptr %op1\n")
|
||||
emith(" %o4b = add i64 %o4, 2\n %g2 = getelementptr [64 x i8], ptr @.ludic_b64tab, i64 0, i64 %e2z\n %ch2 = load i8, ptr %g2\n %op2 = getelementptr i8, ptr %out, i64 %o4b\n store i8 %ch2, ptr %op2\n")
|
||||
emith(" %o4c = add i64 %o4, 3\n %g3 = getelementptr [64 x i8], ptr @.ludic_b64tab, i64 0, i64 %e3z\n %ch3 = load i8, ptr %g3\n %op3 = getelementptr i8, ptr %out, i64 %o4c\n store i8 %ch3, ptr %op3\n")
|
||||
emith(" %gin = add i64 %gi, 1\n store i64 %gin, ptr %gp\n br label %cond\n")
|
||||
emith("pad:\n %rem = urem i64 %n, 3\n %r1 = icmp eq i64 %rem, 1\n %r2 = icmp eq i64 %rem, 2\n")
|
||||
emith(" %eq = getelementptr i8, ptr %out, i64 %olen\n store i8 0, ptr %eq\n")
|
||||
emith(" br i1 %r1, label %pad1, label %maybe2\n")
|
||||
emith("pad1:\n %pm1 = sub i64 %olen, 1\n %pp1 = getelementptr i8, ptr %out, i64 %pm1\n store i8 61, ptr %pp1\n %pm2 = sub i64 %olen, 2\n %pp2 = getelementptr i8, ptr %out, i64 %pm2\n store i8 61, ptr %pp2\n br label %done\n")
|
||||
emith("maybe2:\n br i1 %r2, label %pad2, label %done\n")
|
||||
emith("pad2:\n %qm1 = sub i64 %olen, 1\n %qp1 = getelementptr i8, ptr %out, i64 %qm1\n store i8 61, ptr %qp1\n br label %done\n")
|
||||
emith("done:\n call void @free(ptr %inbuf)\n ret ptr %out\n}\n")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,7 +104,8 @@ function emit_program() -> void {
|
|||
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
|
||||
if g_uses_cryptort { emit_crypto_prelude() } # @fn_sha256_hex / @fn_hmac_sha256_hex + constant-time compare
|
||||
if g_uses_cryptort { emit_crypto_prelude() } # @fn_sha256_hex / @fn_hmac_sha256_hex + constant-time compare + CSPRNG
|
||||
if g_uses_uuidrt { emit_uuid_prelude() } # @fn_uuid_v4 / @fn_uuid_v7 / parse / equals (over the crypto CSPRNG)
|
||||
if g_uses_datert { emit_datetime_prelude() } # @fn_days_from_civil / @fn_civil_from_days conversions
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -235,6 +235,10 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
if is_crypto_ns(meth) { return emit_crypto_ns(meth, e) }
|
||||
perr(`unknown builtin Crypto.{meth}`)
|
||||
}
|
||||
if (ns == "Uuid") {
|
||||
if is_uuid_ns(meth) { return emit_uuid_ns(meth, e) }
|
||||
perr(`unknown builtin Uuid.{meth}`)
|
||||
}
|
||||
if (ns == "Vector") {
|
||||
if is_vector_ns(meth) { return emit_vector_ns(meth, e) }
|
||||
perr(`unknown builtin Vector.{meth}`)
|
||||
|
|
|
|||
164
selfhost/emit_uuid.ludic
Normal file
164
selfhost/emit_uuid.ludic
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
# emit_uuid.ludic — the Uuid.* namespace: universally-unique identifiers for
|
||||
# stable IDs that don't collide (players, sessions, networked entities, saved
|
||||
# and shared user content, per-install analytics IDs).
|
||||
#
|
||||
# Uuid.new() a v4 (random) UUID -> canonical 36-char string
|
||||
# Uuid.v4() explicit alias for new()
|
||||
# Uuid.new_v7() a v7 (time-ordered) UUID: the first 48 bits are a Unix-ms
|
||||
# Uuid.v7() timestamp, so v7 IDs sort by creation time (DB/index-friendly)
|
||||
# Uuid.parse(s) normalise an untrusted string -> lowercase UUID, or the nil
|
||||
# UUID if it is not a well-formed UUID (pair with Uuid.is_valid)
|
||||
# Uuid.is_valid(s) is `s` a well-formed UUID? -> bool
|
||||
# Uuid.to_text(id) the canonical text form -> string (identity here)
|
||||
# Uuid.equals(a, b) case-insensitive equality -> bool
|
||||
# Uuid.nil() the all-zero UUID -> "00000000-0000-0000-0000-000000000000"
|
||||
#
|
||||
# A UUID is represented as its canonical lowercase 36-char text form. This is the
|
||||
# form you store, print, send over the wire and compare, so keeping IDs in that
|
||||
# shape avoids a conversion at every boundary; `equals` is case-insensitive so an
|
||||
# upper-case UUID from another system still matches.
|
||||
#
|
||||
# DETERMINISM CAVEAT: v4 and the random tail of v7 come from the OS CSPRNG
|
||||
# (Crypto.random_*), which is deliberately non-deterministic. Minting a UUID
|
||||
# inside the lockstep simulation will desync replays / networked peers — generate
|
||||
# IDs at the edges (on connect, on save, on spawn-from-input), never per tick in
|
||||
# gameplay code that must reproduce.
|
||||
|
||||
function is_uuid_ns(meth: pointer) -> bool {
|
||||
if (meth == "new") or (meth == "v4") or (meth == "new_v7") or (meth == "v7") { return true }
|
||||
if (meth == "parse") or (meth == "is_valid") or (meth == "to_text") { return true }
|
||||
if (meth == "equals") or (meth == "nil") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
function emit_uuid_ns(meth: pointer, e: Node) -> Val {
|
||||
# Uuid.* reuses the crypto prelude's CSPRNG (fn_secure_bytes) and hex encoder
|
||||
# (fn_hex_encode), so pull that runtime in as well as the uuid formatters.
|
||||
g_uses_cryptort = true
|
||||
g_uses_uuidrt = true
|
||||
if (meth == "new") or (meth == "v4") { # v4: 122 random bits
|
||||
return val(emit_bind("call ptr @fn_uuid_v4()"), "string")
|
||||
}
|
||||
if (meth == "new_v7") or (meth == "v7") { # v7: ms timestamp + random
|
||||
return val(emit_bind("call ptr @fn_uuid_v7()"), "string")
|
||||
}
|
||||
if (meth == "nil") { # the all-zero UUID
|
||||
return val(emit_bind("call ptr @fn_uuid_nil()"), "string")
|
||||
}
|
||||
if (meth == "is_valid") { # well-formed UUID? -> bool
|
||||
let s = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call i32 @fn_uuid_valid(ptr {s.code})`), "bool")
|
||||
}
|
||||
if (meth == "to_text") { # already canonical text: identity
|
||||
let s = emit_expr(e.kids[0])
|
||||
return val(s.code, "string")
|
||||
}
|
||||
if (meth == "equals") { # case-insensitive equality -> bool
|
||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
||||
return val(emit_bind(`call i32 @fn_uuid_eq(ptr {a.code}, ptr {b.code})`), "bool")
|
||||
}
|
||||
# parse(s): normalise an untrusted string to a lowercase UUID, or the nil UUID
|
||||
# when it is not well-formed. Callers that must reject bad input should gate on
|
||||
# Uuid.is_valid(s) first; this never faults on garbage.
|
||||
let s = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call ptr @fn_uuid_parse(ptr {s.code})`), "string")
|
||||
}
|
||||
|
||||
# emit_uuid_prelude — the UUID runtime, emitted once per program that uses Uuid.*
|
||||
# (g_uses_uuidrt). All pure integer IR over the crypto prelude's CSPRNG + hex
|
||||
# encoder; format is the canonical 8-4-4-4-12 lowercase text with RFC 4122
|
||||
# version and variant bits set.
|
||||
function emit_uuid_prelude() -> void {
|
||||
# 16 raw bytes -> a fresh canonical 36-char string. hex-encode all 16 bytes,
|
||||
# then splice the four hyphens between the 8/4/4/4/12 groups.
|
||||
emith("define ptr @fn_uuid_format(ptr %b16) {\n")
|
||||
emith("entry:\n %hex = call ptr @fn_hex_encode(ptr %b16, i64 16)\n %out = call ptr @malloc(i64 37)\n")
|
||||
emith(" call ptr @memcpy(ptr %out, ptr %hex, i64 8)\n")
|
||||
emith(" %o8 = getelementptr i8, ptr %out, i64 8\n store i8 45, ptr %o8\n")
|
||||
emith(" %h8 = getelementptr i8, ptr %hex, i64 8\n %o9 = getelementptr i8, ptr %out, i64 9\n call ptr @memcpy(ptr %o9, ptr %h8, i64 4)\n")
|
||||
emith(" %o13 = getelementptr i8, ptr %out, i64 13\n store i8 45, ptr %o13\n")
|
||||
emith(" %h12 = getelementptr i8, ptr %hex, i64 12\n %o14 = getelementptr i8, ptr %out, i64 14\n call ptr @memcpy(ptr %o14, ptr %h12, i64 4)\n")
|
||||
emith(" %o18 = getelementptr i8, ptr %out, i64 18\n store i8 45, ptr %o18\n")
|
||||
emith(" %h16 = getelementptr i8, ptr %hex, i64 16\n %o19 = getelementptr i8, ptr %out, i64 19\n call ptr @memcpy(ptr %o19, ptr %h16, i64 4)\n")
|
||||
emith(" %o23 = getelementptr i8, ptr %out, i64 23\n store i8 45, ptr %o23\n")
|
||||
emith(" %h20 = getelementptr i8, ptr %hex, i64 20\n %o24 = getelementptr i8, ptr %out, i64 24\n call ptr @memcpy(ptr %o24, ptr %h20, i64 12)\n")
|
||||
emith(" %o36 = getelementptr i8, ptr %out, i64 36\n store i8 0, ptr %o36\n")
|
||||
emith(" call void @free(ptr %hex)\n ret ptr %out\n}\n")
|
||||
|
||||
# v4: 16 CSPRNG bytes, then set version (0x4x in byte 6) and variant (0b10xx in
|
||||
# byte 8). 0x80 does not fit an i8 immediate, so it is written as -128.
|
||||
emith("define ptr @fn_uuid_v4() {\n")
|
||||
emith("entry:\n %b = alloca [16 x i8]\n %bp = getelementptr [16 x i8], ptr %b, i64 0, i64 0\n call void @fn_secure_bytes(ptr %bp, i64 16)\n")
|
||||
emith(" %p6 = getelementptr i8, ptr %bp, i64 6\n %v6 = load i8, ptr %p6\n %v6a = and i8 %v6, 15\n %v6b = or i8 %v6a, 64\n store i8 %v6b, ptr %p6\n")
|
||||
emith(" %p8 = getelementptr i8, ptr %bp, i64 8\n %v8 = load i8, ptr %p8\n %v8a = and i8 %v8, 63\n %v8b = or i8 %v8a, -128\n store i8 %v8b, ptr %p8\n")
|
||||
emith(" %s = call ptr @fn_uuid_format(ptr %bp)\n ret ptr %s\n}\n")
|
||||
|
||||
# v7: random fill, then overwrite the first 6 bytes with a 48-bit big-endian
|
||||
# Unix-millisecond timestamp; set version 7 (0x7x) and the variant. Sub-second
|
||||
# resolution is derived from time() seconds * 1000 — monotonic per second, with
|
||||
# the random tail keeping same-millisecond IDs distinct.
|
||||
emith("define ptr @fn_uuid_v7() {\n")
|
||||
emith("entry:\n %b = alloca [16 x i8]\n %bp = getelementptr [16 x i8], ptr %b, i64 0, i64 0\n call void @fn_secure_bytes(ptr %bp, i64 16)\n")
|
||||
emith(" %t = call i64 @time(ptr null)\n %ms = mul i64 %t, 1000\n")
|
||||
emith(" %s40 = lshr i64 %ms, 40\n %t0 = trunc i64 %s40 to i8\n %q0 = getelementptr i8, ptr %bp, i64 0\n store i8 %t0, ptr %q0\n")
|
||||
emith(" %s32 = lshr i64 %ms, 32\n %t1 = trunc i64 %s32 to i8\n %q1 = getelementptr i8, ptr %bp, i64 1\n store i8 %t1, ptr %q1\n")
|
||||
emith(" %s24 = lshr i64 %ms, 24\n %t2 = trunc i64 %s24 to i8\n %q2 = getelementptr i8, ptr %bp, i64 2\n store i8 %t2, ptr %q2\n")
|
||||
emith(" %s16 = lshr i64 %ms, 16\n %t3 = trunc i64 %s16 to i8\n %q3 = getelementptr i8, ptr %bp, i64 3\n store i8 %t3, ptr %q3\n")
|
||||
emith(" %s8 = lshr i64 %ms, 8\n %t4 = trunc i64 %s8 to i8\n %q4 = getelementptr i8, ptr %bp, i64 4\n store i8 %t4, ptr %q4\n")
|
||||
emith(" %t5 = trunc i64 %ms to i8\n %q5 = getelementptr i8, ptr %bp, i64 5\n store i8 %t5, ptr %q5\n")
|
||||
emith(" %p6 = getelementptr i8, ptr %bp, i64 6\n %v6 = load i8, ptr %p6\n %v6a = and i8 %v6, 15\n %v6b = or i8 %v6a, 112\n store i8 %v6b, ptr %p6\n")
|
||||
emith(" %p8 = getelementptr i8, ptr %bp, i64 8\n %v8 = load i8, ptr %p8\n %v8a = and i8 %v8, 63\n %v8b = or i8 %v8a, -128\n store i8 %v8b, ptr %p8\n")
|
||||
emith(" %s = call ptr @fn_uuid_format(ptr %bp)\n ret ptr %s\n}\n")
|
||||
|
||||
# the nil UUID: 36 '0' with hyphens spliced in
|
||||
emith("define ptr @fn_uuid_nil() {\n")
|
||||
emith("entry:\n %out = call ptr @malloc(i64 37)\n call ptr @memset(ptr %out, i32 48, i64 36)\n")
|
||||
emith(" %o8 = getelementptr i8, ptr %out, i64 8\n store i8 45, ptr %o8\n")
|
||||
emith(" %o13 = getelementptr i8, ptr %out, i64 13\n store i8 45, ptr %o13\n")
|
||||
emith(" %o18 = getelementptr i8, ptr %out, i64 18\n store i8 45, ptr %o18\n")
|
||||
emith(" %o23 = getelementptr i8, ptr %out, i64 23\n store i8 45, ptr %o23\n")
|
||||
emith(" %o36 = getelementptr i8, ptr %out, i64 36\n store i8 0, ptr %o36\n ret ptr %out\n}\n")
|
||||
|
||||
# is %s a well-formed UUID? length 36, hyphens at 8/13/18/23, hex elsewhere.
|
||||
emith("define i32 @fn_uuid_valid(ptr %s) {\n")
|
||||
emith("entry:\n %n = call i64 @strlen(ptr %s)\n %ne = icmp eq i64 %n, 36\n br i1 %ne, label %go, label %bad\n")
|
||||
emith("go:\n %ip = alloca i64\n store i64 0, ptr %ip\n br label %cond\n")
|
||||
emith("cond:\n %i = load i64, ptr %ip\n %lt = icmp ult i64 %i, 36\n br i1 %lt, label %body, label %good\n")
|
||||
emith("body:\n %p = getelementptr i8, ptr %s, i64 %i\n %c = load i8, ptr %p\n")
|
||||
emith(" %h8 = icmp eq i64 %i, 8\n %h13 = icmp eq i64 %i, 13\n %h18 = icmp eq i64 %i, 18\n %h23 = icmp eq i64 %i, 23\n")
|
||||
emith(" %hx = or i1 %h8, %h13\n %hy = or i1 %hx, %h18\n %hyph = or i1 %hy, %h23\n br i1 %hyph, label %ckhyph, label %ckhex\n")
|
||||
emith("ckhyph:\n %ish = icmp eq i8 %c, 45\n br i1 %ish, label %next, label %bad\n")
|
||||
emith("ckhex:\n")
|
||||
emith(" %ge0 = icmp uge i8 %c, 48\n %le9 = icmp ule i8 %c, 57\n %isdig = and i1 %ge0, %le9\n")
|
||||
emith(" %gea = icmp uge i8 %c, 97\n %lef = icmp ule i8 %c, 102\n %islo = and i1 %gea, %lef\n")
|
||||
emith(" %geA = icmp uge i8 %c, 65\n %leF = icmp ule i8 %c, 70\n %ishi = and i1 %geA, %leF\n")
|
||||
emith(" %hx1 = or i1 %isdig, %islo\n %ishex = or i1 %hx1, %ishi\n br i1 %ishex, label %next, label %bad\n")
|
||||
emith("next:\n %i1 = add i64 %i, 1\n store i64 %i1, ptr %ip\n br label %cond\n")
|
||||
emith("good:\n ret i32 1\n")
|
||||
emith("bad:\n ret i32 0\n}\n")
|
||||
|
||||
# case-insensitive equality of two null-terminated strings -> i32 bool
|
||||
emith("define i32 @fn_uuid_eq(ptr %a, ptr %b) {\n")
|
||||
emith("entry:\n %la = call i64 @strlen(ptr %a)\n %lb = call i64 @strlen(ptr %b)\n %eq = icmp eq i64 %la, %lb\n br i1 %eq, label %go, label %ne\n")
|
||||
emith("go:\n %ip = alloca i64\n store i64 0, ptr %ip\n br label %cond\n")
|
||||
emith("cond:\n %i = load i64, ptr %ip\n %lt = icmp ult i64 %i, %la\n br i1 %lt, label %body, label %eqret\n")
|
||||
emith("body:\n %pa = getelementptr i8, ptr %a, i64 %i\n %ca = load i8, ptr %pa\n %pb = getelementptr i8, ptr %b, i64 %i\n %cb = load i8, ptr %pb\n")
|
||||
emith(" %caA = icmp uge i8 %ca, 65\n %caZ = icmp ule i8 %ca, 90\n %caup = and i1 %caA, %caZ\n %ca32 = add i8 %ca, 32\n %cal = select i1 %caup, i8 %ca32, i8 %ca\n")
|
||||
emith(" %cbA = icmp uge i8 %cb, 65\n %cbZ = icmp ule i8 %cb, 90\n %cbup = and i1 %cbA, %cbZ\n %cb32 = add i8 %cb, 32\n %cbl = select i1 %cbup, i8 %cb32, i8 %cb\n")
|
||||
emith(" %same = icmp eq i8 %cal, %cbl\n br i1 %same, label %next, label %ne\n")
|
||||
emith("next:\n %i1 = add i64 %i, 1\n store i64 %i1, ptr %ip\n br label %cond\n")
|
||||
emith("eqret:\n ret i32 1\n")
|
||||
emith("ne:\n ret i32 0\n}\n")
|
||||
|
||||
# parse: lowercase-normalise a valid UUID, else return the nil UUID.
|
||||
emith("define ptr @fn_uuid_parse(ptr %s) {\n")
|
||||
emith("entry:\n %ok = call i32 @fn_uuid_valid(ptr %s)\n %isok = icmp ne i32 %ok, 0\n br i1 %isok, label %dup, label %nilb\n")
|
||||
emith("dup:\n %out = call ptr @malloc(i64 37)\n %ip = alloca i64\n store i64 0, ptr %ip\n br label %cond\n")
|
||||
emith("cond:\n %i = load i64, ptr %ip\n %lt = icmp ult i64 %i, 36\n br i1 %lt, label %body, label %fin\n")
|
||||
emith("body:\n %p = getelementptr i8, ptr %s, i64 %i\n %c = load i8, ptr %p\n")
|
||||
emith(" %cA = icmp uge i8 %c, 65\n %cZ = icmp ule i8 %c, 90\n %cup = and i1 %cA, %cZ\n %c32 = add i8 %c, 32\n %cl = select i1 %cup, i8 %c32, i8 %c\n")
|
||||
emith(" %op = getelementptr i8, ptr %out, i64 %i\n store i8 %cl, ptr %op\n")
|
||||
emith(" %i1 = add i64 %i, 1\n store i64 %i1, ptr %ip\n br label %cond\n")
|
||||
emith("fin:\n %o36 = getelementptr i8, ptr %out, i64 36\n store i8 0, ptr %o36\n ret ptr %out\n")
|
||||
emith("nilb:\n %nn = call ptr @fn_uuid_nil()\n ret ptr %nn\n}\n")
|
||||
}
|
||||
22200
selfhost/ludicc.seed.ll
22200
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue