Found by reading every builtin (Os.platform's 8 KB per call started it). Log builds its line only at or above the threshold and frees it; DateTime.format folds through + so its pieces go; Input.text encodes into one buffer; Path/Mime/Fs/Os free their temporaries on every path; string results of Text/Path/Mime/DateTime/Os dirs are fresh and Text frees a fresh argument. Reseeded. runtime_temps.ludic: 19.8 MB -> 0 over 20,000 rounds, 64 KB -> 0 over 200 of file work; clean under MallocScribble. string_temps still 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
443 lines
22 KiB
Text
443 lines
22 KiB
Text
# emit_datetime.ludic — the calendar/clock half of the stdlib: the Duration.*,
|
|
# Date.* and DateTime.* namespaces (issue #9). Everything is a plain i32 integer
|
|
# epoch, never a float, so it is deterministic and bit-identical on every
|
|
# platform:
|
|
# Duration — a span in whole seconds (Duration.hours(3) == 10800).
|
|
# Date — a civil day, stored as the count of days since 1970-01-01 (UTC),
|
|
# so Date arithmetic is ordinary integer add/subtract.
|
|
# DateTime — an instant, stored as the count of seconds since the 1970 epoch
|
|
# (UTC), matching Time.now().
|
|
# The two civil<->epoch conversions are Howard Hinnant's public-domain algorithms
|
|
# (chrono-compatible, proleptic Gregorian), emitted once per program as the
|
|
# @lp_days_from_civil / @lp_civil_from_days prelude and gated by g_uses_datert.
|
|
# v1 is UTC-only with no leap seconds; instants are assumed non-negative (dates
|
|
# at or after 1970). Timezones, format/parse and a game-controlled simulated
|
|
# clock are tracked follow-ups.
|
|
|
|
# --- Duration.* — spans in whole seconds -------------------------------------
|
|
function is_duration_ns(meth: pointer) -> bool {
|
|
if (meth == "seconds") or (meth == "minutes") or (meth == "hours") or (meth == "days") { return true }
|
|
if (meth == "as_seconds") or (meth == "as_minutes") or (meth == "as_hours") or (meth == "as_days") { return true }
|
|
return false
|
|
}
|
|
|
|
function emit_duration_ns(meth: pointer, e: Node) -> Val {
|
|
let n = emit_expr(e.kids[0])
|
|
if (meth == "seconds") { return val(n.code, "int") } # already seconds
|
|
if (meth == "minutes") { return val(emit_bind(`mul i32 {n.code}, 60`), "int") }
|
|
if (meth == "hours") { return val(emit_bind(`mul i32 {n.code}, 3600`), "int") }
|
|
if (meth == "days") { return val(emit_bind(`mul i32 {n.code}, 86400`), "int") }
|
|
if (meth == "as_seconds") { return val(n.code, "int") }
|
|
if (meth == "as_minutes") { return val(emit_bind(`sdiv i32 {n.code}, 60`), "int") }
|
|
if (meth == "as_hours") { return val(emit_bind(`sdiv i32 {n.code}, 3600`), "int") }
|
|
# as_days: whole days in the span (floor toward zero)
|
|
return val(emit_bind(`sdiv i32 {n.code}, 86400`), "int")
|
|
}
|
|
|
|
# --- Date.* — a civil day as days-since-1970 ---------------------------------
|
|
# load one component (0=year, 1=month, 2=day) of the civil date for epoch-day
|
|
# code `ed` by calling the civil_from_days prelude into three stack slots.
|
|
function date_component(ed: pointer, which: int) -> pointer {
|
|
g_uses_datert = true
|
|
let yp = emit_alloca("i32")
|
|
let mp = emit_alloca("i32")
|
|
let dp = emit_alloca("i32")
|
|
emit(` call void @lp_civil_from_days(i32 {ed}, ptr {yp}, ptr {mp}, ptr {dp})\n`)
|
|
var p = yp
|
|
if (which == 1) { p = mp }
|
|
if (which == 2) { p = dp }
|
|
return emit_bind(`load i32, ptr {p}`)
|
|
}
|
|
|
|
function is_date_ns(meth: pointer) -> bool {
|
|
if (meth == "new") or (meth == "year") or (meth == "month") or (meth == "day") { return true }
|
|
if (meth == "weekday") or (meth == "is_leap") or (meth == "days_in_month") { return true }
|
|
if (meth == "to_epoch") or (meth == "add_days") or (meth == "diff_days") { return true }
|
|
return false
|
|
}
|
|
|
|
function emit_date_ns(meth: pointer, e: Node) -> Val {
|
|
if (meth == "new") { # new(year, month, day) -> Date (epoch-day)
|
|
g_uses_datert = true
|
|
let y = emit_expr(e.kids[0]); let m = emit_expr(e.kids[1]); let d = emit_expr(e.kids[2])
|
|
return val(emit_bind(`call i32 @lp_days_from_civil(i32 {y.code}, i32 {m.code}, i32 {d.code})`), "int")
|
|
}
|
|
if (meth == "year") { let ed = emit_expr(e.kids[0]); return val(date_component(ed.code, 0), "int") }
|
|
if (meth == "month") { let ed = emit_expr(e.kids[0]); return val(date_component(ed.code, 1), "int") }
|
|
if (meth == "day") { let ed = emit_expr(e.kids[0]); return val(date_component(ed.code, 2), "int") }
|
|
if (meth == "weekday") { # 0=Sunday .. 6=Saturday (1970-01-01 was Thursday)
|
|
let ed = emit_expr(e.kids[0])
|
|
let s = emit_bind(`add i32 {ed.code}, 4`) # shift so the epoch's Thursday lands right
|
|
let r = emit_bind(`srem i32 {s}, 7`)
|
|
let rr = emit_bind(`add i32 {r}, 7`) # normalise a negative remainder into [0,7)
|
|
return val(emit_bind(`srem i32 {rr}, 7`), "int")
|
|
}
|
|
if (meth == "is_leap") { # proleptic Gregorian leap-year test
|
|
let y = emit_expr(e.kids[0])
|
|
let m4 = emit_bind(`srem i32 {y.code}, 4`); let c4 = emit_bind(`icmp eq i32 {m4}, 0`)
|
|
let m100 = emit_bind(`srem i32 {y.code}, 100`); let c100 = emit_bind(`icmp ne i32 {m100}, 0`)
|
|
let m400 = emit_bind(`srem i32 {y.code}, 400`); let c400 = emit_bind(`icmp eq i32 {m400}, 0`)
|
|
let common = emit_bind(`and i1 {c4}, {c100}`) # divisible by 4 but not by 100
|
|
let leap = emit_bind(`or i1 {common}, {c400}`) # ...or divisible by 400
|
|
return val(emit_bind(`zext i1 {leap} to i32`), "bool")
|
|
}
|
|
if (meth == "days_in_month") { # length of (year, month) = next month's day 0
|
|
g_uses_datert = true
|
|
let y = emit_expr(e.kids[0]); let m = emit_expr(e.kids[1])
|
|
let this = emit_bind(`call i32 @lp_days_from_civil(i32 {y.code}, i32 {m.code}, i32 1)`)
|
|
let dec = emit_bind(`icmp eq i32 {m.code}, 12`) # December rolls over to next January
|
|
let ny = emit_bind(`add i32 {y.code}, 1`)
|
|
let ny2 = emit_bind(`select i1 {dec}, i32 {ny}, i32 {y.code}`)
|
|
let nm = emit_bind(`add i32 {m.code}, 1`)
|
|
let nm2 = emit_bind(`select i1 {dec}, i32 1, i32 {nm}`)
|
|
let next = emit_bind(`call i32 @lp_days_from_civil(i32 {ny2}, i32 {nm2}, i32 1)`)
|
|
return val(emit_bind(`sub i32 {next}, {this}`), "int")
|
|
}
|
|
if (meth == "to_epoch") { # midnight UTC of the day, as a DateTime instant
|
|
let ed = emit_expr(e.kids[0])
|
|
return val(emit_bind(`mul i32 {ed.code}, 86400`), "int")
|
|
}
|
|
if (meth == "add_days") { # the day `n` days after `ed`
|
|
let ed = emit_expr(e.kids[0]); let n = emit_expr(e.kids[1])
|
|
return val(emit_bind(`add i32 {ed.code}, {n.code}`), "int")
|
|
}
|
|
# diff_days(a, b) -> whole days from b to a (a - b)
|
|
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
|
return val(emit_bind(`sub i32 {a.code}, {b.code}`), "int")
|
|
}
|
|
|
|
# --- DateTime.* — an instant as seconds-since-1970 ---------------------------
|
|
# seconds-of-day for instant `t`, normalised into [0, 86400) even for a negative
|
|
# instant, so hour/minute/second stay correct.
|
|
function dt_secofday(t: pointer) -> pointer {
|
|
let r = emit_bind(`srem i32 {t}, 86400`)
|
|
let rr = emit_bind(`add i32 {r}, 86400`)
|
|
return emit_bind(`srem i32 {rr}, 86400`)
|
|
}
|
|
|
|
# the epoch-day (floored) an instant falls on: floor(t / 86400).
|
|
function dt_epochday(t: pointer) -> pointer {
|
|
let sod = dt_secofday(t) # t - sod is an exact multiple of 86400
|
|
let base = emit_bind(`sub i32 {t}, {sod}`)
|
|
return emit_bind(`sdiv i32 {base}, 86400`)
|
|
}
|
|
|
|
function is_datetime_ns(meth: pointer) -> bool {
|
|
if (meth == "from") or (meth == "date") or (meth == "add") { return true }
|
|
if (meth == "year") or (meth == "month") or (meth == "day") or (meth == "weekday") { return true }
|
|
if (meth == "hour") or (meth == "minute") or (meth == "second") { return true }
|
|
if (meth == "format") or (meth == "parse") { return true }
|
|
return false
|
|
}
|
|
|
|
|
|
# does the pattern have token `tok` (length tlen) starting at index i?
|
|
function dt_tok_at(pat: pointer, n: int, i: int, tok: pointer, tlen: int) -> bool {
|
|
if (i + tlen) > n { return false }
|
|
var k = 0
|
|
while k < tlen { if pat[i + k] != tok[k] { return false }; k += 1 }
|
|
return true
|
|
}
|
|
|
|
# DateTime.format(t, "pattern") -> string. The pattern MUST be a string literal;
|
|
# the tokens YYYY / YY / MM / DD / HH / mm / ss expand to zero-padded fields and
|
|
# every other character is copied through verbatim. Expanded at compile time into
|
|
# a fold of @lp_str_concat over literal runs and @lp_dt_pad0 field conversions.
|
|
function emit_datetime_format(e: Node) -> Val {
|
|
g_uses_datert = true
|
|
g_uses_str = true
|
|
if e.kids[1].kind != E_STR { perr("DateTime.format needs a string-literal pattern") }
|
|
let t = emit_expr(e.kids[0])
|
|
let pat = e.kids[1].s
|
|
let n = len(pat)
|
|
# compute all six components once
|
|
let ed = dt_epochday(t.code)
|
|
let yp = emit_alloca("i32"); let mp = emit_alloca("i32"); let dp = emit_alloca("i32")
|
|
emit(` call void @lp_civil_from_days(i32 {ed}, ptr {yp}, ptr {mp}, ptr {dp})\n`)
|
|
let yv = emit_bind(`load i32, ptr {yp}`)
|
|
let mv = emit_bind(`load i32, ptr {mp}`)
|
|
let dv = emit_bind(`load i32, ptr {dp}`)
|
|
let sod = dt_secofday(t.code)
|
|
let hh = emit_bind(`sdiv i32 {sod}, 3600`)
|
|
let m3 = emit_bind(`srem i32 {sod}, 3600`)
|
|
let mi = emit_bind(`sdiv i32 {m3}, 60`)
|
|
let ss = emit_bind(`srem i32 {sod}, 60`)
|
|
let yy = emit_bind(`srem i32 {yv}, 100`)
|
|
# joined as ordinary text: each field's digits and each piece so far are freed once the next join
|
|
# has copied them, so only the answer is left (every one of them was once kept)
|
|
var acc = val(emit_str_const(""), "string")
|
|
let lit = buf_new()
|
|
var i = 0
|
|
while i < n {
|
|
var field: pointer = null; var width = 0; var tlen = 0
|
|
if dt_tok_at(pat, n, i, "YYYY", 4) { field = yv; width = 4; tlen = 4 }
|
|
else { if dt_tok_at(pat, n, i, "YY", 2) { field = yy; width = 2; tlen = 2 }
|
|
else { if dt_tok_at(pat, n, i, "MM", 2) { field = mv; width = 2; tlen = 2 }
|
|
else { if dt_tok_at(pat, n, i, "DD", 2) { field = dv; width = 2; tlen = 2 }
|
|
else { if dt_tok_at(pat, n, i, "HH", 2) { field = hh; width = 2; tlen = 2 }
|
|
else { if dt_tok_at(pat, n, i, "mm", 2) { field = mi; width = 2; tlen = 2 }
|
|
else { if dt_tok_at(pat, n, i, "ss", 2) { field = ss; width = 2; tlen = 2 } } } } } } }
|
|
if (field == null) { buf_putc(lit, pat[i]); i += 1 }
|
|
else {
|
|
let ls = buf_str(lit)
|
|
if len(ls) > 0 { acc = emit_str_op("+", acc, val(emit_str_const(ls), "string")) }
|
|
let piece = fresh_val(emit_bind(`call ptr @lp_dt_pad0(i32 {field}, i32 {width})`), "string")
|
|
acc = emit_str_op("+", acc, piece)
|
|
lit.len = 0 # start a fresh literal run
|
|
i += tlen
|
|
}
|
|
}
|
|
let tail = buf_str(lit)
|
|
if len(tail) > 0 { acc = emit_str_op("+", acc, val(emit_str_const(tail), "string")) }
|
|
return acc
|
|
}
|
|
|
|
# DateTime.parse(s, "pattern") -> DateTime. The pattern MUST be a string literal;
|
|
# each field is read from a fixed offset (the layout the pattern fixes) and any
|
|
# non-digit where a digit is expected fails the parse, returning -1. Missing
|
|
# fields default to 1970-01-01 00:00:00.
|
|
function emit_datetime_parse(e: Node) -> Val {
|
|
g_uses_datert = true
|
|
if e.kids[1].kind != E_STR { perr("DateTime.parse needs a string-literal pattern") }
|
|
let s = emit_expr(e.kids[0])
|
|
let pat = e.kids[1].s
|
|
let n = len(pat)
|
|
let failp = emit_alloca("i32")
|
|
store_at("i32", "0", failp)
|
|
# component slots, seeded to the epoch defaults
|
|
let yp = emit_alloca("i32"); store_at("i32", "1970", yp)
|
|
let mp = emit_alloca("i32"); store_at("i32", "1", mp)
|
|
let dp = emit_alloca("i32"); store_at("i32", "1", dp)
|
|
let hp = emit_alloca("i32"); store_at("i32", "0", hp)
|
|
let np = emit_alloca("i32"); store_at("i32", "0", np)
|
|
let sp = emit_alloca("i32"); store_at("i32", "0", sp)
|
|
var off = 0
|
|
var i = 0
|
|
while i < n {
|
|
var slot: pointer = null; var width = 0; var tlen = 0
|
|
if dt_tok_at(pat, n, i, "YYYY", 4) { slot = yp; width = 4; tlen = 4 }
|
|
else { if dt_tok_at(pat, n, i, "MM", 2) { slot = mp; width = 2; tlen = 2 }
|
|
else { if dt_tok_at(pat, n, i, "DD", 2) { slot = dp; width = 2; tlen = 2 }
|
|
else { if dt_tok_at(pat, n, i, "HH", 2) { slot = hp; width = 2; tlen = 2 }
|
|
else { if dt_tok_at(pat, n, i, "mm", 2) { slot = np; width = 2; tlen = 2 }
|
|
else { if dt_tok_at(pat, n, i, "ss", 2) { slot = sp; width = 2; tlen = 2 } } } } } }
|
|
if (slot == null) { off += 1; i += 1 }
|
|
else {
|
|
let rd = emit_bind(`call i32 @lp_dt_rd(ptr {s.code}, i32 {itoa(off)}, i32 {width}, ptr {failp})`)
|
|
store_at("i32", rd, slot)
|
|
off += width
|
|
i += tlen
|
|
}
|
|
}
|
|
let y = emit_bind(`load i32, ptr {yp}`)
|
|
let mo = emit_bind(`load i32, ptr {mp}`)
|
|
let d = emit_bind(`load i32, ptr {dp}`)
|
|
let h = emit_bind(`load i32, ptr {hp}`)
|
|
let mn = emit_bind(`load i32, ptr {np}`)
|
|
let sc = emit_bind(`load i32, ptr {sp}`)
|
|
let ed = emit_bind(`call i32 @lp_days_from_civil(i32 {y}, i32 {mo}, i32 {d})`)
|
|
let days = emit_bind(`mul i32 {ed}, 86400`)
|
|
let hs = emit_bind(`mul i32 {h}, 3600`)
|
|
let ns = emit_bind(`mul i32 {mn}, 60`)
|
|
let a1 = emit_bind(`add i32 {days}, {hs}`)
|
|
let a2 = emit_bind(`add i32 {a1}, {ns}`)
|
|
let inst = emit_bind(`add i32 {a2}, {sc}`)
|
|
let failed = emit_bind(`load i32, ptr {failp}`)
|
|
let bad = emit_bind(`icmp ne i32 {failed}, 0`)
|
|
return val(emit_bind(`select i1 {bad}, i32 -1, i32 {inst}`), "int")
|
|
}
|
|
|
|
function emit_datetime_ns(meth: pointer, e: Node) -> Val {
|
|
if (meth == "format") { return emit_datetime_format(e) }
|
|
if (meth == "parse") { return emit_datetime_parse(e) }
|
|
if (meth == "from") { # from(year, month, day, hour, minute, second) -> DateTime
|
|
g_uses_datert = true
|
|
let y = emit_expr(e.kids[0]); let mo = emit_expr(e.kids[1]); let d = emit_expr(e.kids[2])
|
|
let h = emit_expr(e.kids[3]); let mi = emit_expr(e.kids[4]); let s = emit_expr(e.kids[5])
|
|
let ed = emit_bind(`call i32 @lp_days_from_civil(i32 {y.code}, i32 {mo.code}, i32 {d.code})`)
|
|
let days = emit_bind(`mul i32 {ed}, 86400`)
|
|
let hh = emit_bind(`mul i32 {h.code}, 3600`)
|
|
let mm = emit_bind(`mul i32 {mi.code}, 60`)
|
|
let a = emit_bind(`add i32 {days}, {hh}`)
|
|
let b = emit_bind(`add i32 {a}, {mm}`)
|
|
return val(emit_bind(`add i32 {b}, {s.code}`), "int")
|
|
}
|
|
if (meth == "date") { # the calendar day this instant is on -> Date
|
|
let t = emit_expr(e.kids[0])
|
|
return val(dt_epochday(t.code), "int")
|
|
}
|
|
if (meth == "add") { # the instant `dur` seconds after `t`
|
|
let t = emit_expr(e.kids[0]); let dur = emit_expr(e.kids[1])
|
|
return val(emit_bind(`add i32 {t.code}, {dur.code}`), "int")
|
|
}
|
|
if (meth == "year") { let t = emit_expr(e.kids[0]); return val(date_component(dt_epochday(t.code), 0), "int") }
|
|
if (meth == "month") { let t = emit_expr(e.kids[0]); return val(date_component(dt_epochday(t.code), 1), "int") }
|
|
if (meth == "day") { let t = emit_expr(e.kids[0]); return val(date_component(dt_epochday(t.code), 2), "int") }
|
|
if (meth == "weekday") { # 0=Sunday .. 6=Saturday
|
|
let t = emit_expr(e.kids[0])
|
|
let ed = dt_epochday(t.code)
|
|
let s = emit_bind(`add i32 {ed}, 4`)
|
|
let r = emit_bind(`srem i32 {s}, 7`)
|
|
let rr = emit_bind(`add i32 {r}, 7`)
|
|
return val(emit_bind(`srem i32 {rr}, 7`), "int")
|
|
}
|
|
if (meth == "hour") { # 0..23
|
|
let t = emit_expr(e.kids[0])
|
|
let sod = dt_secofday(t.code)
|
|
return val(emit_bind(`sdiv i32 {sod}, 3600`), "int")
|
|
}
|
|
if (meth == "minute") { # 0..59
|
|
let t = emit_expr(e.kids[0])
|
|
let sod = dt_secofday(t.code)
|
|
let h = emit_bind(`srem i32 {sod}, 3600`)
|
|
return val(emit_bind(`sdiv i32 {h}, 60`), "int")
|
|
}
|
|
# second: 0..59
|
|
let t = emit_expr(e.kids[0])
|
|
let sod = dt_secofday(t.code)
|
|
return val(emit_bind(`srem i32 {sod}, 60`), "int")
|
|
}
|
|
|
|
# emit_datetime_prelude — the two civil<->epoch conversions, emitted once per
|
|
# program that uses Date/DateTime (g_uses_datert). Both are Howard Hinnant's
|
|
# public-domain proleptic-Gregorian algorithms, in pure i32 integer IR, so they
|
|
# are deterministic and bit-identical everywhere. 719468 is the day count from
|
|
# 0000-03-01 to 1970-01-01; 146097 is the days in a 400-year era.
|
|
function emit_datetime_prelude() -> void {
|
|
# @lp_days_from_civil(y, m, d) -> days since 1970-01-01
|
|
emith("define i32 @lp_days_from_civil(i32 %y0, i32 %m, i32 %d) {\n")
|
|
emith(" %mle2 = icmp sle i32 %m, 2\n")
|
|
emith(" %ysub = select i1 %mle2, i32 1, i32 0\n")
|
|
emith(" %y = sub i32 %y0, %ysub\n")
|
|
emith(" %yneg = icmp slt i32 %y, 0\n")
|
|
emith(" %ym399 = sub i32 %y, 399\n")
|
|
emith(" %enum = select i1 %yneg, i32 %ym399, i32 %y\n")
|
|
emith(" %era = sdiv i32 %enum, 400\n")
|
|
emith(" %era400 = mul i32 %era, 400\n")
|
|
emith(" %yoe = sub i32 %y, %era400\n")
|
|
emith(" %mgt2 = icmp sgt i32 %m, 2\n")
|
|
emith(" %mshift = select i1 %mgt2, i32 -3, i32 9\n")
|
|
emith(" %mm = add i32 %m, %mshift\n")
|
|
emith(" %t153 = mul i32 153, %mm\n")
|
|
emith(" %t153b = add i32 %t153, 2\n")
|
|
emith(" %doy0 = sdiv i32 %t153b, 5\n")
|
|
emith(" %doy1 = add i32 %doy0, %d\n")
|
|
emith(" %doy = sub i32 %doy1, 1\n")
|
|
emith(" %yoe365 = mul i32 %yoe, 365\n")
|
|
emith(" %yoe4 = sdiv i32 %yoe, 4\n")
|
|
emith(" %yoe100 = sdiv i32 %yoe, 100\n")
|
|
emith(" %doe0 = add i32 %yoe365, %yoe4\n")
|
|
emith(" %doe1 = sub i32 %doe0, %yoe100\n")
|
|
emith(" %doe = add i32 %doe1, %doy\n")
|
|
emith(" %e146097 = mul i32 %era, 146097\n")
|
|
emith(" %r0 = add i32 %e146097, %doe\n")
|
|
emith(" %r = sub i32 %r0, 719468\n")
|
|
emith(" ret i32 %r\n")
|
|
emith("}\n")
|
|
# @lp_civil_from_days(z0, yp, mp, dp): write the civil (year, month, day) of
|
|
# the epoch-day z0 through the three out-pointers.
|
|
emith("define void @lp_civil_from_days(i32 %z0, ptr %yp, ptr %mp, ptr %dp) {\n")
|
|
emith(" %z = add i32 %z0, 719468\n")
|
|
emith(" %zneg = icmp slt i32 %z, 0\n")
|
|
emith(" %zm = sub i32 %z, 146096\n")
|
|
emith(" %enum = select i1 %zneg, i32 %zm, i32 %z\n")
|
|
emith(" %era = sdiv i32 %enum, 146097\n")
|
|
emith(" %era146097 = mul i32 %era, 146097\n")
|
|
emith(" %doe = sub i32 %z, %era146097\n")
|
|
emith(" %d1460 = sdiv i32 %doe, 1460\n")
|
|
emith(" %d36524 = sdiv i32 %doe, 36524\n")
|
|
emith(" %d146096 = sdiv i32 %doe, 146096\n")
|
|
emith(" %ya = sub i32 %doe, %d1460\n")
|
|
emith(" %yb = add i32 %ya, %d36524\n")
|
|
emith(" %yc = sub i32 %yb, %d146096\n")
|
|
emith(" %yoe = sdiv i32 %yc, 365\n")
|
|
emith(" %era400 = mul i32 %era, 400\n")
|
|
emith(" %yy = add i32 %yoe, %era400\n")
|
|
emith(" %yoe365 = mul i32 %yoe, 365\n")
|
|
emith(" %yoe4 = sdiv i32 %yoe, 4\n")
|
|
emith(" %yoe100 = sdiv i32 %yoe, 100\n")
|
|
emith(" %sub0 = add i32 %yoe365, %yoe4\n")
|
|
emith(" %sub1 = sub i32 %sub0, %yoe100\n")
|
|
emith(" %doy = sub i32 %doe, %sub1\n")
|
|
emith(" %fivedoy = mul i32 5, %doy\n")
|
|
emith(" %fivedoy2 = add i32 %fivedoy, 2\n")
|
|
emith(" %mpv = sdiv i32 %fivedoy2, 153\n")
|
|
emith(" %m153 = mul i32 153, %mpv\n")
|
|
emith(" %m153b = add i32 %m153, 2\n")
|
|
emith(" %m153c = sdiv i32 %m153b, 5\n")
|
|
emith(" %dd0 = sub i32 %doy, %m153c\n")
|
|
emith(" %dd = add i32 %dd0, 1\n")
|
|
emith(" %mplt10 = icmp slt i32 %mpv, 10\n")
|
|
emith(" %mplus3 = add i32 %mpv, 3\n")
|
|
emith(" %mminus9 = sub i32 %mpv, 9\n")
|
|
emith(" %mfin = select i1 %mplt10, i32 %mplus3, i32 %mminus9\n")
|
|
emith(" %mmle2 = icmp sle i32 %mfin, 2\n")
|
|
emith(" %yinc = select i1 %mmle2, i32 1, i32 0\n")
|
|
emith(" %yfin = add i32 %yy, %yinc\n")
|
|
emith(" store i32 %yfin, ptr %yp\n")
|
|
emith(" store i32 %mfin, ptr %mp\n")
|
|
emith(" store i32 %dd, ptr %dp\n")
|
|
emith(" ret void\n")
|
|
emith("}\n")
|
|
# @lp_dt_pad0(v, w): a fresh, malloc'd, w-digit zero-padded decimal of v
|
|
# (v assumed non-negative). Backs DateTime.format's numeric fields.
|
|
emith("define ptr @lp_dt_pad0(i32 %v, i32 %w) {\n")
|
|
emith(" %we = zext i32 %w to i64\n")
|
|
emith(" %sz = add i64 %we, 1\n")
|
|
emith(" %buf = call ptr @malloc(i64 %sz)\n")
|
|
emith(" %endp = getelementptr i8, ptr %buf, i32 %w\n")
|
|
emith(" store i8 0, ptr %endp\n")
|
|
emith(" %kp = alloca i32\n %vp = alloca i32\n")
|
|
emith(" %km1 = sub i32 %w, 1\n store i32 %km1, ptr %kp\n store i32 %v, ptr %vp\n")
|
|
emith(" br label %loop\n")
|
|
emith("loop:\n %k = load i32, ptr %kp\n %kok = icmp sge i32 %k, 0\n br i1 %kok, label %body, label %done\n")
|
|
emith("body:\n %vv = load i32, ptr %vp\n %d = srem i32 %vv, 10\n %ch = add i32 %d, 48\n %ch8 = trunc i32 %ch to i8\n")
|
|
emith(" %cp = getelementptr i8, ptr %buf, i32 %k\n store i8 %ch8, ptr %cp\n")
|
|
emith(" %vn = sdiv i32 %vv, 10\n store i32 %vn, ptr %vp\n %kn = sub i32 %k, 1\n store i32 %kn, ptr %kp\n br label %loop\n")
|
|
emith("done:\n ret ptr %buf\n}\n")
|
|
# @lp_dt_rd(s, off, w, failp): read w decimal digits of s starting at off into an
|
|
# int; on any non-digit set *failp = 1. Backs DateTime.parse's fixed-width fields.
|
|
emith("define i32 @lp_dt_rd(ptr %s, i32 %off, i32 %w, ptr %failp) {\n")
|
|
emith(" %accp = alloca i32\n store i32 0, ptr %accp\n %kp = alloca i32\n store i32 0, ptr %kp\n")
|
|
emith(" br label %loop\n")
|
|
emith("loop:\n %k = load i32, ptr %kp\n %kok = icmp slt i32 %k, %w\n br i1 %kok, label %body, label %done\n")
|
|
emith("body:\n %idx = add i32 %off, %k\n %cp = getelementptr i8, ptr %s, i32 %idx\n %c8 = load i8, ptr %cp\n %c = sext i8 %c8 to i32\n")
|
|
emith(" %zero = icmp eq i32 %c, 0\n br i1 %zero, label %stop, label %digit\n") # stop at the terminator, never read past it
|
|
emith("stop:\n store i32 1, ptr %failp\n br label %done\n")
|
|
emith("digit:\n %lt = icmp slt i32 %c, 48\n %gt = icmp sgt i32 %c, 57\n %isbad = or i1 %lt, %gt\n br i1 %isbad, label %fail, label %ok\n")
|
|
emith("fail:\n store i32 1, ptr %failp\n br label %next\n")
|
|
emith("ok:\n %acc0 = load i32, ptr %accp\n %m10 = mul i32 %acc0, 10\n %dg = sub i32 %c, 48\n %acc1 = add i32 %m10, %dg\n store i32 %acc1, ptr %accp\n br label %next\n")
|
|
emith("next:\n %kn = add i32 %k, 1\n store i32 %kn, ptr %kp\n br label %loop\n")
|
|
emith("done:\n %r = load i32, ptr %accp\n ret i32 %r\n}\n")
|
|
}
|
|
|
|
# --- Clock.* — a game-controlled simulated clock -----------------------------
|
|
# @L_clock (declared in emit_head, universal) is a plain seconds counter the game
|
|
# owns: unlike Time.now / Time.since, it never touches the wall clock, so gameplay
|
|
# that reads Clock.now() is deterministic and replay-safe. Advance it however the
|
|
# simulation dictates (e.g. by Duration.* each tick), or set it outright.
|
|
function is_clock_ns(meth: pointer) -> bool {
|
|
if (meth == "now") or (meth == "set") or (meth == "advance") or (meth == "reset") { return true }
|
|
return false
|
|
}
|
|
|
|
function emit_clock_ns(meth: pointer, e: Node) -> Val {
|
|
if (meth == "now") { # the current simulated instant
|
|
return val(emit_bind("load i32, ptr @L_clock"), "int")
|
|
}
|
|
if (meth == "reset") { # back to the epoch (0)
|
|
emit(" store i32 0, ptr @L_clock\n")
|
|
return val("0", "void")
|
|
}
|
|
if (meth == "set") { # set the clock to instant t
|
|
let t = emit_expr(e.kids[0])
|
|
emit(` store i32 {t.code}, ptr @L_clock\n`)
|
|
return val("0", "void")
|
|
}
|
|
# advance: move the clock forward by a Duration (seconds)
|
|
let d = emit_expr(e.kids[0])
|
|
let cur = emit_bind("load i32, ptr @L_clock")
|
|
let nv = emit_bind(`add i32 {cur}, {d.code}`)
|
|
emit(` store i32 {nv}, ptr @L_clock\n`)
|
|
return val("0", "void")
|
|
}
|