refactor(selfhost): reorganise into concern-based subdirectories
Split the flat 38-file selfhost/ into concern-based subdirectories:
frontend/ lex, parse, parse_game, ast
support/ str, buf, io
backend/ core IR + expression/statement lowering
backend/game/ ECS/scene/event/world lowering
backend/stdlib/ the namespaced Math.*/Text.*/Crypto.*/… intrinsics
and split the three oversized emitters at responsibility boundaries so
no file mixes concerns:
emit_game.ludic -> + emit_world.ludic (reflection world table,
tick helpers, @main synthesis)
emit_expr.ludic -> + emit_call.ludic (namespaced builtins, call
lowering, expr dispatch)
emit_text.ludic -> + emit_text_prelude.ludic (emitted string-builder runtime)
FRAGS in tools/x/selfhost.ludic is updated to the new paths with the link
order preserved, and the Python doc/vocabulary tooling is updated to walk
the new layout. Because the build is a plain in-order concatenation and
every split lands on a blank-line boundary, the regenerated seed is
byte-identical: `x reseed` leaves selfhost/ludicc.seed.ll unchanged,
`x bootstrap-cfree` still reaches its fixed point, and both `x test` (56)
and `x selfhost-test` (29, incl. golden renders) stay green.
Closes #29
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
f55216af50
commit
23726afa90
51 changed files with 780 additions and 771 deletions
236
selfhost/backend/stdlib/emit_color.ludic
Normal file
236
selfhost/backend/stdlib/emit_color.ludic
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
# ============================================================================
|
||||
# emit_color.ludic — the named-color palette, resolved at compile time.
|
||||
#
|
||||
# `Color.Name` in a game lowers to a plain 0xRRGGBB int here: no runtime cost,
|
||||
# no allocation, identical codegen to writing the hex by hand. Unknown names are
|
||||
# a compile error (color_lookup returns -1, which emit_expr reports).
|
||||
#
|
||||
# GENERATED by scratchpad/palette.py from the single source-of-truth palette.
|
||||
# Edit the palette there and regenerate; do not hand-edit this file.
|
||||
# ============================================================================
|
||||
|
||||
function color_lookup(name: pointer) -> int {
|
||||
if (name == "White") { return 0xFFFFFF }
|
||||
if (name == "Snow") { return 0xFFFAFA }
|
||||
if (name == "Ivory") { return 0xFFFFF0 }
|
||||
if (name == "EggShellWhite") { return 0xF0EAD6 }
|
||||
if (name == "FloralWhite") { return 0xFFFAF0 }
|
||||
if (name == "SeaShell") { return 0xFFF5EE }
|
||||
if (name == "Linen") { return 0xFAF0E6 }
|
||||
if (name == "AntiqueWhite") { return 0xFAEBD7 }
|
||||
if (name == "OldLace") { return 0xFDF5E6 }
|
||||
if (name == "Beige") { return 0xF5F5DC }
|
||||
if (name == "Cream") { return 0xFFFDD0 }
|
||||
if (name == "Honeydew") { return 0xF0FFF0 }
|
||||
if (name == "MintCream") { return 0xF5FFFA }
|
||||
if (name == "Azure") { return 0xF0FFFF }
|
||||
if (name == "AliceBlue") { return 0xF0F8FF }
|
||||
if (name == "GhostWhite") { return 0xF8F8FF }
|
||||
if (name == "WhiteSmoke") { return 0xF5F5F5 }
|
||||
if (name == "Lavender") { return 0xE6E6FA }
|
||||
if (name == "Bone") { return 0xE3DAC9 }
|
||||
if (name == "Parchment") { return 0xF1E9D2 }
|
||||
if (name == "Gainsboro") { return 0xDCDCDC }
|
||||
if (name == "LightGray") { return 0xD3D3D3 }
|
||||
if (name == "Silver") { return 0xC0C0C0 }
|
||||
if (name == "Ash") { return 0xB2BEB5 }
|
||||
if (name == "DarkGray") { return 0xA9A9A9 }
|
||||
if (name == "Gray") { return 0x808080 }
|
||||
if (name == "DimGray") { return 0x696969 }
|
||||
if (name == "Nickel") { return 0x727472 }
|
||||
if (name == "Slate") { return 0x708090 }
|
||||
if (name == "SlateGray") { return 0x708090 }
|
||||
if (name == "LightSlateGray") { return 0x778899 }
|
||||
if (name == "Gunmetal") { return 0x2A3439 }
|
||||
if (name == "Charcoal") { return 0x36454F }
|
||||
if (name == "Graphite") { return 0x1C1C1C }
|
||||
if (name == "Onyx") { return 0x353839 }
|
||||
if (name == "Jet") { return 0x343434 }
|
||||
if (name == "Black") { return 0x000000 }
|
||||
if (name == "EerieBlack") { return 0x1B1B1B }
|
||||
if (name == "RaisinBlack") { return 0x242124 }
|
||||
if (name == "Ebony") { return 0x555D50 }
|
||||
if (name == "Red") { return 0xFF0000 }
|
||||
if (name == "Crimson") { return 0xDC143C }
|
||||
if (name == "Scarlet") { return 0xFF2400 }
|
||||
if (name == "Vermilion") { return 0xE34234 }
|
||||
if (name == "FireBrick") { return 0xB22222 }
|
||||
if (name == "Cinnabar") { return 0xE44D2E }
|
||||
if (name == "DarkRed") { return 0x8B0000 }
|
||||
if (name == "Maroon") { return 0x800000 }
|
||||
if (name == "Ruby") { return 0xE0115F }
|
||||
if (name == "Cardinal") { return 0xC41E3A }
|
||||
if (name == "IndianRed") { return 0xCD5C5C }
|
||||
if (name == "Rust") { return 0xB7410E }
|
||||
if (name == "Sangria") { return 0x92000A }
|
||||
if (name == "Redwood") { return 0xA45A52 }
|
||||
if (name == "Cerise") { return 0xDE3163 }
|
||||
if (name == "Amaranth") { return 0xE52B50 }
|
||||
if (name == "Carmine") { return 0x960018 }
|
||||
if (name == "Chestnut") { return 0x954535 }
|
||||
if (name == "Brick") { return 0xCB4154 }
|
||||
if (name == "TerraCotta") { return 0xE2725B }
|
||||
if (name == "Pink") { return 0xFFC0CB }
|
||||
if (name == "LightPink") { return 0xFFB6C1 }
|
||||
if (name == "HotPink") { return 0xFF69B4 }
|
||||
if (name == "DeepPink") { return 0xFF1493 }
|
||||
if (name == "PaleVioletRed") { return 0xDB7093 }
|
||||
if (name == "Rose") { return 0xFF007F }
|
||||
if (name == "Blush") { return 0xDE5D83 }
|
||||
if (name == "Salmon") { return 0xFA8072 }
|
||||
if (name == "LightSalmon") { return 0xFFA07A }
|
||||
if (name == "DarkSalmon") { return 0xE9967A }
|
||||
if (name == "Coral") { return 0xFF7F50 }
|
||||
if (name == "Watermelon") { return 0xFC6C85 }
|
||||
if (name == "Flamingo") { return 0xFC8EAC }
|
||||
if (name == "Bubblegum") { return 0xFFC1CC }
|
||||
if (name == "Fuchsia") { return 0xFF00FF }
|
||||
if (name == "Magenta") { return 0xFF00FF }
|
||||
if (name == "Mauve") { return 0xE0B0FF }
|
||||
if (name == "Puce") { return 0xCC8899 }
|
||||
if (name == "Thistle") { return 0xD8BFD8 }
|
||||
if (name == "Orchid") { return 0xDA70D6 }
|
||||
if (name == "Orange") { return 0xFFA500 }
|
||||
if (name == "DarkOrange") { return 0xFF8C00 }
|
||||
if (name == "Tangerine") { return 0xF28500 }
|
||||
if (name == "Pumpkin") { return 0xFF7518 }
|
||||
if (name == "Apricot") { return 0xFBCEB1 }
|
||||
if (name == "Peach") { return 0xFFE5B4 }
|
||||
if (name == "Cantaloupe") { return 0xFFA62B }
|
||||
if (name == "Amber") { return 0xFFBF00 }
|
||||
if (name == "Bronze") { return 0xCD7F32 }
|
||||
if (name == "Copper") { return 0xB87333 }
|
||||
if (name == "Marigold") { return 0xEAA221 }
|
||||
if (name == "Carrot") { return 0xED9121 }
|
||||
if (name == "Persimmon") { return 0xEC5800 }
|
||||
if (name == "Papaya") { return 0xFF9E2C }
|
||||
if (name == "Sunset") { return 0xFAD6A5 }
|
||||
if (name == "Yellow") { return 0xFFFF00 }
|
||||
if (name == "LightYellow") { return 0xFFFFE0 }
|
||||
if (name == "Gold") { return 0xFFD700 }
|
||||
if (name == "Goldenrod") { return 0xDAA520 }
|
||||
if (name == "Lemon") { return 0xFFF700 }
|
||||
if (name == "Canary") { return 0xFFEF00 }
|
||||
if (name == "Mustard") { return 0xFFDB58 }
|
||||
if (name == "Flax") { return 0xEEDC82 }
|
||||
if (name == "Wheat") { return 0xF5DEB3 }
|
||||
if (name == "Corn") { return 0xFBEC5D }
|
||||
if (name == "Dandelion") { return 0xF0E130 }
|
||||
if (name == "Saffron") { return 0xF4C430 }
|
||||
if (name == "Khaki") { return 0xF0E68C }
|
||||
if (name == "DarkKhaki") { return 0xBDB76B }
|
||||
if (name == "Straw") { return 0xE4D96F }
|
||||
if (name == "Brown") { return 0x8B4513 }
|
||||
if (name == "SaddleBrown") { return 0x8B4513 }
|
||||
if (name == "Sienna") { return 0xA0522D }
|
||||
if (name == "Chocolate") { return 0xD2691E }
|
||||
if (name == "Peru") { return 0xCD853F }
|
||||
if (name == "Tan") { return 0xD2B48C }
|
||||
if (name == "BurlyWood") { return 0xDEB887 }
|
||||
if (name == "Sand") { return 0xC2B280 }
|
||||
if (name == "Coffee") { return 0x6F4E37 }
|
||||
if (name == "Espresso") { return 0x4B3621 }
|
||||
if (name == "Mahogany") { return 0xC04000 }
|
||||
if (name == "Walnut") { return 0x773F1A }
|
||||
if (name == "Umber") { return 0x635147 }
|
||||
if (name == "Sepia") { return 0x704214 }
|
||||
if (name == "Taupe") { return 0x483C32 }
|
||||
if (name == "Fawn") { return 0xE5AA70 }
|
||||
if (name == "Caramel") { return 0xC68E17 }
|
||||
if (name == "Cocoa") { return 0xD2691E }
|
||||
if (name == "Hazel") { return 0x8E7618 }
|
||||
if (name == "Wenge") { return 0x645452 }
|
||||
if (name == "Green") { return 0x008000 }
|
||||
if (name == "Lime") { return 0x00FF00 }
|
||||
if (name == "LimeGreen") { return 0x32CD32 }
|
||||
if (name == "LawnGreen") { return 0x7CFC00 }
|
||||
if (name == "Chartreuse") { return 0x7FFF00 }
|
||||
if (name == "GreenYellow") { return 0xADFF2F }
|
||||
if (name == "SpringGreen") { return 0x00FF7F }
|
||||
if (name == "MintGreen") { return 0x98FF98 }
|
||||
if (name == "SeaGreen") { return 0x2E8B57 }
|
||||
if (name == "MediumSeaGreen") { return 0x3CB371 }
|
||||
if (name == "ForestGreen") { return 0x228B22 }
|
||||
if (name == "DarkGreen") { return 0x006400 }
|
||||
if (name == "OliveDrab") { return 0x6B8E23 }
|
||||
if (name == "Olive") { return 0x808000 }
|
||||
if (name == "Moss") { return 0x8A9A5B }
|
||||
if (name == "Fern") { return 0x4F7942 }
|
||||
if (name == "Emerald") { return 0x50C878 }
|
||||
if (name == "Jade") { return 0x00A86B }
|
||||
if (name == "Malachite") { return 0x0BDA51 }
|
||||
if (name == "Shamrock") { return 0x009E60 }
|
||||
if (name == "Pistachio") { return 0x93C572 }
|
||||
if (name == "Avocado") { return 0x568203 }
|
||||
if (name == "Pine") { return 0x01796F }
|
||||
if (name == "Sage") { return 0x9CAF88 }
|
||||
if (name == "Kelly") { return 0x4CBB17 }
|
||||
if (name == "Hunter") { return 0x355E3B }
|
||||
if (name == "Basil") { return 0x579229 }
|
||||
if (name == "Clover") { return 0x2E8B57 }
|
||||
if (name == "Juniper") { return 0x6D9A79 }
|
||||
if (name == "Neon") { return 0x39FF14 }
|
||||
if (name == "Cyan") { return 0x00FFFF }
|
||||
if (name == "Aqua") { return 0x00FFFF }
|
||||
if (name == "LightCyan") { return 0xE0FFFF }
|
||||
if (name == "PaleTurquoise") { return 0xAFEEEE }
|
||||
if (name == "Aquamarine") { return 0x7FFFD4 }
|
||||
if (name == "Turquoise") { return 0x40E0D0 }
|
||||
if (name == "MediumTurquoise") { return 0x48D1CC }
|
||||
if (name == "DarkTurquoise") { return 0x00CED1 }
|
||||
if (name == "Teal") { return 0x008080 }
|
||||
if (name == "DarkCyan") { return 0x008B8B }
|
||||
if (name == "CadetBlue") { return 0x5F9EA0 }
|
||||
if (name == "Lagoon") { return 0x018E8E }
|
||||
if (name == "Seafoam") { return 0x93E9BE }
|
||||
if (name == "Cerulean") { return 0x007BA7 }
|
||||
if (name == "SkyBlueLight") { return 0x80DAEB }
|
||||
if (name == "Robin") { return 0x00CCCC }
|
||||
if (name == "Verdigris") { return 0x43B3AE }
|
||||
if (name == "Celadon") { return 0xACE1AF }
|
||||
if (name == "Blue") { return 0x0000FF }
|
||||
if (name == "LightBlue") { return 0xADD8E6 }
|
||||
if (name == "PowderBlue") { return 0xB0E0E6 }
|
||||
if (name == "SkyBlue") { return 0x87CEEB }
|
||||
if (name == "LightSkyBlue") { return 0x87CEFA }
|
||||
if (name == "DeepSkyBlue") { return 0x00BFFF }
|
||||
if (name == "DodgerBlue") { return 0x1E90FF }
|
||||
if (name == "CornflowerBlue") { return 0x6495ED }
|
||||
if (name == "SteelBlue") { return 0x4682B4 }
|
||||
if (name == "RoyalBlue") { return 0x4169E1 }
|
||||
if (name == "MediumBlue") { return 0x0000CD }
|
||||
if (name == "DarkBlue") { return 0x00008B }
|
||||
if (name == "Navy") { return 0x000080 }
|
||||
if (name == "MidnightBlue") { return 0x191970 }
|
||||
if (name == "Cobalt") { return 0x0047AB }
|
||||
if (name == "Sapphire") { return 0x0F52BA }
|
||||
if (name == "Denim") { return 0x1560BD }
|
||||
if (name == "Indigo") { return 0x4B0082 }
|
||||
if (name == "Prussian") { return 0x003153 }
|
||||
if (name == "Ultramarine") { return 0x3F00FF }
|
||||
if (name == "Periwinkle") { return 0xCCCCFF }
|
||||
if (name == "Iris") { return 0x5A4FCF }
|
||||
if (name == "Glaucous") { return 0x6082B6 }
|
||||
if (name == "Zaffre") { return 0x0014A8 }
|
||||
if (name == "Berry") { return 0x2E2D88 }
|
||||
if (name == "Purple") { return 0x800080 }
|
||||
if (name == "Violet") { return 0xEE82EE }
|
||||
if (name == "DarkViolet") { return 0x9400D3 }
|
||||
if (name == "BlueViolet") { return 0x8A2BE2 }
|
||||
if (name == "MediumPurple") { return 0x9370DB }
|
||||
if (name == "Amethyst") { return 0x9966CC }
|
||||
if (name == "Plum") { return 0x8E4585 }
|
||||
if (name == "Eggplant") { return 0x614051 }
|
||||
if (name == "Grape") { return 0x6F2DA8 }
|
||||
if (name == "Wine") { return 0x722F37 }
|
||||
if (name == "Mulberry") { return 0xC54B8C }
|
||||
if (name == "Lilac") { return 0xC8A2C8 }
|
||||
if (name == "Wisteria") { return 0xC9A0DC }
|
||||
if (name == "Heliotrope") { return 0xDF73FF }
|
||||
if (name == "Byzantium") { return 0x702963 }
|
||||
if (name == "Tyrian") { return 0x66023C }
|
||||
if (name == "RebeccaPurple") { return 0x663399 }
|
||||
if (name == "Orchid2") { return 0xAF69EF }
|
||||
return -1
|
||||
}
|
||||
|
||||
74
selfhost/backend/stdlib/emit_colorfn.ludic
Normal file
74
selfhost/backend/stdlib/emit_colorfn.ludic
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# emit_colorfn.ludic — the Color.* function surface (Color.Name constants are
|
||||
# resolved at compile time elsewhere). Colors are 0x00RRGGBB ints; these build
|
||||
# and blend them with plain integer/fixed math. rgb/rgba pack channels; lerp/
|
||||
# darken/lighten/with_alpha transform an existing color.
|
||||
|
||||
function is_colorfn_ns(meth: pointer) -> bool {
|
||||
if (meth == "rgb") or (meth == "rgba") or (meth == "lerp") { return true }
|
||||
if (meth == "darken") or (meth == "lighten") or (meth == "with_alpha") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
# (c >> shift) & 255 -> code of a channel value
|
||||
function color_ch(c: pointer, shift: pointer) -> pointer {
|
||||
let sh = emit_bind(`lshr i32 {c}, {shift}`)
|
||||
return emit_bind(`and i32 {sh}, 255`)
|
||||
}
|
||||
|
||||
# (r << 16) | (g << 8) | b -> code of a packed color
|
||||
function color_pack(r: pointer, g: pointer, b: pointer) -> pointer {
|
||||
let r16 = emit_bind(`shl i32 {r}, 16`)
|
||||
let g8 = emit_bind(`shl i32 {g}, 8`)
|
||||
let rg = emit_bind(`or i32 {r16}, {g8}`)
|
||||
return emit_bind(`or i32 {rg}, {b}`)
|
||||
}
|
||||
|
||||
# ch0 + ((ch1 - ch0) * t >> 16), t a fixed 0..1 -> code of a blended channel
|
||||
function color_lerp_ch(ch0: pointer, ch1: pointer, t: pointer) -> pointer {
|
||||
let d = emit_bind(`sub i32 {ch1}, {ch0}`)
|
||||
let dt = emit_bind(`mul i32 {d}, {t}`)
|
||||
let dsh = emit_bind(`ashr i32 {dt}, 16`)
|
||||
return emit_bind(`add i32 {ch0}, {dsh}`)
|
||||
}
|
||||
|
||||
function emit_colorfn_ns(meth: pointer, e: Node) -> Val {
|
||||
if (meth == "rgb") { # rgb(r, g, b) -> 0xRRGGBB
|
||||
let r = emit_expr(e.kids[0]); let g = emit_expr(e.kids[1]); let b = emit_expr(e.kids[2])
|
||||
return val(color_pack(r.code, g.code, b.code), "int")
|
||||
}
|
||||
if (meth == "rgba") { # rgba(r, g, b, a) -> 0xAARRGGBB
|
||||
let r = emit_expr(e.kids[0]); let g = emit_expr(e.kids[1]); let b = emit_expr(e.kids[2]); let a = emit_expr(e.kids[3])
|
||||
let base = color_pack(r.code, g.code, b.code)
|
||||
let a24 = emit_bind(`shl i32 {a.code}, 24`)
|
||||
return val(emit_bind(`or i32 {base}, {a24}`), "int")
|
||||
}
|
||||
if (meth == "with_alpha") { # replace the alpha byte
|
||||
let c = emit_expr(e.kids[0]); let a = emit_expr(e.kids[1])
|
||||
let rgb = emit_bind(`and i32 {c.code}, 16777215`) # c & 0x00FFFFFF
|
||||
let a24 = emit_bind(`shl i32 {a.code}, 24`)
|
||||
return val(emit_bind(`or i32 {rgb}, {a24}`), "int")
|
||||
}
|
||||
if (meth == "lerp") { # blend two colors by t (fixed 0..1)
|
||||
let c0 = emit_expr(e.kids[0]); let c1 = emit_expr(e.kids[1]); let t = emit_expr(e.kids[2])
|
||||
let r = color_lerp_ch(color_ch(c0.code, "16"), color_ch(c1.code, "16"), t.code)
|
||||
let g = color_lerp_ch(color_ch(c0.code, "8"), color_ch(c1.code, "8"), t.code)
|
||||
let b = color_lerp_ch(color_ch(c0.code, "0"), color_ch(c1.code, "0"), t.code)
|
||||
return val(color_pack(r, g, b), "int")
|
||||
}
|
||||
if (meth == "darken") { # scale channels by (1 - amount)
|
||||
let c = emit_expr(e.kids[0]); let amt = emit_expr(e.kids[1])
|
||||
let factor = emit_bind(`sub i32 65536, {amt.code}`)
|
||||
let r0 = color_ch(c.code, "16"); let g0 = color_ch(c.code, "8"); let b0 = color_ch(c.code, "0")
|
||||
let rm = emit_bind(`mul i32 {r0}, {factor}`); let r = emit_bind(`ashr i32 {rm}, 16`)
|
||||
let gm = emit_bind(`mul i32 {g0}, {factor}`); let g = emit_bind(`ashr i32 {gm}, 16`)
|
||||
let bm = emit_bind(`mul i32 {b0}, {factor}`); let b = emit_bind(`ashr i32 {bm}, 16`)
|
||||
return val(color_pack(r, g, b), "int")
|
||||
}
|
||||
# lighten: ch + (255 - ch) * amount
|
||||
let c = emit_expr(e.kids[0]); let amt = emit_expr(e.kids[1])
|
||||
let r0 = color_ch(c.code, "16"); let g0 = color_ch(c.code, "8"); let b0 = color_ch(c.code, "0")
|
||||
let rr = emit_bind(`sub i32 255, {r0}`); let rm = emit_bind(`mul i32 {rr}, {amt.code}`); let rt = emit_bind(`ashr i32 {rm}, 16`); let r = emit_bind(`add i32 {r0}, {rt}`)
|
||||
let gr = emit_bind(`sub i32 255, {g0}`); let gm = emit_bind(`mul i32 {gr}, {amt.code}`); let gt = emit_bind(`ashr i32 {gm}, 16`); let g = emit_bind(`add i32 {g0}, {gt}`)
|
||||
let br = emit_bind(`sub i32 255, {b0}`); let bm = emit_bind(`mul i32 {br}, {amt.code}`); let bt = emit_bind(`ashr i32 {bm}, 16`); let b = emit_bind(`add i32 {b0}, {bt}`)
|
||||
return val(color_pack(r, g, b), "int")
|
||||
}
|
||||
351
selfhost/backend/stdlib/emit_crypto.ludic
Normal file
351
selfhost/backend/stdlib/emit_crypto.ludic
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
# emit_crypto.ludic — the Crypto.* namespace: secure, test-vector-backed hashing
|
||||
# for the few security-sensitive things games do (signed saves, message/token
|
||||
# integrity), kept deliberately separate from the fast, non-cryptographic Hash.*
|
||||
# library so nobody reaches for the wrong tool.
|
||||
#
|
||||
# Crypto.sha256(s) SHA-256 of the bytes of `s` -> 64-char lowercase hex
|
||||
# Crypto.hmac_sha256(key, msg) HMAC-SHA256(key, msg) -> 64-char lowercase hex
|
||||
# Crypto.verify_hmac(key, msg, mac) recompute the MAC and compare it to `mac`
|
||||
# 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
|
||||
# data-dependent branches in the compression rounds, so a given input hashes to
|
||||
# the same 32 bytes on every platform and every run. Digests are returned as hex
|
||||
# strings (not raw bytes) because a `str` is null-terminated and a raw digest can
|
||||
# contain NUL — hex is the directly-printable, directly-comparable form.
|
||||
#
|
||||
# What this is NOT: it is not DRM and not unbeatable anti-cheat. A client-side
|
||||
# game cannot keep a secret from the machine running it; a determined owner can
|
||||
# always read the key out of the binary. Use it to make *casual* tampering with a
|
||||
# save or a leaderboard payload detectable, and to verify a network message was
|
||||
# not forged by a third party who does not hold the key — nothing stronger.
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
function emit_crypto_ns(meth: pointer, e: Node) -> Val {
|
||||
g_uses_cryptort = true
|
||||
if (meth == "sha256") { # SHA-256 -> 64-char hex string
|
||||
let s = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call ptr @fn_sha256_hex(ptr {s.code})`), "string")
|
||||
}
|
||||
if (meth == "hmac_sha256") { # HMAC-SHA256 -> 64-char hex string
|
||||
let k = emit_expr(e.kids[0]); let m = emit_expr(e.kids[1])
|
||||
return val(emit_bind(`call ptr @fn_hmac_sha256_hex(ptr {k.code}, ptr {m.code})`), "string")
|
||||
}
|
||||
if (meth == "hex") { # lowercase hex of a string's bytes
|
||||
let s = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call ptr @fn_str_hex(ptr {s.code})`), "string")
|
||||
}
|
||||
if (meth == "ct_equal") { # constant-time string equality -> bool
|
||||
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.
|
||||
let k = emit_expr(e.kids[0]); let m = emit_expr(e.kids[1]); let mac = emit_expr(e.kids[2])
|
||||
let computed = emit_bind(`call ptr @fn_hmac_sha256_hex(ptr {k.code}, ptr {m.code})`)
|
||||
return val(emit_bind(`call i32 @fn_ct_streq(ptr {computed}, ptr {mac.code})`), "bool")
|
||||
}
|
||||
|
||||
# emit_crypto_prelude — the SHA-256 / HMAC-SHA256 runtime, emitted once per program
|
||||
# that uses Crypto.* (g_uses_cryptort). Everything below is FIPS 180-4 / RFC 2104
|
||||
# to the letter, in pure integer IR with no libc crypto.
|
||||
function emit_crypto_prelude() -> void {
|
||||
# the 64 SHA-256 round constants (first 32 bits of the fractional parts of the
|
||||
# cube roots of the first 64 primes), as signed i32.
|
||||
emith("@sha256_K = private unnamed_addr constant [64 x i32] [i32 1116352408, i32 1899447441, i32 -1245643825, i32 -373957723, i32 961987163, i32 1508970993, i32 -1841331548, i32 -1424204075, i32 -670586216, i32 310598401, i32 607225278, i32 1426881987, i32 1925078388, i32 -2132889090, i32 -1680079193, i32 -1046744716, i32 -459576895, i32 -272742522, i32 264347078, i32 604807628, i32 770255983, i32 1249150122, i32 1555081692, i32 1996064986, i32 -1740746414, i32 -1473132947, i32 -1341970488, i32 -1084653625, i32 -958395405, i32 -710438585, i32 113926993, i32 338241895, i32 666307205, i32 773529912, i32 1294757372, i32 1396182291, i32 1695183700, i32 1986661051, i32 -2117940946, i32 -1838011259, i32 -1564481375, i32 -1474664885, i32 -1035236496, i32 -949202525, i32 -778901479, i32 -694614492, i32 -200395387, i32 275423344, i32 430227734, i32 506948616, i32 659060556, i32 883997877, i32 958139571, i32 1322822218, i32 1537002063, i32 1747873779, i32 1955562222, i32 2024104815, i32 -2067236844, i32 -1933114872, i32 -1866530822, i32 -1538233109, i32 -1090935817, i32 -965641998]\n")
|
||||
|
||||
# rotate a 32-bit word right by %n (1..31)
|
||||
emith("define i32 @fn_rotr32(i32 %x, i32 %n) {\n")
|
||||
emith(" %r = lshr i32 %x, %n\n %m = sub i32 32, %n\n %l = shl i32 %x, %m\n %o = or i32 %r, %l\n ret i32 %o\n}\n")
|
||||
|
||||
# SHA-256 of %len bytes at %msg -> the 32 raw digest bytes at %out. Pads into a
|
||||
# fresh malloc'd buffer (append 0x80, zero-fill, 64-bit big-endian bit length),
|
||||
# then runs the standard 64-round compression over each 512-bit block.
|
||||
emith("define void @fn_sha256_buf(ptr %msg, i64 %len, ptr %out) {\n")
|
||||
emith("entry:\n")
|
||||
emith(" %H = alloca [8 x i32]\n %W = alloca [64 x i32]\n")
|
||||
emith(" %a = alloca i32\n %b = alloca i32\n %c = alloca i32\n %d = alloca i32\n %e = alloca i32\n %f = alloca i32\n %g = alloca i32\n %h = alloca i32\n")
|
||||
emith(" %ip = alloca i64\n %bp = alloca i64\n")
|
||||
# padded length = ((len + 8) / 64 + 1) * 64
|
||||
emith(" %e0 = add i64 %len, 8\n %e1 = lshr i64 %e0, 6\n %e2 = add i64 %e1, 1\n %pl = shl i64 %e2, 6\n")
|
||||
emith(" %buf = call ptr @malloc(i64 %pl)\n")
|
||||
emith(" call ptr @memset(ptr %buf, i32 0, i64 %pl)\n")
|
||||
emith(" call ptr @memcpy(ptr %buf, ptr %msg, i64 %len)\n")
|
||||
emith(" %pmark = getelementptr i8, ptr %buf, i64 %len\n store i8 -128, ptr %pmark\n") # 0x80
|
||||
emith(" %bits = shl i64 %len, 3\n")
|
||||
# write the 64-bit big-endian message length into the final 8 bytes
|
||||
emith(" store i64 0, ptr %ip\n br label %lenc\n")
|
||||
emith("lenc:\n %lj = load i64, ptr %ip\n %ljlt = icmp slt i64 %lj, 8\n br i1 %ljlt, label %lenb, label %hinit\n")
|
||||
emith("lenb:\n")
|
||||
emith(" %lj8 = mul i64 %lj, 8\n %lsh = sub i64 56, %lj8\n %lbsh = lshr i64 %bits, %lsh\n %lbb = trunc i64 %lbsh to i8\n")
|
||||
emith(" %lpm8 = sub i64 %pl, 8\n %lpos = add i64 %lpm8, %lj\n %lpp = getelementptr i8, ptr %buf, i64 %lpos\n store i8 %lbb, ptr %lpp\n")
|
||||
emith(" %lj1 = add i64 %lj, 1\n store i64 %lj1, ptr %ip\n br label %lenc\n")
|
||||
# H := the eight initial hash values (fractional parts of the sqrt of primes)
|
||||
emith("hinit:\n")
|
||||
emith(" %H0 = getelementptr [8 x i32], ptr %H, i64 0, i64 0\n store i32 1779033703, ptr %H0\n")
|
||||
emith(" %H1 = getelementptr [8 x i32], ptr %H, i64 0, i64 1\n store i32 -1150833019, ptr %H1\n")
|
||||
emith(" %H2 = getelementptr [8 x i32], ptr %H, i64 0, i64 2\n store i32 1013904242, ptr %H2\n")
|
||||
emith(" %H3 = getelementptr [8 x i32], ptr %H, i64 0, i64 3\n store i32 -1521486534, ptr %H3\n")
|
||||
emith(" %H4 = getelementptr [8 x i32], ptr %H, i64 0, i64 4\n store i32 1359893119, ptr %H4\n")
|
||||
emith(" %H5 = getelementptr [8 x i32], ptr %H, i64 0, i64 5\n store i32 -1694144372, ptr %H5\n")
|
||||
emith(" %H6 = getelementptr [8 x i32], ptr %H, i64 0, i64 6\n store i32 528734635, ptr %H6\n")
|
||||
emith(" %H7 = getelementptr [8 x i32], ptr %H, i64 0, i64 7\n store i32 1541459225, ptr %H7\n")
|
||||
emith(" %nb = lshr i64 %pl, 6\n store i64 0, ptr %bp\n br label %blkc\n")
|
||||
# ---- per-block loop ----
|
||||
emith("blkc:\n %bi = load i64, ptr %bp\n %blt = icmp ult i64 %bi, %nb\n br i1 %blt, label %blkb, label %outp\n")
|
||||
emith("blkb:\n %bi64 = shl i64 %bi, 6\n %base = getelementptr i8, ptr %buf, i64 %bi64\n")
|
||||
# W[0..15] <- the block's sixteen big-endian 32-bit words
|
||||
emith(" store i64 0, ptr %ip\n br label %w1c\n")
|
||||
emith("w1c:\n %wi = load i64, ptr %ip\n %wilt = icmp slt i64 %wi, 16\n br i1 %wilt, label %w1b, label %w2init\n")
|
||||
emith("w1b:\n")
|
||||
emith(" %wi4 = shl i64 %wi, 2\n")
|
||||
emith(" %wp0 = getelementptr i8, ptr %base, i64 %wi4\n %wc0 = load i8, ptr %wp0\n")
|
||||
emith(" %wo1 = add i64 %wi4, 1\n %wp1 = getelementptr i8, ptr %base, i64 %wo1\n %wc1 = load i8, ptr %wp1\n")
|
||||
emith(" %wo2 = add i64 %wi4, 2\n %wp2 = getelementptr i8, ptr %base, i64 %wo2\n %wc2 = load i8, ptr %wp2\n")
|
||||
emith(" %wo3 = add i64 %wi4, 3\n %wp3 = getelementptr i8, ptr %base, i64 %wo3\n %wc3 = load i8, ptr %wp3\n")
|
||||
emith(" %wz0 = zext i8 %wc0 to i32\n %wz1 = zext i8 %wc1 to i32\n %wz2 = zext i8 %wc2 to i32\n %wz3 = zext i8 %wc3 to i32\n")
|
||||
emith(" %ws24 = shl i32 %wz0, 24\n %ws16 = shl i32 %wz1, 16\n %ws8 = shl i32 %wz2, 8\n")
|
||||
emith(" %wor1 = or i32 %ws24, %ws16\n %wor2 = or i32 %wor1, %ws8\n %word = or i32 %wor2, %wz3\n")
|
||||
emith(" %wwp = getelementptr [64 x i32], ptr %W, i64 0, i64 %wi\n store i32 %word, ptr %wwp\n")
|
||||
emith(" %wi1 = add i64 %wi, 1\n store i64 %wi1, ptr %ip\n br label %w1c\n")
|
||||
# W[16..63] <- the message schedule extension
|
||||
emith("w2init:\n store i64 16, ptr %ip\n br label %w2c\n")
|
||||
emith("w2c:\n %xi = load i64, ptr %ip\n %xilt = icmp slt i64 %xi, 64\n br i1 %xilt, label %w2b, label %compinit\n")
|
||||
emith("w2b:\n")
|
||||
emith(" %im15 = sub i64 %xi, 15\n %pm15 = getelementptr [64 x i32], ptr %W, i64 0, i64 %im15\n %w15 = load i32, ptr %pm15\n")
|
||||
emith(" %r7 = call i32 @fn_rotr32(i32 %w15, i32 7)\n %r18 = call i32 @fn_rotr32(i32 %w15, i32 18)\n %sh3 = lshr i32 %w15, 3\n")
|
||||
emith(" %x01 = xor i32 %r7, %r18\n %s0 = xor i32 %x01, %sh3\n")
|
||||
emith(" %im2 = sub i64 %xi, 2\n %pm2 = getelementptr [64 x i32], ptr %W, i64 0, i64 %im2\n %w2v = load i32, ptr %pm2\n")
|
||||
emith(" %r17 = call i32 @fn_rotr32(i32 %w2v, i32 17)\n %r19 = call i32 @fn_rotr32(i32 %w2v, i32 19)\n %sh10 = lshr i32 %w2v, 10\n")
|
||||
emith(" %x02 = xor i32 %r17, %r19\n %s1 = xor i32 %x02, %sh10\n")
|
||||
emith(" %im16 = sub i64 %xi, 16\n %pm16 = getelementptr [64 x i32], ptr %W, i64 0, i64 %im16\n %w16 = load i32, ptr %pm16\n")
|
||||
emith(" %im7 = sub i64 %xi, 7\n %pm7 = getelementptr [64 x i32], ptr %W, i64 0, i64 %im7\n %w7 = load i32, ptr %pm7\n")
|
||||
emith(" %wa1 = add i32 %w16, %s0\n %wa2 = add i32 %wa1, %w7\n %wv = add i32 %wa2, %s1\n")
|
||||
emith(" %wpi = getelementptr [64 x i32], ptr %W, i64 0, i64 %xi\n store i32 %wv, ptr %wpi\n")
|
||||
emith(" %xi1 = add i64 %xi, 1\n store i64 %xi1, ptr %ip\n br label %w2c\n")
|
||||
# a..h <- H
|
||||
emith("compinit:\n")
|
||||
emith(" %cv0 = load i32, ptr %H0\n store i32 %cv0, ptr %a\n")
|
||||
emith(" %cv1 = load i32, ptr %H1\n store i32 %cv1, ptr %b\n")
|
||||
emith(" %cv2 = load i32, ptr %H2\n store i32 %cv2, ptr %c\n")
|
||||
emith(" %cv3 = load i32, ptr %H3\n store i32 %cv3, ptr %d\n")
|
||||
emith(" %cv4 = load i32, ptr %H4\n store i32 %cv4, ptr %e\n")
|
||||
emith(" %cv5 = load i32, ptr %H5\n store i32 %cv5, ptr %f\n")
|
||||
emith(" %cv6 = load i32, ptr %H6\n store i32 %cv6, ptr %g\n")
|
||||
emith(" %cv7 = load i32, ptr %H7\n store i32 %cv7, ptr %h\n")
|
||||
emith(" store i64 0, ptr %ip\n br label %rc\n")
|
||||
# ---- the 64 compression rounds ----
|
||||
emith("rc:\n %ri = load i64, ptr %ip\n %rlt = icmp slt i64 %ri, 64\n br i1 %rlt, label %rb, label %addH\n")
|
||||
emith("rb:\n")
|
||||
emith(" %av = load i32, ptr %a\n %bv = load i32, ptr %b\n %cvv = load i32, ptr %c\n %dv = load i32, ptr %d\n")
|
||||
emith(" %ev = load i32, ptr %e\n %fv = load i32, ptr %f\n %gv = load i32, ptr %g\n %hv = load i32, ptr %h\n")
|
||||
# S1 = rotr(e,6) ^ rotr(e,11) ^ rotr(e,25); ch = (e & f) ^ (~e & g)
|
||||
emith(" %e6 = call i32 @fn_rotr32(i32 %ev, i32 6)\n %e11 = call i32 @fn_rotr32(i32 %ev, i32 11)\n %e25 = call i32 @fn_rotr32(i32 %ev, i32 25)\n")
|
||||
emith(" %S1a = xor i32 %e6, %e11\n %S1 = xor i32 %S1a, %e25\n")
|
||||
emith(" %ef = and i32 %ev, %fv\n %ne = xor i32 %ev, -1\n %neg = and i32 %ne, %gv\n %ch = xor i32 %ef, %neg\n")
|
||||
emith(" %kp = getelementptr [64 x i32], ptr @sha256_K, i64 0, i64 %ri\n %kv = load i32, ptr %kp\n")
|
||||
emith(" %wpr = getelementptr [64 x i32], ptr %W, i64 0, i64 %ri\n %wvr = load i32, ptr %wpr\n")
|
||||
# temp1 = h + S1 + ch + K[i] + W[i]
|
||||
emith(" %t1a = add i32 %hv, %S1\n %t1b = add i32 %t1a, %ch\n %t1c = add i32 %t1b, %kv\n %temp1 = add i32 %t1c, %wvr\n")
|
||||
# S0 = rotr(a,2) ^ rotr(a,13) ^ rotr(a,22); maj = (a&b) ^ (a&c) ^ (b&c)
|
||||
emith(" %a2r = call i32 @fn_rotr32(i32 %av, i32 2)\n %a13 = call i32 @fn_rotr32(i32 %av, i32 13)\n %a22 = call i32 @fn_rotr32(i32 %av, i32 22)\n")
|
||||
emith(" %S0a = xor i32 %a2r, %a13\n %S0 = xor i32 %S0a, %a22\n")
|
||||
emith(" %ab = and i32 %av, %bv\n %ac = and i32 %av, %cvv\n %bc = and i32 %bv, %cvv\n %mj1 = xor i32 %ab, %ac\n %maj = xor i32 %mj1, %bc\n")
|
||||
emith(" %temp2 = add i32 %S0, %maj\n")
|
||||
# rotate the working registers: h=g, g=f, f=e, e=d+temp1, d=c, c=b, b=a, a=temp1+temp2
|
||||
emith(" store i32 %gv, ptr %h\n store i32 %fv, ptr %g\n store i32 %ev, ptr %f\n")
|
||||
emith(" %newe = add i32 %dv, %temp1\n store i32 %newe, ptr %e\n")
|
||||
emith(" store i32 %cvv, ptr %d\n store i32 %bv, ptr %c\n store i32 %av, ptr %b\n")
|
||||
emith(" %newa = add i32 %temp1, %temp2\n store i32 %newa, ptr %a\n")
|
||||
emith(" %rin = add i64 %ri, 1\n store i64 %rin, ptr %ip\n br label %rc\n")
|
||||
# H[i] += the working registers
|
||||
emith("addH:\n")
|
||||
emith(" %fa = load i32, ptr %a\n %lH0 = load i32, ptr %H0\n %nH0 = add i32 %lH0, %fa\n store i32 %nH0, ptr %H0\n")
|
||||
emith(" %fb = load i32, ptr %b\n %lH1 = load i32, ptr %H1\n %nH1 = add i32 %lH1, %fb\n store i32 %nH1, ptr %H1\n")
|
||||
emith(" %fc = load i32, ptr %c\n %lH2 = load i32, ptr %H2\n %nH2 = add i32 %lH2, %fc\n store i32 %nH2, ptr %H2\n")
|
||||
emith(" %fd = load i32, ptr %d\n %lH3 = load i32, ptr %H3\n %nH3 = add i32 %lH3, %fd\n store i32 %nH3, ptr %H3\n")
|
||||
emith(" %fe = load i32, ptr %e\n %lH4 = load i32, ptr %H4\n %nH4 = add i32 %lH4, %fe\n store i32 %nH4, ptr %H4\n")
|
||||
emith(" %ff = load i32, ptr %f\n %lH5 = load i32, ptr %H5\n %nH5 = add i32 %lH5, %ff\n store i32 %nH5, ptr %H5\n")
|
||||
emith(" %fg = load i32, ptr %g\n %lH6 = load i32, ptr %H6\n %nH6 = add i32 %lH6, %fg\n store i32 %nH6, ptr %H6\n")
|
||||
emith(" %fh = load i32, ptr %h\n %lH7 = load i32, ptr %H7\n %nH7 = add i32 %lH7, %fh\n store i32 %nH7, ptr %H7\n")
|
||||
emith(" %binc = add i64 %bi, 1\n store i64 %binc, ptr %bp\n br label %blkc\n")
|
||||
# ---- serialize H[0..7] big-endian into the 32-byte output ----
|
||||
emith("outp:\n store i64 0, ptr %ip\n br label %oc\n")
|
||||
emith("oc:\n %oi = load i64, ptr %ip\n %olt = icmp slt i64 %oi, 8\n br i1 %olt, label %ob, label %freeb\n")
|
||||
emith("ob:\n")
|
||||
emith(" %hpp = getelementptr [8 x i32], ptr %H, i64 0, i64 %oi\n %hval = load i32, ptr %hpp\n %oi4 = shl i64 %oi, 2\n")
|
||||
emith(" %ob24 = lshr i32 %hval, 24\n %obb24 = trunc i32 %ob24 to i8\n %op0 = getelementptr i8, ptr %out, i64 %oi4\n store i8 %obb24, ptr %op0\n")
|
||||
emith(" %ob16 = lshr i32 %hval, 16\n %obb16 = trunc i32 %ob16 to i8\n %oo1 = add i64 %oi4, 1\n %op1 = getelementptr i8, ptr %out, i64 %oo1\n store i8 %obb16, ptr %op1\n")
|
||||
emith(" %ob8 = lshr i32 %hval, 8\n %obb8 = trunc i32 %ob8 to i8\n %oo2 = add i64 %oi4, 2\n %op2 = getelementptr i8, ptr %out, i64 %oo2\n store i8 %obb8, ptr %op2\n")
|
||||
emith(" %obb0 = trunc i32 %hval to i8\n %oo3 = add i64 %oi4, 3\n %op3 = getelementptr i8, ptr %out, i64 %oo3\n store i8 %obb0, ptr %op3\n")
|
||||
emith(" %oin = add i64 %oi, 1\n store i64 %oin, ptr %ip\n br label %oc\n")
|
||||
emith("freeb:\n call void @free(ptr %buf)\n ret void\n}\n")
|
||||
|
||||
# one hex digit (0..15) -> its lowercase ASCII byte
|
||||
emith("define i8 @fn_hex_digit(i32 %d) {\n")
|
||||
emith(" %lt = icmp ult i32 %d, 10\n %base = select i1 %lt, i32 48, i32 87\n %v = add i32 %base, %d\n %c = trunc i32 %v to i8\n ret i8 %c\n}\n")
|
||||
|
||||
# hex-encode %n bytes at %in -> a fresh null-terminated 2n-char string
|
||||
emith("define ptr @fn_hex_encode(ptr %in, i64 %n) {\n")
|
||||
emith("entry:\n %ip = alloca i64\n %olen = shl i64 %n, 1\n %olen1 = add i64 %olen, 1\n %s = call ptr @malloc(i64 %olen1)\n store i64 0, ptr %ip\n br label %c\n")
|
||||
emith("c:\n %i = load i64, ptr %ip\n %lt = icmp ult i64 %i, %n\n br i1 %lt, label %bdy, label %done\n")
|
||||
emith("bdy:\n %pp = getelementptr i8, ptr %in, i64 %i\n %byte = load i8, ptr %pp\n %bz = zext i8 %byte to i32\n")
|
||||
emith(" %hi = lshr i32 %bz, 4\n %lo = and i32 %bz, 15\n %hc = call i8 @fn_hex_digit(i32 %hi)\n %lc = call i8 @fn_hex_digit(i32 %lo)\n")
|
||||
emith(" %oi = shl i64 %i, 1\n %o0 = getelementptr i8, ptr %s, i64 %oi\n store i8 %hc, ptr %o0\n %oi1 = add i64 %oi, 1\n %o1 = getelementptr i8, ptr %s, i64 %oi1\n store i8 %lc, ptr %o1\n")
|
||||
emith(" %in1 = add i64 %i, 1\n store i64 %in1, ptr %ip\n br label %c\n")
|
||||
emith("done:\n %tp = getelementptr i8, ptr %s, i64 %olen\n store i8 0, ptr %tp\n ret ptr %s\n}\n")
|
||||
|
||||
# SHA-256 of a null-terminated string -> 64-char hex
|
||||
emith("define ptr @fn_sha256_hex(ptr %s) {\n")
|
||||
emith("entry:\n %dig = alloca [32 x i8]\n %len = call i64 @strlen(ptr %s)\n %dp = getelementptr [32 x i8], ptr %dig, i64 0, i64 0\n")
|
||||
emith(" call void @fn_sha256_buf(ptr %s, i64 %len, ptr %dp)\n %hex = call ptr @fn_hex_encode(ptr %dp, i64 32)\n ret ptr %hex\n}\n")
|
||||
|
||||
# hex of a whole null-terminated string's bytes
|
||||
emith("define ptr @fn_str_hex(ptr %s) {\n")
|
||||
emith(" %n = call i64 @strlen(ptr %s)\n %h = call ptr @fn_hex_encode(ptr %s, i64 %n)\n ret ptr %h\n}\n")
|
||||
|
||||
# xor 64 bytes of %src with the byte %pad into %dst (the HMAC key padding step)
|
||||
emith("define void @fn_xor64(ptr %dst, ptr %src, i32 %pad) {\n")
|
||||
emith("entry:\n %ip = alloca i64\n store i64 0, ptr %ip\n br label %c\n")
|
||||
emith("c:\n %i = load i64, ptr %ip\n %lt = icmp ult i64 %i, 64\n br i1 %lt, label %b, label %d\n")
|
||||
emith("b:\n %sp = getelementptr i8, ptr %src, i64 %i\n %sv = load i8, ptr %sp\n %sz = zext i8 %sv to i32\n %xr = xor i32 %sz, %pad\n %xb = trunc i32 %xr to i8\n %dp = getelementptr i8, ptr %dst, i64 %i\n store i8 %xb, ptr %dp\n %in = add i64 %i, 1\n store i64 %in, ptr %ip\n br label %c\n")
|
||||
emith("d:\n ret void\n}\n")
|
||||
|
||||
# HMAC-SHA256(key, msg) -> 64-char hex (RFC 2104, block size 64).
|
||||
emith("define ptr @fn_hmac_sha256_hex(ptr %key, ptr %msg) {\n")
|
||||
emith("entry:\n")
|
||||
emith(" %k0 = alloca [64 x i8]\n %inner = alloca [32 x i8]\n %outbuf = alloca [96 x i8]\n %fin = alloca [32 x i8]\n")
|
||||
emith(" %klen = call i64 @strlen(ptr %key)\n %mlen = call i64 @strlen(ptr %msg)\n")
|
||||
emith(" %k0p = getelementptr [64 x i8], ptr %k0, i64 0, i64 0\n call ptr @memset(ptr %k0p, i32 0, i64 64)\n")
|
||||
# K0: a key longer than the block is replaced by its own hash; otherwise it is
|
||||
# right-zero-padded to 64 bytes.
|
||||
emith(" %big = icmp ugt i64 %klen, 64\n br i1 %big, label %hashk, label %copyk\n")
|
||||
emith("hashk:\n call void @fn_sha256_buf(ptr %key, i64 %klen, ptr %k0p)\n br label %pads\n")
|
||||
emith("copyk:\n call ptr @memcpy(ptr %k0p, ptr %key, i64 %klen)\n br label %pads\n")
|
||||
emith("pads:\n")
|
||||
# inner = SHA-256( (K0 ^ ipad) || msg ), ipad = 0x36
|
||||
emith(" %inlen = add i64 64, %mlen\n %inbuf = call ptr @malloc(i64 %inlen)\n")
|
||||
emith(" call void @fn_xor64(ptr %inbuf, ptr %k0p, i32 54)\n")
|
||||
emith(" %inmsg = getelementptr i8, ptr %inbuf, i64 64\n call ptr @memcpy(ptr %inmsg, ptr %msg, i64 %mlen)\n")
|
||||
emith(" %innerp = getelementptr [32 x i8], ptr %inner, i64 0, i64 0\n call void @fn_sha256_buf(ptr %inbuf, i64 %inlen, ptr %innerp)\n call void @free(ptr %inbuf)\n")
|
||||
# digest = SHA-256( (K0 ^ opad) || inner ), opad = 0x5c
|
||||
emith(" %outp = getelementptr [96 x i8], ptr %outbuf, i64 0, i64 0\n call void @fn_xor64(ptr %outp, ptr %k0p, i32 92)\n")
|
||||
emith(" %outmsg = getelementptr i8, ptr %outbuf, i64 64\n call ptr @memcpy(ptr %outmsg, ptr %innerp, i64 32)\n")
|
||||
emith(" %finp = getelementptr [32 x i8], ptr %fin, i64 0, i64 0\n call void @fn_sha256_buf(ptr %outp, i64 96, ptr %finp)\n")
|
||||
emith(" %hex = call ptr @fn_hex_encode(ptr %finp, i64 32)\n ret ptr %hex\n}\n")
|
||||
|
||||
# constant-time equality of two null-terminated strings. Length is not secret,
|
||||
# so an unequal length returns early; equal-length inputs are compared with a
|
||||
# data-independent XOR-accumulate that never short-circuits.
|
||||
emith("define i32 @fn_ct_streq(ptr %a, ptr %b) {\n")
|
||||
emith("entry:\n %accp = alloca i32\n %ip = alloca i64\n %la = call i64 @strlen(ptr %a)\n %lb = call i64 @strlen(ptr %b)\n %eqlen = icmp eq i64 %la, %lb\n br i1 %eqlen, label %go, label %ne\n")
|
||||
emith("ne:\n ret i32 0\n")
|
||||
emith("go:\n store i32 0, ptr %accp\n store i64 0, ptr %ip\n br label %c\n")
|
||||
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")
|
||||
}
|
||||
445
selfhost/backend/stdlib/emit_datetime.ludic
Normal file
445
selfhost/backend/stdlib/emit_datetime.ludic
Normal file
|
|
@ -0,0 +1,445 @@
|
|||
# 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
|
||||
# @fn_days_from_civil / @fn_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 @fn_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 @fn_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 @fn_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 @fn_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
|
||||
}
|
||||
|
||||
# concat two runtime string codes -> a fresh string code
|
||||
function dt_concat(a: pointer, b: pointer) -> pointer {
|
||||
return emit_bind(`call ptr @fn_str_concat(ptr {a}, ptr {b})`)
|
||||
}
|
||||
|
||||
# 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 = 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 @fn_str_concat over literal runs and @fn_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 @fn_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`)
|
||||
var acc = emit_str_const("")
|
||||
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 = i + 1 }
|
||||
else {
|
||||
let ls = buf_str(lit)
|
||||
if len(ls) > 0 { acc = dt_concat(acc, emit_str_const(ls)) }
|
||||
let piece = emit_bind(`call ptr @fn_dt_pad0(i32 {field}, i32 {width})`)
|
||||
acc = dt_concat(acc, piece)
|
||||
lit.len = 0 # start a fresh literal run
|
||||
i = i + tlen
|
||||
}
|
||||
}
|
||||
let tail = buf_str(lit)
|
||||
if len(tail) > 0 { acc = dt_concat(acc, emit_str_const(tail)) }
|
||||
return val(acc, "string")
|
||||
}
|
||||
|
||||
# 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 = off + 1; i = i + 1 }
|
||||
else {
|
||||
let rd = emit_bind(`call i32 @fn_dt_rd(ptr {s.code}, i32 {itoa(off)}, i32 {width}, ptr {failp})`)
|
||||
store_at("i32", rd, slot)
|
||||
off = off + width
|
||||
i = 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 @fn_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 @fn_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 {
|
||||
# @fn_days_from_civil(y, m, d) -> days since 1970-01-01
|
||||
emith("define i32 @fn_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")
|
||||
# @fn_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 @fn_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")
|
||||
# @fn_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 @fn_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")
|
||||
# @fn_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 @fn_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")
|
||||
}
|
||||
63
selfhost/backend/stdlib/emit_ease.ludic
Normal file
63
selfhost/backend/stdlib/emit_ease.ludic
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# emit_ease.ludic — the Ease.* namespace: tween curves over a normalized amount
|
||||
# t in 0.0..1.0, returning an eased fixed. All pure Q16.16, deterministic. The
|
||||
# "juice" layer that makes motion feel good (Robert Penner's easings).
|
||||
|
||||
function is_ease_ns(meth: pointer) -> bool {
|
||||
if (meth == "in") or (meth == "out") or (meth == "in_out") { return true }
|
||||
if (meth == "back") or (meth == "bounce") or (meth == "elastic") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
# n1 * u * u (u a fixed code) -> code of a fixed i32
|
||||
function ease_bounce_seg(u: pointer) -> pointer {
|
||||
let uu = fx_mul_code(u, u)
|
||||
return fx_mul_code(uu, "495616") # 7.5625 * u*u
|
||||
}
|
||||
|
||||
function emit_ease_ns(meth: pointer, e: Node) -> Val {
|
||||
let t = emit_expr(e.kids[0])
|
||||
if (meth == "in") { # ease-in quad: t*t
|
||||
return val(fx_mul_code(t.code, t.code), "fixed")
|
||||
}
|
||||
if (meth == "out") { # ease-out quad: t*(2 - t)
|
||||
let inv = emit_bind(`sub i32 131072, {t.code}`)
|
||||
return val(fx_mul_code(t.code, inv), "fixed")
|
||||
}
|
||||
if (meth == "in_out") { # smooth ease-in-out: 3t^2 - 2t^3
|
||||
let t2 = fx_mul_code(t.code, t.code)
|
||||
let t3 = fx_mul_code(t2, t.code)
|
||||
let three = emit_bind(`mul i32 {t2}, 3`)
|
||||
let two = emit_bind(`mul i32 {t3}, 2`)
|
||||
return val(emit_bind(`sub i32 {three}, {two}`), "fixed")
|
||||
}
|
||||
if (meth == "back") { # ease-in-back (overshoots below 0)
|
||||
let t2 = fx_mul_code(t.code, t.code)
|
||||
let t3 = fx_mul_code(t2, t.code)
|
||||
let a = fx_mul_code(t3, "177051") # 2.70158 * t^3
|
||||
let b = fx_mul_code(t2, "111515") # 1.70158 * t^2
|
||||
return val(emit_bind(`sub i32 {a}, {b}`), "fixed")
|
||||
}
|
||||
if (meth == "elastic") { # ease-out elastic: springy overshoot that settles
|
||||
g_uses_mathrt = true # 2^(-10t) * sin((10t - 0.75) * 2pi/3) + 1
|
||||
let tt = emit_bind(`mul i32 {t.code}, 10`) # 10t
|
||||
let ntt = emit_bind(`sub i32 0, {tt}`) # -10t (exp2 exponent, Q16.16)
|
||||
let decay = emit_bind(`call i32 @fn_fx_exp2(i32 {ntt})`)
|
||||
let ph = emit_bind(`sub i32 {tt}, 49152`) # 10t - 0.75
|
||||
let ang = fx_mul_code(ph, "137258") # * (2pi/3), 2pi/3 = 137258 fixed
|
||||
let s = emit_bind(`call i32 @fn_fx_sin(i32 {ang})`)
|
||||
let osc = fx_mul_code(decay, s)
|
||||
return val(emit_bind(`add i32 {osc}, 65536`), "fixed")
|
||||
}
|
||||
# ease-out bounce: four parabolic segments, selected by t (all computed, then
|
||||
# picked branch-free). Shifts/offsets are the standard 2.75-denominator set.
|
||||
let sA = ease_bounce_seg(t.code)
|
||||
let uB = emit_bind(`sub i32 {t.code}, 35747`); let sB0 = ease_bounce_seg(uB); let sB = emit_bind(`add i32 {sB0}, 49152`)
|
||||
let uC = emit_bind(`sub i32 {t.code}, 53620`); let sC0 = ease_bounce_seg(uC); let sC = emit_bind(`add i32 {sC0}, 61440`)
|
||||
let uD = emit_bind(`sub i32 {t.code}, 62557`); let sD0 = ease_bounce_seg(uD); let sD = emit_bind(`add i32 {sD0}, 64512`)
|
||||
let cCD = emit_bind(`icmp slt i32 {t.code}, 59578`)
|
||||
let rCD = emit_bind(`select i1 {cCD}, i32 {sC}, i32 {sD}`)
|
||||
let cB = emit_bind(`icmp slt i32 {t.code}, 47663`)
|
||||
let rB = emit_bind(`select i1 {cB}, i32 {sB}, i32 {rCD}`)
|
||||
let cA = emit_bind(`icmp slt i32 {t.code}, 23831`)
|
||||
return val(emit_bind(`select i1 {cA}, i32 {sA}, i32 {rB}`), "fixed")
|
||||
}
|
||||
448
selfhost/backend/stdlib/emit_fs.ludic
Normal file
448
selfhost/backend/stdlib/emit_fs.ludic
Normal file
|
|
@ -0,0 +1,448 @@
|
|||
# emit_fs.ludic — the Fs.* / Path.* / Mime.* namespaces: the filesystem, wrapped
|
||||
# into one safe, ergonomic API so a non-expert never touches a file descriptor or
|
||||
# a byte buffer. The foundation for saves, config, mods, and asset loading. The
|
||||
# bare file_* builtins remain the low-level primitive; this is the layer above.
|
||||
#
|
||||
# Path.join(a, b) join two path segments with the separator
|
||||
# Path.dir(p) the directory part ("mods/foo/x.json" -> "mods/foo")
|
||||
# Path.base(p) the final component ("mods/foo/x.json" -> "x.json")
|
||||
# Path.ext(p) the extension incl. dot (".json"), or "" if none
|
||||
# Path.stem(p) the base without its extension ("x")
|
||||
# Path.normalize(p) collapse "." / ".." / duplicate separators
|
||||
#
|
||||
# Fs.exists(p) does the path exist? -> bool
|
||||
# Fs.is_dir(p) is it a directory? -> bool
|
||||
# Fs.read_text(p) read a whole file as a string -> string (null on error)
|
||||
# Fs.write_text(p, s) write a string (atomic tmp+rename) -> bool
|
||||
# Fs.append_text(p, s) append a string -> bool
|
||||
# Fs.remove(p) delete a file -> bool
|
||||
# Fs.size(p) file size in bytes -> int (-1 on error)
|
||||
# Fs.mkdir(p) create a directory (and its parents) -> bool
|
||||
# Fs.copy(src, dst) copy a file, byte-for-byte -> bool
|
||||
# Fs.list(dir) directory entries, sorted, stable -> []string
|
||||
#
|
||||
# Mime.of(path) MIME type from the file extension -> string
|
||||
# Mime.sniff(path) ...refined by magic bytes -> string
|
||||
#
|
||||
# Errors are surfaced as values, never crashes: a fallible call returns null / ""
|
||||
# / false / -1 that the caller can branch on (a first-class try/else lands with
|
||||
# the error-handling work). Determinism: Fs.list is sorted, so a directory walk
|
||||
# is reproducible across runs and platforms.
|
||||
#
|
||||
# Coverage (v1): paths use "/" and the native (macOS/BSD) filesystem — the fully
|
||||
# supported target today. Windows separators, a sandboxed virtual FS for wasm,
|
||||
# recursive directory copy, and richer magic-byte sniffing are follow-ups. Text
|
||||
# helpers are UTF-8 (they tie into Unicode.*); write_text is atomic (tmp+rename)
|
||||
# so a crash mid-write never corrupts the previous file.
|
||||
|
||||
function is_fs_ns(meth: pointer) -> bool {
|
||||
if (meth == "exists") or (meth == "is_dir") or (meth == "read_text") { return true }
|
||||
if (meth == "write_text") or (meth == "append_text") or (meth == "remove") { return true }
|
||||
if (meth == "size") or (meth == "mkdir") or (meth == "copy") or (meth == "list") { return true }
|
||||
return false
|
||||
}
|
||||
function is_path_ns(meth: pointer) -> bool {
|
||||
if (meth == "join") or (meth == "dir") or (meth == "base") { return true }
|
||||
if (meth == "ext") or (meth == "stem") or (meth == "normalize") { return true }
|
||||
return false
|
||||
}
|
||||
function is_mime_ns(meth: pointer) -> bool {
|
||||
if (meth == "of") or (meth == "sniff") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
function emit_fs_ns(meth: pointer, e: Node) -> Val {
|
||||
g_uses_fsrt = true
|
||||
let a = emit_expr(e.kids[0])
|
||||
if (meth == "exists") { return val(emit_bind(`call i32 @fn_fs_exists(ptr {a.code})`), "bool") }
|
||||
if (meth == "is_dir") { return val(emit_bind(`call i32 @fn_fs_is_dir(ptr {a.code})`), "bool") }
|
||||
if (meth == "read_text") { return val(emit_bind(`call ptr @fn_fs_read_text(ptr {a.code})`), "string") }
|
||||
if (meth == "remove") { return val(emit_bind(`call i32 @fn_fs_remove(ptr {a.code})`), "bool") }
|
||||
if (meth == "size") { return val(emit_bind(`call i32 @fn_fs_size(ptr {a.code})`), "int") }
|
||||
if (meth == "mkdir") { return val(emit_bind(`call i32 @fn_fs_mkdir(ptr {a.code})`), "bool") }
|
||||
if (meth == "list") { return val(emit_bind(`call ptr @fn_fs_list(ptr {a.code})`), "[]string") }
|
||||
let b = emit_expr(e.kids[1])
|
||||
if (meth == "write_text") { return val(emit_bind(`call i32 @fn_fs_write_text(ptr {a.code}, ptr {b.code})`), "bool") }
|
||||
if (meth == "append_text") { return val(emit_bind(`call i32 @fn_fs_append_text(ptr {a.code}, ptr {b.code})`), "bool") }
|
||||
# copy
|
||||
return val(emit_bind(`call i32 @fn_fs_copy(ptr {a.code}, ptr {b.code})`), "bool")
|
||||
}
|
||||
|
||||
function emit_path_ns(meth: pointer, e: Node) -> Val {
|
||||
g_uses_fsrt = true
|
||||
let a = emit_expr(e.kids[0])
|
||||
if (meth == "dir") { return val(emit_bind(`call ptr @fn_path_dir(ptr {a.code})`), "string") }
|
||||
if (meth == "base") { return val(emit_bind(`call ptr @fn_path_base(ptr {a.code})`), "string") }
|
||||
if (meth == "ext") { return val(emit_bind(`call ptr @fn_path_ext(ptr {a.code})`), "string") }
|
||||
if (meth == "stem") { return val(emit_bind(`call ptr @fn_path_stem(ptr {a.code})`), "string") }
|
||||
if (meth == "normalize") { return val(emit_bind(`call ptr @fn_path_norm(ptr {a.code})`), "string") }
|
||||
# join
|
||||
let b = emit_expr(e.kids[1])
|
||||
return val(emit_bind(`call ptr @fn_path_join(ptr {a.code}, ptr {b.code})`), "string")
|
||||
}
|
||||
|
||||
function emit_mime_ns(meth: pointer, e: Node) -> Val {
|
||||
g_uses_fsrt = true
|
||||
let a = emit_expr(e.kids[0])
|
||||
if (meth == "sniff") { return val(emit_bind(`call ptr @fn_mime_sniff(ptr {a.code})`), "string") }
|
||||
return val(emit_bind(`call ptr @fn_mime_of(ptr {a.code})`), "string")
|
||||
}
|
||||
|
||||
# emit_fs_prelude — the Fs/Path/Mime runtime, emitted once per program that uses
|
||||
# any of the three namespaces (g_uses_fsrt). Pure libc + byte IR.
|
||||
function emit_fs_prelude() -> void {
|
||||
emith("declare i32 @access(ptr, i32)\n")
|
||||
emith("declare i32 @mkdir(ptr, i32)\n")
|
||||
emith("declare i32 @remove(ptr)\n")
|
||||
emith("declare i32 @rename(ptr, ptr)\n")
|
||||
emith("declare ptr @opendir(ptr)\n")
|
||||
emith("declare ptr @readdir(ptr)\n")
|
||||
emith("declare i32 @closedir(ptr)\n")
|
||||
emith("declare i32 @strcmp(ptr, ptr)\n")
|
||||
emit_fs_common()
|
||||
emit_fs_path()
|
||||
emit_fs_io()
|
||||
emit_fs_dir()
|
||||
emit_fs_mime()
|
||||
}
|
||||
|
||||
# ---- shared helpers --------------------------------------------------------
|
||||
function emit_fs_common() -> void {
|
||||
# duplicate %n bytes of %s into a fresh NUL-terminated buffer
|
||||
emith("define ptr @fn_fs_dup(ptr %s, i32 %n) {\n")
|
||||
emith("entry:\n %nz = zext i32 %n to i64\n %t = add i64 %nz, 1\n %m = call ptr @malloc(i64 %t)\n call ptr @memcpy(ptr %m, ptr %s, i64 %nz)\n")
|
||||
emith(" %end = getelementptr i8, ptr %m, i32 %n\n store i8 0, ptr %end\n ret ptr %m\n}\n")
|
||||
|
||||
emith("define ptr @fn_fs_strdup(ptr %s) {\n")
|
||||
emith("entry:\n %l = call i64 @strlen(ptr %s)\n %li = trunc i64 %l to i32\n %r = call ptr @fn_fs_dup(ptr %s, i32 %li)\n ret ptr %r\n}\n")
|
||||
|
||||
# index of the last '/' in %s, or -1 if none
|
||||
emith("define i32 @fn_fs_lastslash(ptr %s) {\n")
|
||||
emith("entry:\n %ip = alloca i32\n %rp = alloca i32\n store i32 0, ptr %ip\n store i32 -1, ptr %rp\n br label %lp\n")
|
||||
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
||||
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %done, label %go\n")
|
||||
emith("go:\n %sl = icmp eq i32 %c, 47\n br i1 %sl, label %set, label %nx\n")
|
||||
emith("set:\n store i32 %i, ptr %rp\n br label %nx\n")
|
||||
emith("nx:\n %i1 = add i32 %i, 1\n store i32 %i1, ptr %ip\n br label %lp\n")
|
||||
emith("done:\n %r = load i32, ptr %rp\n ret i32 %r\n}\n")
|
||||
}
|
||||
|
||||
# ---- Path.* (pure string) --------------------------------------------------
|
||||
function emit_fs_path() -> void {
|
||||
# join(a, b): b absolute -> b; a empty -> b; b empty -> a; else a + "/" + b
|
||||
# (avoiding a doubled separator when a already ends with one).
|
||||
emith("define ptr @fn_path_join(ptr %a, ptr %b) {\n")
|
||||
emith("entry:\n %la64 = call i64 @strlen(ptr %a)\n %la = trunc i64 %la64 to i32\n %lb64 = call i64 @strlen(ptr %b)\n %lb = trunc i64 %lb64 to i32\n")
|
||||
emith(" %b0 = load i8, ptr %b\n %b0i = zext i8 %b0 to i32\n %babs = icmp eq i32 %b0i, 47\n br i1 %babs, label %retb, label %ka\n")
|
||||
emith("retb:\n %rb = call ptr @fn_fs_strdup(ptr %b)\n ret ptr %rb\n")
|
||||
emith("ka:\n %aemp = icmp eq i32 %la, 0\n br i1 %aemp, label %retb2, label %kb\n")
|
||||
emith("retb2:\n %rb2 = call ptr @fn_fs_strdup(ptr %b)\n ret ptr %rb2\n")
|
||||
emith("kb:\n %bemp = icmp eq i32 %lb, 0\n br i1 %bemp, label %reta, label %chk\n")
|
||||
emith("reta:\n %ra = call ptr @fn_fs_strdup(ptr %a)\n ret ptr %ra\n")
|
||||
emith("chk:\n %lam1 = sub i32 %la, 1\n %pe = getelementptr i8, ptr %a, i32 %lam1\n %ae = load i8, ptr %pe\n %aei = zext i8 %ae to i32\n %ends = icmp eq i32 %aei, 47\n")
|
||||
emith(" %sep = select i1 %ends, i32 0, i32 1\n %tot = add i32 %la, %lb\n %tot2 = add i32 %tot, %sep\n %tot3 = add i32 %tot2, 1\n %totz = zext i32 %tot3 to i64\n %m = call ptr @malloc(i64 %totz)\n")
|
||||
emith(" %laz = zext i32 %la to i64\n call ptr @memcpy(ptr %m, ptr %a, i64 %laz)\n")
|
||||
# write separator if needed
|
||||
emith(" br i1 %ends, label %nosep, label %wsep\n")
|
||||
emith("wsep:\n %sp = getelementptr i8, ptr %m, i32 %la\n store i8 47, ptr %sp\n br label %after\n")
|
||||
emith("nosep:\n br label %after\n")
|
||||
emith("after:\n %boff = add i32 %la, %sep\n %dp = getelementptr i8, ptr %m, i32 %boff\n %lbz = zext i32 %lb to i64\n call ptr @memcpy(ptr %dp, ptr %b, i64 %lbz)\n")
|
||||
emith(" %endoff = add i32 %boff, %lb\n %ep = getelementptr i8, ptr %m, i32 %endoff\n store i8 0, ptr %ep\n ret ptr %m\n}\n")
|
||||
|
||||
# base(p): the component after the last '/', or p itself
|
||||
emith("define ptr @fn_path_base(ptr %s) {\n")
|
||||
emith("entry:\n %ls = call i32 @fn_fs_lastslash(ptr %s)\n %none = icmp eq i32 %ls, -1\n br i1 %none, label %whole, label %tail\n")
|
||||
emith("whole:\n %r = call ptr @fn_fs_strdup(ptr %s)\n ret ptr %r\n")
|
||||
emith("tail:\n %st = add i32 %ls, 1\n %p = getelementptr i8, ptr %s, i32 %st\n %r2 = call ptr @fn_fs_strdup(ptr %p)\n ret ptr %r2\n}\n")
|
||||
|
||||
# dir(p): everything before the last '/', or "." if none; "/" stays "/"
|
||||
emith("define ptr @fn_path_dir(ptr %s) {\n")
|
||||
emith("entry:\n %ls = call i32 @fn_fs_lastslash(ptr %s)\n %none = icmp eq i32 %ls, -1\n br i1 %none, label %dot, label %chk0\n")
|
||||
emith("dot:\n %d = call ptr @fn_fs_strdup(ptr @fn_str_dot)\n ret ptr %d\n")
|
||||
emith("chk0:\n %isroot = icmp eq i32 %ls, 0\n br i1 %isroot, label %root, label %cut\n")
|
||||
emith("root:\n %r = call ptr @fn_fs_strdup(ptr @fn_str_slash)\n ret ptr %r\n")
|
||||
emith("cut:\n %r2 = call ptr @fn_fs_dup(ptr %s, i32 %ls)\n ret ptr %r2\n}\n")
|
||||
|
||||
# ext(p): from the last '.' in the base component to the end, incl. the dot;
|
||||
# "" when the base has no '.' or begins with '.' (a dotfile has no extension)
|
||||
emith("define ptr @fn_path_ext(ptr %s) {\n")
|
||||
emith("entry:\n %ls = call i32 @fn_fs_lastslash(ptr %s)\n %bstart = add i32 %ls, 1\n") # ls=-1 -> bstart=0
|
||||
emith(" %dp = alloca i32\n %ip = alloca i32\n store i32 -1, ptr %dp\n store i32 %bstart, ptr %ip\n br label %lp\n")
|
||||
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n %z = icmp eq i32 %c, 0\n br i1 %z, label %done, label %go\n")
|
||||
emith("go:\n %dot = icmp eq i32 %c, 46\n br i1 %dot, label %sd, label %nx\n")
|
||||
emith("sd:\n store i32 %i, ptr %dp\n br label %nx\n")
|
||||
emith("nx:\n %i1 = add i32 %i, 1\n store i32 %i1, ptr %ip\n br label %lp\n")
|
||||
emith("done:\n %d = load i32, ptr %dp\n %nod = icmp eq i32 %d, -1\n br i1 %nod, label %empty, label %chkpos\n")
|
||||
emith("empty:\n %e = call ptr @fn_fs_strdup(ptr @fn_str_empty)\n ret ptr %e\n")
|
||||
emith("chkpos:\n %atstart = icmp eq i32 %d, %bstart\n br i1 %atstart, label %empty2, label %take\n")
|
||||
emith("empty2:\n %e2 = call ptr @fn_fs_strdup(ptr @fn_str_empty)\n ret ptr %e2\n")
|
||||
emith("take:\n %pp = getelementptr i8, ptr %s, i32 %d\n %r = call ptr @fn_fs_strdup(ptr %pp)\n ret ptr %r\n}\n")
|
||||
|
||||
# stem(p): the base component without its extension
|
||||
emith("define ptr @fn_path_stem(ptr %s) {\n")
|
||||
emith("entry:\n %base = call ptr @fn_path_base(ptr %s)\n %ext = call ptr @fn_path_ext(ptr %s)\n")
|
||||
emith(" %bl64 = call i64 @strlen(ptr %base)\n %bl = trunc i64 %bl64 to i32\n %el64 = call i64 @strlen(ptr %ext)\n %el = trunc i64 %el64 to i32\n")
|
||||
emith(" %keep = sub i32 %bl, %el\n %r = call ptr @fn_fs_dup(ptr %base, i32 %keep)\n ret ptr %r\n}\n")
|
||||
|
||||
emit_fs_normalize()
|
||||
}
|
||||
|
||||
# normalize(p): collapse duplicate '/', drop "." segments, and resolve ".." by
|
||||
# popping the previous kept segment (never above an absolute root). Preserves a
|
||||
# leading '/'. An empty result becomes ".".
|
||||
function emit_fs_normalize() -> void {
|
||||
emith("define ptr @fn_path_norm(ptr %s) {\n")
|
||||
emith("entry:\n %len64 = call i64 @strlen(ptr %s)\n %len = trunc i64 %len64 to i32\n %cap = add i32 %len, 2\n %capz = zext i32 %cap to i64\n")
|
||||
emith(" %out = call ptr @malloc(i64 %capz)\n")
|
||||
# segst holds output offsets (i32) of each kept segment's start; size cap ints
|
||||
emith(" %stz = mul i32 %cap, 4\n %stzz = zext i32 %stz to i64\n %segst = call ptr @malloc(i64 %stzz)\n")
|
||||
emith(" %b0 = load i8, ptr %s\n %b0i = zext i8 %b0 to i32\n %abs = icmp eq i32 %b0i, 47\n")
|
||||
emith(" %ip = alloca i32\n %op = alloca i32\n %scp = alloca i32\n store i32 0, ptr %ip\n store i32 0, ptr %scp\n")
|
||||
emith(" br i1 %abs, label %ldr, label %noldr\n")
|
||||
emith("ldr:\n store i8 47, ptr %out\n store i32 1, ptr %op\n br label %seg\n")
|
||||
emith("noldr:\n store i32 0, ptr %op\n br label %seg\n")
|
||||
emith("seg:\n br label %sksl\n")
|
||||
# skip leading slashes between segments
|
||||
emith("sksl:\n %i = load i32, ptr %ip\n %pp = getelementptr i8, ptr %s, i32 %i\n %cc = load i8, ptr %pp\n %cci = zext i8 %cc to i32\n %issl = icmp eq i32 %cci, 47\n br i1 %issl, label %adv, label %segstart\n")
|
||||
emith("adv:\n %i2 = add i32 %i, 1\n store i32 %i2, ptr %ip\n br label %sksl\n")
|
||||
# gather one segment [sstart, send)
|
||||
emith("segstart:\n %ss = load i32, ptr %ip\n br label %scan\n")
|
||||
emith("scan:\n %j = load i32, ptr %ip\n %pj = getelementptr i8, ptr %s, i32 %j\n %cj = load i8, ptr %pj\n %cji = zext i8 %cj to i32\n %endc = icmp eq i32 %cji, 0\n %slc = icmp eq i32 %cji, 47\n %stop = or i1 %endc, %slc\n br i1 %stop, label %seghave, label %scanadv\n")
|
||||
emith("scanadv:\n %j1 = add i32 %j, 1\n store i32 %j1, ptr %ip\n br label %scan\n")
|
||||
emith("seghave:\n %se = load i32, ptr %ip\n %slen = sub i32 %se, %ss\n %emptyseg = icmp eq i32 %slen, 0\n br i1 %emptyseg, label %atend, label %classify\n")
|
||||
# empty segment only happens at the very end (trailing slashes) -> finish
|
||||
emith("classify:\n")
|
||||
# is it "." ? (len 1, char '.')
|
||||
emith(" %is1 = icmp eq i32 %slen, 1\n %c0p = getelementptr i8, ptr %s, i32 %ss\n %c0 = load i8, ptr %c0p\n %c0i = zext i8 %c0 to i32\n %isdotchar = icmp eq i32 %c0i, 46\n %isdot = and i1 %is1, %isdotchar\n br i1 %isdot, label %contseg, label %chkdd\n")
|
||||
# is it ".." ?
|
||||
emith("chkdd:\n %is2 = icmp eq i32 %slen, 2\n %d0p = getelementptr i8, ptr %s, i32 %ss\n %d0 = load i8, ptr %d0p\n %d0i = zext i8 %d0 to i32\n %d1o = add i32 %ss, 1\n %d1p = getelementptr i8, ptr %s, i32 %d1o\n %d1 = load i8, ptr %d1p\n %d1i = zext i8 %d1 to i32\n")
|
||||
emith(" %dd0 = icmp eq i32 %d0i, 46\n %dd1 = icmp eq i32 %d1i, 46\n %ddx = and i1 %dd0, %dd1\n %isdd = and i1 %is2, %ddx\n br i1 %isdd, label %dotdot, label %keepseg\n")
|
||||
# ".." : pop a segment if we have one, else (relative) keep it literally
|
||||
emith("dotdot:\n %sc = load i32, ptr %scp\n %has = icmp sgt i32 %sc, 0\n br i1 %has, label %pop, label %chkrel\n")
|
||||
emith("pop:\n %sc1 = sub i32 %sc, 1\n %stp = getelementptr i32, ptr %segst, i32 %sc1\n %newop = load i32, ptr %stp\n store i32 %newop, ptr %op\n store i32 %sc1, ptr %scp\n br label %contseg\n")
|
||||
emith("chkrel:\n br i1 %abs, label %contseg, label %keepseg\n") # absolute: drop; relative: keep ".."
|
||||
# keep the segment: record its start, append it + a trailing '/'
|
||||
emith("keepseg:\n %sc2 = load i32, ptr %scp\n %o0 = load i32, ptr %op\n %stp2 = getelementptr i32, ptr %segst, i32 %sc2\n store i32 %o0, ptr %stp2\n %sc2n = add i32 %sc2, 1\n store i32 %sc2n, ptr %scp\n")
|
||||
emith(" %cpp = alloca i32\n store i32 %ss, ptr %cpp\n br label %cpy\n")
|
||||
emith("cpy:\n %k = load i32, ptr %cpp\n %klt = icmp slt i32 %k, %se\n br i1 %klt, label %cpyb, label %cpysl\n")
|
||||
emith("cpyb:\n %skp = getelementptr i8, ptr %s, i32 %k\n %sk = load i8, ptr %skp\n %oo = load i32, ptr %op\n %dkp = getelementptr i8, ptr %out, i32 %oo\n store i8 %sk, ptr %dkp\n %oo1 = add i32 %oo, 1\n store i32 %oo1, ptr %op\n %k1 = add i32 %k, 1\n store i32 %k1, ptr %cpp\n br label %cpy\n")
|
||||
emith("cpysl:\n %oc = load i32, ptr %op\n %scp3 = getelementptr i8, ptr %out, i32 %oc\n store i8 47, ptr %scp3\n %oc1 = add i32 %oc, 1\n store i32 %oc1, ptr %op\n br label %contseg\n")
|
||||
emith("contseg:\n %ci = load i32, ptr %ip\n %cip = getelementptr i8, ptr %s, i32 %ci\n %cic = load i8, ptr %cip\n %cici = zext i8 %cic to i32\n %atz = icmp eq i32 %cici, 0\n br i1 %atz, label %finish, label %seg\n")
|
||||
emith("atend:\n br label %finish\n")
|
||||
# trim a trailing '/' (unless the whole result is just "/"), then NUL-terminate
|
||||
emith("finish:\n %fo = load i32, ptr %op\n %gt1 = icmp sgt i32 %fo, 1\n br i1 %gt1, label %trimchk, label %fdefault\n")
|
||||
emith("trimchk:\n %lasto = sub i32 %fo, 1\n %lop = getelementptr i8, ptr %out, i32 %lasto\n %lc = load i8, ptr %lop\n %lci = zext i8 %lc to i32\n %istsl = icmp eq i32 %lci, 47\n br i1 %istsl, label %trim, label %term\n")
|
||||
emith("trim:\n store i32 %lasto, ptr %op\n br label %term\n")
|
||||
emith("fdefault:\n br label %term\n")
|
||||
emith("term:\n %eo = load i32, ptr %op\n %ez = icmp eq i32 %eo, 0\n br i1 %ez, label %emptyout, label %putnul\n")
|
||||
emith("emptyout:\n store i8 46, ptr %out\n %e1 = getelementptr i8, ptr %out, i32 1\n store i8 0, ptr %e1\n ret ptr %out\n")
|
||||
emith("putnul:\n %ep = getelementptr i8, ptr %out, i32 %eo\n store i8 0, ptr %ep\n ret ptr %out\n}\n")
|
||||
}
|
||||
|
||||
# ---- Fs.* (libc) -----------------------------------------------------------
|
||||
function emit_fs_io() -> void {
|
||||
emith("define i32 @fn_fs_exists(ptr %p) {\n")
|
||||
emith("entry:\n %r = call i32 @access(ptr %p, i32 0)\n %ok = icmp eq i32 %r, 0\n %z = zext i1 %ok to i32\n ret i32 %z\n}\n")
|
||||
|
||||
# is_dir: opendir succeeds iff it is a directory
|
||||
emith("define i32 @fn_fs_is_dir(ptr %p) {\n")
|
||||
emith("entry:\n %d = call ptr @opendir(ptr %p)\n %nz = icmp ne ptr %d, null\n br i1 %nz, label %yes, label %no\n")
|
||||
emith("yes:\n call i32 @closedir(ptr %d)\n ret i32 1\n")
|
||||
emith("no:\n ret i32 0\n}\n")
|
||||
|
||||
# size: bytes via fseek/ftell, or -1 if it cannot be opened
|
||||
emith("define i32 @fn_fs_size(ptr %p) {\n")
|
||||
emith("entry:\n %f = call ptr @fopen(ptr %p, ptr @fn_str_rb)\n %nz = icmp ne ptr %f, null\n br i1 %nz, label %ok, label %bad\n")
|
||||
emith("bad:\n ret i32 -1\n")
|
||||
emith("ok:\n call i32 @fseek(ptr %f, i64 0, i32 2)\n %n = call i64 @ftell(ptr %f)\n call i32 @fclose(ptr %f)\n %ni = trunc i64 %n to i32\n ret i32 %ni\n}\n")
|
||||
|
||||
# readall: whole file into a fresh buffer; store byte length to %lenout; null on
|
||||
# failure. The buffer is NUL-terminated (one past the length) so text callers
|
||||
# can use it directly while binary callers use the length.
|
||||
emith("define ptr @fn_fs_readall(ptr %p, ptr %lenout) {\n")
|
||||
emith("entry:\n store i32 0, ptr %lenout\n %f = call ptr @fopen(ptr %p, ptr @fn_str_rb)\n %nz = icmp ne ptr %f, null\n br i1 %nz, label %ok, label %bad\n")
|
||||
emith("bad:\n ret ptr null\n")
|
||||
emith("ok:\n call i32 @fseek(ptr %f, i64 0, i32 2)\n %n64 = call i64 @ftell(ptr %f)\n call i32 @fseek(ptr %f, i64 0, i32 0)\n %n = trunc i64 %n64 to i32\n")
|
||||
emith(" %cap = add i64 %n64, 1\n %m = call ptr @malloc(i64 %cap)\n %rd = call i64 @fread(ptr %m, i64 1, i64 %n64, ptr %f)\n call i32 @fclose(ptr %f)\n")
|
||||
emith(" %rdi = trunc i64 %rd to i32\n %endp = getelementptr i8, ptr %m, i32 %rdi\n store i8 0, ptr %endp\n store i32 %rdi, ptr %lenout\n ret ptr %m\n}\n")
|
||||
|
||||
emith("define ptr @fn_fs_read_text(ptr %p) {\n")
|
||||
emith("entry:\n %lp = alloca i32\n %r = call ptr @fn_fs_readall(ptr %p, ptr %lp)\n ret ptr %r\n}\n")
|
||||
|
||||
# write_text: write to "<p>.tmp" then rename over %p, so a crash mid-write
|
||||
# never truncates the previous file. Returns 1 on success.
|
||||
emith("define i32 @fn_fs_write_text(ptr %p, ptr %s) {\n")
|
||||
emith("entry:\n %tmp = call ptr @fn_path_join_ext(ptr %p, ptr @fn_str_dottmp)\n %f = call ptr @fopen(ptr %tmp, ptr @fn_str_wb)\n %nz = icmp ne ptr %f, null\n br i1 %nz, label %ok, label %bad\n")
|
||||
emith("bad:\n ret i32 0\n")
|
||||
emith("ok:\n %n = call i64 @strlen(ptr %s)\n %w = call i64 @fwrite(ptr %s, i64 1, i64 %n, ptr %f)\n call i32 @fclose(ptr %f)\n")
|
||||
emith(" %rr = call i32 @rename(ptr %tmp, ptr %p)\n %ok2 = icmp eq i32 %rr, 0\n %z = zext i1 %ok2 to i32\n ret i32 %z\n}\n")
|
||||
|
||||
# concat two strings (used to build "<p>.tmp"); local so write_text needs no
|
||||
# dependency on the Os prelude
|
||||
emith("define ptr @fn_path_join_ext(ptr %a, ptr %b) {\n")
|
||||
emith("entry:\n %la = call i64 @strlen(ptr %a)\n %lb = call i64 @strlen(ptr %b)\n %sum = add i64 %la, %lb\n %tot = add i64 %sum, 1\n %m = call ptr @malloc(i64 %tot)\n")
|
||||
emith(" call ptr @memcpy(ptr %m, ptr %a, i64 %la)\n %m2 = getelementptr i8, ptr %m, i64 %la\n call ptr @memcpy(ptr %m2, ptr %b, i64 %lb)\n %ep = getelementptr i8, ptr %m, i64 %sum\n store i8 0, ptr %ep\n ret ptr %m\n}\n")
|
||||
|
||||
emith("define i32 @fn_fs_append_text(ptr %p, ptr %s) {\n")
|
||||
emith("entry:\n %f = call ptr @fopen(ptr %p, ptr @fn_str_ab)\n %nz = icmp ne ptr %f, null\n br i1 %nz, label %ok, label %bad\n")
|
||||
emith("bad:\n ret i32 0\n")
|
||||
emith("ok:\n %n = call i64 @strlen(ptr %s)\n call i64 @fwrite(ptr %s, i64 1, i64 %n, ptr %f)\n call i32 @fclose(ptr %f)\n ret i32 1\n}\n")
|
||||
|
||||
emith("define i32 @fn_fs_remove(ptr %p) {\n")
|
||||
emith("entry:\n %r = call i32 @remove(ptr %p)\n %ok = icmp eq i32 %r, 0\n %z = zext i1 %ok to i32\n ret i32 %z\n}\n")
|
||||
|
||||
# copy: byte-for-byte via readall + a sized write. Returns 1 on success.
|
||||
emith("define i32 @fn_fs_copy(ptr %src, ptr %dst) {\n")
|
||||
emith("entry:\n %lp = alloca i32\n %buf = call ptr @fn_fs_readall(ptr %src, ptr %lp)\n %nz = icmp ne ptr %buf, null\n br i1 %nz, label %ok, label %bad\n")
|
||||
emith("bad:\n ret i32 0\n")
|
||||
emith("ok:\n %tmp = call ptr @fn_path_join_ext(ptr %dst, ptr @fn_str_dottmp)\n %f = call ptr @fopen(ptr %tmp, ptr @fn_str_wb)\n %fnz = icmp ne ptr %f, null\n br i1 %fnz, label %w, label %bad\n")
|
||||
emith("w:\n %n = load i32, ptr %lp\n %nz64 = zext i32 %n to i64\n call i64 @fwrite(ptr %buf, i64 1, i64 %nz64, ptr %f)\n call i32 @fclose(ptr %f)\n %rr = call i32 @rename(ptr %tmp, ptr %dst)\n %ok2 = icmp eq i32 %rr, 0\n %z = zext i1 %ok2 to i32\n ret i32 %z\n}\n")
|
||||
|
||||
# mkdir: create %p and any missing parents (mkdir -p). Returns 1 if the
|
||||
# directory exists afterwards. Intermediate EEXIST errors are ignored.
|
||||
emith("define i32 @fn_fs_mkdir(ptr %p) {\n")
|
||||
emith("entry:\n %dup = call ptr @fn_fs_strdup(ptr %p)\n %ip = alloca i32\n store i32 1, ptr %ip\n br label %lp\n")
|
||||
emith("lp:\n %i = load i32, ptr %ip\n %pp = getelementptr i8, ptr %dup, i32 %i\n %c = load i8, ptr %pp\n %ci = zext i8 %c to i32\n %z = icmp eq i32 %ci, 0\n br i1 %z, label %fin, label %go\n")
|
||||
emith("go:\n %sl = icmp eq i32 %ci, 47\n br i1 %sl, label %cut, label %nx\n")
|
||||
emith("cut:\n store i8 0, ptr %pp\n call i32 @mkdir(ptr %dup, i32 493)\n store i8 47, ptr %pp\n br label %nx\n")
|
||||
emith("nx:\n %i1 = add i32 %i, 1\n store i32 %i1, ptr %ip\n br label %lp\n")
|
||||
emith("fin:\n call i32 @mkdir(ptr %dup, i32 493)\n %r = call i32 @fn_fs_is_dir(ptr %p)\n ret i32 %r\n}\n")
|
||||
}
|
||||
|
||||
# ---- Fs.list (+ sort) ------------------------------------------------------
|
||||
function emit_fs_dir() -> void {
|
||||
# list(dir) -> []string of entries (excluding "." and ".."), sorted ascending
|
||||
# by byte value for a stable, reproducible order. Each name is copied out of
|
||||
# the readdir buffer before the next call. macOS/BSD dirent: d_name at offset
|
||||
# 21 (documented native layout).
|
||||
emith("define ptr @fn_fs_list(ptr %path) {\n")
|
||||
emith("entry:\n %h = call ptr @malloc(i64 16)\n %d0 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 0\n %d1 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 1\n %d2 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 2\n")
|
||||
emith(" %datap = alloca ptr\n %cntp = alloca i32\n %capp = alloca i32\n %init = call ptr @malloc(i64 128)\n store ptr %init, ptr %datap\n store i32 0, ptr %cntp\n store i32 16, ptr %capp\n")
|
||||
emith(" %dir = call ptr @opendir(ptr %path)\n %dnz = icmp ne ptr %dir, null\n br i1 %dnz, label %rd, label %empty\n")
|
||||
emith("rd:\n %de = call ptr @readdir(ptr %dir)\n %denz = icmp ne ptr %de, null\n br i1 %denz, label %ent, label %closed\n")
|
||||
emith("ent:\n %namep = getelementptr i8, ptr %de, i32 21\n")
|
||||
# skip "." and ".."
|
||||
emith(" %n0 = load i8, ptr %namep\n %n0i = zext i8 %n0 to i32\n %isdot0 = icmp eq i32 %n0i, 46\n br i1 %isdot0, label %chkdots, label %keep\n")
|
||||
emith("chkdots:\n %n1p = getelementptr i8, ptr %namep, i32 1\n %n1 = load i8, ptr %n1p\n %n1i = zext i8 %n1 to i32\n %n1z = icmp eq i32 %n1i, 0\n br i1 %n1z, label %rd, label %chkdd\n") # "." -> skip
|
||||
emith("chkdd:\n %isdot1 = icmp eq i32 %n1i, 46\n br i1 %isdot1, label %chkdd2, label %keep\n")
|
||||
emith("chkdd2:\n %n2p = getelementptr i8, ptr %namep, i32 2\n %n2 = load i8, ptr %n2p\n %n2i = zext i8 %n2 to i32\n %n2z = icmp eq i32 %n2i, 0\n br i1 %n2z, label %rd, label %keep\n") # ".." -> skip
|
||||
emith("keep:\n %nm = call ptr @fn_fs_strdup(ptr %namep)\n")
|
||||
# grow if full
|
||||
emith(" %cnt = load i32, ptr %cntp\n %cap = load i32, ptr %capp\n %full = icmp sge i32 %cnt, %cap\n br i1 %full, label %grow, label %put\n")
|
||||
emith("grow:\n %nc = mul i32 %cap, 2\n store i32 %nc, ptr %capp\n %ncz = zext i32 %nc to i64\n %nb = mul i64 %ncz, 8\n %old = load ptr, ptr %datap\n %new = call ptr @realloc(ptr %old, i64 %nb)\n store ptr %new, ptr %datap\n br label %put\n")
|
||||
emith("put:\n %data = load ptr, ptr %datap\n %cnt2 = load i32, ptr %cntp\n %slot = getelementptr ptr, ptr %data, i32 %cnt2\n store ptr %nm, ptr %slot\n %cnt3 = add i32 %cnt2, 1\n store i32 %cnt3, ptr %cntp\n br label %rd\n")
|
||||
emith("closed:\n call i32 @closedir(ptr %dir)\n br label %sortit\n")
|
||||
emith("empty:\n br label %sortit\n")
|
||||
# insertion sort the ptr array by strcmp
|
||||
emith("sortit:\n %fdata = load ptr, ptr %datap\n %fcnt = load i32, ptr %cntp\n %aip = alloca i32\n store i32 1, ptr %aip\n br label %so\n")
|
||||
emith("so:\n %ai = load i32, ptr %aip\n %ailt = icmp slt i32 %ai, %fcnt\n br i1 %ailt, label %sob, label %sod\n")
|
||||
emith("sob:\n %kp = getelementptr ptr, ptr %fdata, i32 %ai\n %key = load ptr, ptr %kp\n %jp = alloca i32\n %aim1 = sub i32 %ai, 1\n store i32 %aim1, ptr %jp\n br label %si\n")
|
||||
emith("si:\n %jj = load i32, ptr %jp\n %jge = icmp sge i32 %jj, 0\n br i1 %jge, label %sic, label %sins\n")
|
||||
emith("sic:\n %ejp = getelementptr ptr, ptr %fdata, i32 %jj\n %ej = load ptr, ptr %ejp\n %cmp = call i32 @strcmp(ptr %ej, ptr %key)\n %gt = icmp sgt i32 %cmp, 0\n br i1 %gt, label %shift, label %sins\n")
|
||||
emith("shift:\n %j1 = add i32 %jj, 1\n %dp1 = getelementptr ptr, ptr %fdata, i32 %j1\n store ptr %ej, ptr %dp1\n %jm = sub i32 %jj, 1\n store i32 %jm, ptr %jp\n br label %si\n")
|
||||
emith("sins:\n %jf = load i32, ptr %jp\n %jf1 = add i32 %jf, 1\n %insp = getelementptr ptr, ptr %fdata, i32 %jf1\n store ptr %key, ptr %insp\n %ai1 = add i32 %ai, 1\n store i32 %ai1, ptr %aip\n br label %so\n")
|
||||
emith("sod:\n store ptr %fdata, ptr %d0\n store i32 %fcnt, ptr %d1\n store i32 %fcnt, ptr %d2\n ret ptr %h\n}\n")
|
||||
}
|
||||
|
||||
# ---- Mime.* ----------------------------------------------------------------
|
||||
function emit_fs_mime() -> void {
|
||||
# the extension -> MIME table, as parallel lists. Kept small and documented;
|
||||
# unknown extensions fall through to application/octet-stream.
|
||||
let exts = new []pointer; let tys = new []pointer
|
||||
push(exts, "png"); push(tys, "image/png")
|
||||
push(exts, "jpg"); push(tys, "image/jpeg")
|
||||
push(exts, "jpeg"); push(tys, "image/jpeg")
|
||||
push(exts, "gif"); push(tys, "image/gif")
|
||||
push(exts, "bmp"); push(tys, "image/bmp")
|
||||
push(exts, "webp"); push(tys, "image/webp")
|
||||
push(exts, "svg"); push(tys, "image/svg+xml")
|
||||
push(exts, "txt"); push(tys, "text/plain")
|
||||
push(exts, "md"); push(tys, "text/markdown")
|
||||
push(exts, "csv"); push(tys, "text/csv")
|
||||
push(exts, "html"); push(tys, "text/html")
|
||||
push(exts, "htm"); push(tys, "text/html")
|
||||
push(exts, "css"); push(tys, "text/css")
|
||||
push(exts, "js"); push(tys, "text/javascript")
|
||||
push(exts, "json"); push(tys, "application/json")
|
||||
push(exts, "xml"); push(tys, "application/xml")
|
||||
push(exts, "zip"); push(tys, "application/zip")
|
||||
push(exts, "pdf"); push(tys, "application/pdf")
|
||||
push(exts, "wav"); push(tys, "audio/wav")
|
||||
push(exts, "ogg"); push(tys, "audio/ogg")
|
||||
push(exts, "mp3"); push(tys, "audio/mpeg")
|
||||
push(exts, "ttf"); push(tys, "font/ttf")
|
||||
push(exts, "otf"); push(tys, "font/otf")
|
||||
push(exts, "ludic"); push(tys, "text/x-ludic")
|
||||
|
||||
# create the string constants FIRST (as module globals), then reference them
|
||||
let en = new []pointer; let tn = new []pointer
|
||||
var i = 0
|
||||
while i < len(exts) { push(en, emit_str_const(exts[i])); push(tn, emit_str_const(tys[i])); i = i + 1 }
|
||||
let k_octet = emit_str_const("application/octet-stream")
|
||||
let n = len(exts)
|
||||
|
||||
# the lookup table: an array of { extension, type } string-pointer pairs
|
||||
emith(`@mime_tbl = private unnamed_addr constant [{itoa(n)} x {{ ptr, ptr }}] [`)
|
||||
i = 0
|
||||
while i < len(exts) {
|
||||
if i > 0 { emith(", ") }
|
||||
emith(`{{ ptr, ptr }} {{ ptr {en[i]}, ptr {tn[i]} }}`)
|
||||
i = i + 1
|
||||
}
|
||||
emith("]\n")
|
||||
|
||||
# of(path): lowercased extension, then a linear scan of the table
|
||||
emith("define ptr @fn_mime_of(ptr %path) {\n")
|
||||
emith("entry:\n %ext = call ptr @fn_path_ext(ptr %path)\n %lc = call ptr @fn_mime_lc(ptr %ext)\n %ip = alloca i32\n store i32 0, ptr %ip\n br label %lp\n")
|
||||
emith(`lp:\n %i = load i32, ptr %ip\n %lt = icmp slt i32 %i, {itoa(n)}\n br i1 %lt, label %body, label %def\n`)
|
||||
emith(`body:\n %kp = getelementptr [{itoa(n)} x {{ ptr, ptr }}], ptr @mime_tbl, i32 0, i32 %i, i32 0\n %k = load ptr, ptr %kp\n %c = call i32 @strcmp(ptr %lc, ptr %k)\n %eq = icmp eq i32 %c, 0\n br i1 %eq, label %hit, label %nx\n`)
|
||||
emith(`hit:\n %vp = getelementptr [{itoa(n)} x {{ ptr, ptr }}], ptr @mime_tbl, i32 0, i32 %i, i32 1\n %v = load ptr, ptr %vp\n %r = call ptr @fn_fs_strdup(ptr %v)\n ret ptr %r\n`)
|
||||
emith("nx:\n %i1 = add i32 %i, 1\n store i32 %i1, ptr %ip\n br label %lp\n")
|
||||
emith(`def:\n %d = call ptr @fn_fs_strdup(ptr {k_octet})\n ret ptr %d\n}}\n`)
|
||||
|
||||
# lowercase an extension, dropping a leading '.' (ASCII only, for table lookup)
|
||||
emith("define ptr @fn_mime_lc(ptr %s) {\n")
|
||||
emith("entry:\n %l64 = call i64 @strlen(ptr %s)\n %l = trunc i64 %l64 to i32\n %cap = add i64 %l64, 1\n %out = call ptr @malloc(i64 %cap)\n")
|
||||
emith(" %b0 = load i8, ptr %s\n %b0i = zext i8 %b0 to i32\n %isdot = icmp eq i32 %b0i, 46\n %start = select i1 %isdot, i32 1, i32 0\n")
|
||||
emith(" %ip = alloca i32\n %op = alloca i32\n store i32 %start, ptr %ip\n store i32 0, ptr %op\n br label %lp\n")
|
||||
emith("lp:\n %i = load i32, ptr %ip\n %pp = getelementptr i8, ptr %s, i32 %i\n %c = load i8, ptr %pp\n %ci = zext i8 %c to i32\n %z = icmp eq i32 %ci, 0\n br i1 %z, label %done, label %go\n")
|
||||
emith("go:\n %ua = icmp uge i32 %ci, 65\n %ub = icmp ule i32 %ci, 90\n %up = and i1 %ua, %ub\n %lo = add i32 %ci, 32\n %m = select i1 %up, i32 %lo, i32 %ci\n %mt = trunc i32 %m to i8\n %o = load i32, ptr %op\n %dp = getelementptr i8, ptr %out, i32 %o\n store i8 %mt, ptr %dp\n %o1 = add i32 %o, 1\n store i32 %o1, ptr %op\n %i1 = add i32 %i, 1\n store i32 %i1, ptr %ip\n br label %lp\n")
|
||||
emith("done:\n %fo = load i32, ptr %op\n %ep = getelementptr i8, ptr %out, i32 %fo\n store i8 0, ptr %ep\n ret ptr %out\n}\n")
|
||||
|
||||
emit_fs_sniff()
|
||||
}
|
||||
|
||||
# sniff(path): read the first bytes and recognise a few well-known signatures,
|
||||
# otherwise fall back to the extension. Covers PNG/JPEG/GIF/PDF for now.
|
||||
function emit_fs_sniff() -> void {
|
||||
emith("define ptr @fn_mime_sniff(ptr %path) {\n")
|
||||
emith("entry:\n %f = call ptr @fopen(ptr %path, ptr @fn_str_rb)\n %nz = icmp ne ptr %f, null\n br i1 %nz, label %ok, label %fallback\n")
|
||||
emith("ok:\n %buf = call ptr @malloc(i64 16)\n %rd = call i64 @fread(ptr %buf, i64 1, i64 8, ptr %f)\n call i32 @fclose(ptr %f)\n %rdi = trunc i64 %rd to i32\n %has4 = icmp sge i32 %rdi, 4\n br i1 %has4, label %chk, label %fallback\n")
|
||||
emith("chk:\n %b0p = getelementptr i8, ptr %buf, i32 0\n %b0 = load i8, ptr %b0p\n %b0i = zext i8 %b0 to i32\n %b1p = getelementptr i8, ptr %buf, i32 1\n %b1 = load i8, ptr %b1p\n %b1i = zext i8 %b1 to i32\n %b2p = getelementptr i8, ptr %buf, i32 2\n %b2 = load i8, ptr %b2p\n %b2i = zext i8 %b2 to i32\n %b3p = getelementptr i8, ptr %buf, i32 3\n %b3 = load i8, ptr %b3p\n %b3i = zext i8 %b3 to i32\n")
|
||||
# PNG: 89 50 4E 47
|
||||
emith(" %p0 = icmp eq i32 %b0i, 137\n %p1 = icmp eq i32 %b1i, 80\n %p2 = icmp eq i32 %b2i, 78\n %p3 = icmp eq i32 %b3i, 71\n %pa = and i1 %p0, %p1\n %pb = and i1 %pa, %p2\n %pc = and i1 %pb, %p3\n br i1 %pc, label %png, label %cj\n")
|
||||
emith("png:\n %rpng = call ptr @fn_fs_strdup(ptr @fn_sig_png)\n ret ptr %rpng\n")
|
||||
# JPEG: FF D8 FF
|
||||
emith("cj:\n %j0 = icmp eq i32 %b0i, 255\n %j1 = icmp eq i32 %b1i, 216\n %j2 = icmp eq i32 %b2i, 255\n %ja = and i1 %j0, %j1\n %jb = and i1 %ja, %j2\n br i1 %jb, label %jpg, label %cg\n")
|
||||
emith("jpg:\n %rjpg = call ptr @fn_fs_strdup(ptr @fn_sig_jpg)\n ret ptr %rjpg\n")
|
||||
# GIF: 47 49 46
|
||||
emith("cg:\n %g0 = icmp eq i32 %b0i, 71\n %g1 = icmp eq i32 %b1i, 73\n %g2 = icmp eq i32 %b2i, 70\n %ga = and i1 %g0, %g1\n %gb = and i1 %ga, %g2\n br i1 %gb, label %gif, label %cp\n")
|
||||
emith("gif:\n %rgif = call ptr @fn_fs_strdup(ptr @fn_sig_gif)\n ret ptr %rgif\n")
|
||||
# PDF: 25 50 44 46
|
||||
emith("cp:\n %q0 = icmp eq i32 %b0i, 37\n %q1 = icmp eq i32 %b1i, 80\n %q2 = icmp eq i32 %b2i, 68\n %q3 = icmp eq i32 %b3i, 70\n %qa = and i1 %q0, %q1\n %qb = and i1 %qa, %q2\n %qc = and i1 %qb, %q3\n br i1 %qc, label %pdf, label %fallback\n")
|
||||
emith("pdf:\n %rpdf = call ptr @fn_fs_strdup(ptr @fn_sig_pdf)\n ret ptr %rpdf\n")
|
||||
emith("fallback:\n %r = call ptr @fn_mime_of(ptr %path)\n ret ptr %r\n}\n")
|
||||
|
||||
# the small string constants the Fs/Path/Mime runtime references
|
||||
emith("@fn_str_rb = private unnamed_addr constant [3 x i8] c\"rb\\00\"\n")
|
||||
emith("@fn_str_wb = private unnamed_addr constant [3 x i8] c\"wb\\00\"\n")
|
||||
emith("@fn_str_ab = private unnamed_addr constant [3 x i8] c\"ab\\00\"\n")
|
||||
emith("@fn_str_dot = private unnamed_addr constant [2 x i8] c\".\\00\"\n")
|
||||
emith("@fn_str_slash = private unnamed_addr constant [2 x i8] c\"/\\00\"\n")
|
||||
emith("@fn_str_empty = private unnamed_addr constant [1 x i8] c\"\\00\"\n")
|
||||
emith("@fn_str_dottmp = private unnamed_addr constant [5 x i8] c\".tmp\\00\"\n")
|
||||
emith("@fn_sig_png = private unnamed_addr constant [10 x i8] c\"image/png\\00\"\n")
|
||||
emith("@fn_sig_jpg = private unnamed_addr constant [11 x i8] c\"image/jpeg\\00\"\n")
|
||||
emith("@fn_sig_gif = private unnamed_addr constant [10 x i8] c\"image/gif\\00\"\n")
|
||||
emith("@fn_sig_pdf = private unnamed_addr constant [16 x i8] c\"application/pdf\\00\"\n")
|
||||
}
|
||||
195
selfhost/backend/stdlib/emit_hash.ludic
Normal file
195
selfhost/backend/stdlib/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)
|
||||
|
||||
function is_hash_ns(meth: pointer) -> 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.
|
||||
function hash_mix_code(x: pointer) -> pointer {
|
||||
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.
|
||||
function hash_mix64_code(x: pointer) -> pointer {
|
||||
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}`)
|
||||
}
|
||||
|
||||
function emit_hash_ns(meth: pointer, 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: pointer = "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.
|
||||
function 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")
|
||||
}
|
||||
506
selfhost/backend/stdlib/emit_list.ludic
Normal file
506
selfhost/backend/stdlib/emit_list.ludic
Normal file
|
|
@ -0,0 +1,506 @@
|
|||
# emit_list.ludic — the List.* namespace over []T slices. A slice is the
|
||||
# { data, len, cap } %LSlice header (see emit_new.ludic); every op here reads or
|
||||
# mutates that header in place, so all holders of the slice observe the change.
|
||||
# Element comparison (contains/index_of) is by value for scalars and by identity
|
||||
# for reference elements (structs/strings), matching how `==` behaves elsewhere.
|
||||
|
||||
# address of element `idx` (an i32 code) in slice header `h`, element LLVM type `elt`
|
||||
function list_elem_addr(h: pointer, elt: pointer, idx: pointer) -> pointer {
|
||||
let dp = slice_field(h, 0)
|
||||
let data = emit_bind(`load ptr, ptr {dp}`)
|
||||
return emit_bind(`getelementptr inbounds {elt}, ptr {data}, i32 {idx}`)
|
||||
}
|
||||
|
||||
function is_list_ns(meth: pointer) -> bool {
|
||||
if (meth == "len") or (meth == "push") or (meth == "clear") { return true }
|
||||
if (meth == "first") or (meth == "last") or (meth == "pop") or (meth == "swap") { return true }
|
||||
if (meth == "contains") or (meth == "index_of") or (meth == "reverse") { return true }
|
||||
if (meth == "insert") or (meth == "remove_at") or (meth == "remove") or (meth == "sort") { return true }
|
||||
if (meth == "sort_by") or (meth == "sort_desc_by") or (meth == "sort_with") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
# grow the slice's backing buffer if it is full, exactly as push does (double,
|
||||
# or 8 from empty). Leaves length untouched; only capacity/data may change.
|
||||
function list_grow_if_full(h: pointer, elt: pointer) -> void {
|
||||
let lp = slice_field(h, 1); let cp = slice_field(h, 2); let dp = slice_field(h, 0)
|
||||
let l = emit_bind(`load i32, ptr {lp}`)
|
||||
let c = emit_bind(`load i32, ptr {cp}`)
|
||||
let full = emit_bind(`icmp sge i32 {l}, {c}`)
|
||||
let grow = lbl("ig"); let done = lbl("igd")
|
||||
emit(" br i1 "); emit(full); emit(", label %"); emit(grow); emit(", label %"); emit(done); emit("\n")
|
||||
emit(grow); emit(":\n")
|
||||
let dbl = emit_bind(`mul i32 {c}, 2`)
|
||||
let isz = emit_bind(`icmp eq i32 {c}, 0`)
|
||||
let nc = emit_bind(`select i1 {isz}, i32 8, i32 {dbl}`)
|
||||
let esz = emit_sizeof(elt)
|
||||
let ncw = emit_bind(`zext i32 {nc} to i64`)
|
||||
let byts = emit_bind(`mul i64 {ncw}, {esz}`)
|
||||
let old = emit_bind(`load ptr, ptr {dp}`)
|
||||
let nd = emit_bind(`call ptr @realloc(ptr {old}, i64 {byts})`)
|
||||
emit(" store ptr "); emit(nd); emit(", ptr "); emit(dp); emit("\n")
|
||||
emit(" store i32 "); emit(nc); emit(", ptr "); emit(cp); emit("\n")
|
||||
emit(" br label %"); emit(done); emit("\n")
|
||||
emit(done); emit(":\n")
|
||||
}
|
||||
|
||||
# ---- the sorting toolkit ---------------------------------------------------
|
||||
# sort_by / sort_desc_by / sort_with lower to a stable, bottom-up (iterative)
|
||||
# merge sort: O(n log n) time, O(n) scratch, and stable — equal elements keep
|
||||
# their prior order, which is what tie-breaks (leaderboards, draw order) rely
|
||||
# on. The ordering rule is supplied per call site as one of these modes; every
|
||||
# mode reduces to a single "should the right run's head come out before the
|
||||
# left's?" predicate (emit_takeright), the only place stability is decided.
|
||||
const SORT_WITH: int = 0 # comparator fn(a, b) -> int; a after b when cmp(a, b) > 0
|
||||
const SORT_KEY_ASC: int = 1 # key fn(x) -> K; ascending by key
|
||||
const SORT_KEY_DESC: int = 2 # key fn(x) -> K; descending by key
|
||||
const SORT_SCALAR: int = 3 # bare ascending over scalar elements (List.sort's large-n path)
|
||||
|
||||
# address of element `idx` (an i32 code) within a raw base pointer `base`
|
||||
function gep_at(base: pointer, elt: pointer, idx: pointer) -> pointer {
|
||||
return emit_bind(`getelementptr inbounds {elt}, ptr {base}, i32 {idx}`)
|
||||
}
|
||||
|
||||
# i1 code for "take the right run's head (`bv`) before the left's (`av`)". True
|
||||
# only on a STRICT win for the right, so equal keys keep the left (= earlier)
|
||||
# element first — that is the stability guarantee. `fnsym` is the @fn_ name of
|
||||
# the user comparator/key function; `keyll` its key's LLVM type (key modes only).
|
||||
function emit_takeright(elt: pointer, mode: int, fnsym: pointer, keyll: pointer, av: pointer, bv: pointer) -> pointer {
|
||||
if (mode == SORT_SCALAR) {
|
||||
return emit_bind(`icmp slt {elt} {bv}, {av}`) # right strictly less than left
|
||||
}
|
||||
if (mode == SORT_WITH) {
|
||||
let c = emit_bind(`call i32 @fn_{fnsym}({elt} {av}, {elt} {bv})`)
|
||||
return emit_bind(`icmp sgt i32 {c}, 0`) # cmp(left, right) > 0 -> left comes after
|
||||
}
|
||||
let kl = emit_bind(`call {keyll} @fn_{fnsym}({elt} {av})`)
|
||||
let kr = emit_bind(`call {keyll} @fn_{fnsym}({elt} {bv})`)
|
||||
if (mode == SORT_KEY_DESC) {
|
||||
return emit_bind(`icmp slt {keyll} {kl}, {kr}`) # desc: right first when its key is larger
|
||||
}
|
||||
return emit_bind(`icmp sgt {keyll} {kl}, {kr}`) # asc: right first when its key is smaller
|
||||
}
|
||||
|
||||
# emit a stable bottom-up merge sort of slice header `h` (element LLVM type
|
||||
# `elt`), ordered by `mode`. Passes double the run width each round, merging
|
||||
# adjacent runs through a malloc'd scratch buffer and copying the result back,
|
||||
# so the slice is sorted in place from the caller's view.
|
||||
function emit_merge_sort(h: pointer, elt: pointer, mode: int, fnsym: pointer, keyll: pointer) -> void {
|
||||
let dp = slice_field(h, 0)
|
||||
let data = emit_bind(`load ptr, ptr {dp}`) # data pointer is stable during the sort
|
||||
let lp = slice_field(h, 1)
|
||||
let n = emit_bind(`load i32, ptr {lp}`)
|
||||
let esz = emit_sizeof(elt)
|
||||
let nz = emit_bind(`zext i32 {n} to i64`)
|
||||
let byts = emit_bind(`mul i64 {nz}, {esz}`)
|
||||
let scratch = emit_bind(`call ptr @malloc(i64 {byts})`)
|
||||
|
||||
# outer: for (width = 1; width < n; width *= 2)
|
||||
let wp = emit_alloca("i32"); store_at("i32", "1", wp)
|
||||
let wc = lbl("ms_wc"); let wb = lbl("ms_wb"); let we = lbl("ms_we")
|
||||
emit(" br label %"); emit(wc); emit("\n"); emit(wc); emit(":\n")
|
||||
let w = emit_bind(`load i32, ptr {wp}`)
|
||||
let wgo = emit_bind(`icmp slt i32 {w}, {n}`)
|
||||
emit(" br i1 "); emit(wgo); emit(", label %"); emit(wb); emit(", label %"); emit(we); emit("\n")
|
||||
emit(wb); emit(":\n")
|
||||
let w2 = emit_bind(`mul i32 {w}, 2`)
|
||||
|
||||
# middle: for (i = 0; i < n; i += 2*width) merge [i, i+w) with [i+w, i+2w)
|
||||
let ip = emit_alloca("i32"); store_at("i32", "0", ip)
|
||||
let ic = lbl("ms_ic"); let ib = lbl("ms_ib"); let ie = lbl("ms_ie")
|
||||
emit(" br label %"); emit(ic); emit("\n"); emit(ic); emit(":\n")
|
||||
let i = emit_bind(`load i32, ptr {ip}`)
|
||||
let igo = emit_bind(`icmp slt i32 {i}, {n}`)
|
||||
emit(" br i1 "); emit(igo); emit(", label %"); emit(ib); emit(", label %"); emit(ie); emit("\n")
|
||||
emit(ib); emit(":\n")
|
||||
let iw = emit_bind(`add i32 {i}, {w}`)
|
||||
let mlt = emit_bind(`icmp slt i32 {iw}, {n}`)
|
||||
let mid = emit_bind(`select i1 {mlt}, i32 {iw}, i32 {n}`) # mid = min(i+w, n)
|
||||
let iw2 = emit_bind(`add i32 {i}, {w2}`)
|
||||
let hlt = emit_bind(`icmp slt i32 {iw2}, {n}`)
|
||||
let hi = emit_bind(`select i1 {hlt}, i32 {iw2}, i32 {n}`) # hi = min(i+2w, n)
|
||||
|
||||
let ap = emit_alloca("i32"); store_at("i32", i, ap) # a: cursor in left run [i, mid)
|
||||
let bp = emit_alloca("i32"); store_at("i32", mid, bp) # b: cursor in right run [mid, hi)
|
||||
let kp = emit_alloca("i32"); store_at("i32", i, kp) # k: write cursor in scratch
|
||||
|
||||
# merge while both runs have elements
|
||||
let mc = lbl("ms_mc"); let mbb = lbl("ms_mb"); let mend = lbl("ms_mend")
|
||||
emit(" br label %"); emit(mc); emit("\n"); emit(mc); emit(":\n")
|
||||
let a = emit_bind(`load i32, ptr {ap}`)
|
||||
let b = emit_bind(`load i32, ptr {bp}`)
|
||||
let aok = emit_bind(`icmp slt i32 {a}, {mid}`)
|
||||
let bok = emit_bind(`icmp slt i32 {b}, {hi}`)
|
||||
let both = emit_bind(`and i1 {aok}, {bok}`)
|
||||
emit(" br i1 "); emit(both); emit(", label %"); emit(mbb); emit(", label %"); emit(mend); emit("\n")
|
||||
emit(mbb); emit(":\n")
|
||||
let aad = gep_at(data, elt, a)
|
||||
let av = emit_bind(`load {elt}, ptr {aad}`)
|
||||
let bad = gep_at(data, elt, b)
|
||||
let bv = emit_bind(`load {elt}, ptr {bad}`)
|
||||
let tr = emit_takeright(elt, mode, fnsym, keyll, av, bv)
|
||||
let k = emit_bind(`load i32, ptr {kp}`)
|
||||
let sad = gep_at(scratch, elt, k)
|
||||
let tR = lbl("ms_takeR"); let tL = lbl("ms_takeL"); let tD = lbl("ms_takeD")
|
||||
emit(" br i1 "); emit(tr); emit(", label %"); emit(tR); emit(", label %"); emit(tL); emit("\n")
|
||||
emit(tR); emit(":\n")
|
||||
emit(" store "); emit(elt); emit(" "); emit(bv); emit(", ptr "); emit(sad); emit("\n")
|
||||
let b1 = emit_bind(`add i32 {b}, 1`); store_at("i32", b1, bp)
|
||||
emit(" br label %"); emit(tD); emit("\n")
|
||||
emit(tL); emit(":\n")
|
||||
emit(" store "); emit(elt); emit(" "); emit(av); emit(", ptr "); emit(sad); emit("\n")
|
||||
let a1 = emit_bind(`add i32 {a}, 1`); store_at("i32", a1, ap)
|
||||
emit(" br label %"); emit(tD); emit("\n")
|
||||
emit(tD); emit(":\n")
|
||||
let k1 = emit_bind(`add i32 {k}, 1`); store_at("i32", k1, kp)
|
||||
emit(" br label %"); emit(mc); emit("\n")
|
||||
emit(mend); emit(":\n")
|
||||
|
||||
# drain the tail of the left run
|
||||
let dac = lbl("ms_dac"); let dab = lbl("ms_dab"); let dae = lbl("ms_dae")
|
||||
emit(" br label %"); emit(dac); emit("\n"); emit(dac); emit(":\n")
|
||||
let a2 = emit_bind(`load i32, ptr {ap}`)
|
||||
let amore = emit_bind(`icmp slt i32 {a2}, {mid}`)
|
||||
emit(" br i1 "); emit(amore); emit(", label %"); emit(dab); emit(", label %"); emit(dae); emit("\n")
|
||||
emit(dab); emit(":\n")
|
||||
let aad2 = gep_at(data, elt, a2)
|
||||
let av2 = emit_bind(`load {elt}, ptr {aad2}`)
|
||||
let k2 = emit_bind(`load i32, ptr {kp}`)
|
||||
let sad2 = gep_at(scratch, elt, k2)
|
||||
emit(" store "); emit(elt); emit(" "); emit(av2); emit(", ptr "); emit(sad2); emit("\n")
|
||||
let a3 = emit_bind(`add i32 {a2}, 1`); store_at("i32", a3, ap)
|
||||
let k3 = emit_bind(`add i32 {k2}, 1`); store_at("i32", k3, kp)
|
||||
emit(" br label %"); emit(dac); emit("\n")
|
||||
emit(dae); emit(":\n")
|
||||
|
||||
# drain the tail of the right run
|
||||
let dbc = lbl("ms_dbc"); let dbb = lbl("ms_dbb"); let dbe = lbl("ms_dbe")
|
||||
emit(" br label %"); emit(dbc); emit("\n"); emit(dbc); emit(":\n")
|
||||
let b2 = emit_bind(`load i32, ptr {bp}`)
|
||||
let bmore = emit_bind(`icmp slt i32 {b2}, {hi}`)
|
||||
emit(" br i1 "); emit(bmore); emit(", label %"); emit(dbb); emit(", label %"); emit(dbe); emit("\n")
|
||||
emit(dbb); emit(":\n")
|
||||
let bad2 = gep_at(data, elt, b2)
|
||||
let bv2 = emit_bind(`load {elt}, ptr {bad2}`)
|
||||
let k4 = emit_bind(`load i32, ptr {kp}`)
|
||||
let sad3 = gep_at(scratch, elt, k4)
|
||||
emit(" store "); emit(elt); emit(" "); emit(bv2); emit(", ptr "); emit(sad3); emit("\n")
|
||||
let b3 = emit_bind(`add i32 {b2}, 1`); store_at("i32", b3, bp)
|
||||
let k5 = emit_bind(`add i32 {k4}, 1`); store_at("i32", k5, kp)
|
||||
emit(" br label %"); emit(dbc); emit("\n")
|
||||
emit(dbe); emit(":\n")
|
||||
|
||||
# copy the merged run scratch[i, hi) back into data[i, hi)
|
||||
let jp = emit_alloca("i32"); store_at("i32", i, jp)
|
||||
let cbc = lbl("ms_cbc"); let cbb = lbl("ms_cbb"); let cbe = lbl("ms_cbe")
|
||||
emit(" br label %"); emit(cbc); emit("\n"); emit(cbc); emit(":\n")
|
||||
let j = emit_bind(`load i32, ptr {jp}`)
|
||||
let jmore = emit_bind(`icmp slt i32 {j}, {hi}`)
|
||||
emit(" br i1 "); emit(jmore); emit(", label %"); emit(cbb); emit(", label %"); emit(cbe); emit("\n")
|
||||
emit(cbb); emit(":\n")
|
||||
let ssad = gep_at(scratch, elt, j)
|
||||
let sv = emit_bind(`load {elt}, ptr {ssad}`)
|
||||
let ddad = gep_at(data, elt, j)
|
||||
emit(" store "); emit(elt); emit(" "); emit(sv); emit(", ptr "); emit(ddad); emit("\n")
|
||||
let j1 = emit_bind(`add i32 {j}, 1`); store_at("i32", j1, jp)
|
||||
emit(" br label %"); emit(cbc); emit("\n")
|
||||
emit(cbe); emit(":\n")
|
||||
|
||||
let inx = emit_bind(`add i32 {i}, {w2}`); store_at("i32", inx, ip)
|
||||
emit(" br label %"); emit(ic); emit("\n")
|
||||
emit(ie); emit(":\n")
|
||||
store_at("i32", w2, wp) # width *= 2
|
||||
emit(" br label %"); emit(wc); emit("\n")
|
||||
emit(we); emit(":\n")
|
||||
emit(" call void @free(ptr "); emit(scratch); emit(")\n")
|
||||
}
|
||||
|
||||
# resolve a sort_by/sort_with function argument (a bare identifier naming a
|
||||
# top-level function) to its name, erroring clearly on misuse.
|
||||
function sort_fn_arg(meth: pointer, e: Node) -> pointer {
|
||||
if (len(e.kids) < 2) { perr(`List.{meth} needs (slice, function)`) }
|
||||
if (e.kids[1].kind != E_ID) { perr(`List.{meth}: the second argument must be a function name`) }
|
||||
let fname = e.kids[1].s
|
||||
if (find_fn(fname) == null) { perr(`List.{meth}: no function named '{fname}'`) }
|
||||
return fname
|
||||
}
|
||||
|
||||
function emit_list_ns(meth: pointer, e: Node) -> Val {
|
||||
if (meth == "len") { return emit_len(e) } # same header length as len(s)
|
||||
if (meth == "push") { return emit_push(e) } # same as push(s, v)
|
||||
let s = emit_expr(e.kids[0])
|
||||
if not is_slice_ty(s.ty) { perr(`List.{meth} needs a slice`) }
|
||||
let el = slice_elem(s.ty)
|
||||
let elt = llty(el)
|
||||
let h = s.code
|
||||
let lp = slice_field(h, 1)
|
||||
|
||||
if (meth == "clear") { # drop to length 0 (keeps capacity)
|
||||
emit(" store i32 0, ptr "); emit(lp); emit("\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if (meth == "first") { # element 0 (assumes non-empty)
|
||||
let a = list_elem_addr(h, elt, "0")
|
||||
return val(emit_bind(`load {elt}, ptr {a}`), el)
|
||||
}
|
||||
if (meth == "last") { # element len-1 (assumes non-empty)
|
||||
let l = emit_bind(`load i32, ptr {lp}`)
|
||||
let l1 = emit_bind(`sub i32 {l}, 1`)
|
||||
let a = list_elem_addr(h, elt, l1)
|
||||
return val(emit_bind(`load {elt}, ptr {a}`), el)
|
||||
}
|
||||
if (meth == "pop") { # remove & return the last element
|
||||
let l = emit_bind(`load i32, ptr {lp}`)
|
||||
let l1 = emit_bind(`sub i32 {l}, 1`)
|
||||
let a = list_elem_addr(h, elt, l1)
|
||||
let v = emit_bind(`load {elt}, ptr {a}`)
|
||||
emit(" store i32 "); emit(l1); emit(", ptr "); emit(lp); emit("\n")
|
||||
return val(v, el)
|
||||
}
|
||||
if (meth == "swap") { # exchange elements i and j
|
||||
let i = emit_expr(e.kids[1]); let j = emit_expr(e.kids[2])
|
||||
let ai = list_elem_addr(h, elt, i.code)
|
||||
let aj = list_elem_addr(h, elt, j.code)
|
||||
let vi = emit_bind(`load {elt}, ptr {ai}`)
|
||||
let vj = emit_bind(`load {elt}, ptr {aj}`)
|
||||
emit(" store "); emit(elt); emit(" "); emit(vj); emit(", ptr "); emit(ai); emit("\n")
|
||||
emit(" store "); emit(elt); emit(" "); emit(vi); emit(", ptr "); emit(aj); emit("\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if (meth == "contains") or (meth == "index_of") { # linear scan; index_of -> -1 if absent
|
||||
let needle = emit_expr(e.kids[1])
|
||||
let l = emit_bind(`load i32, ptr {lp}`)
|
||||
let res = emit_alloca("i32")
|
||||
store_at("i32", "-1", res)
|
||||
let ix = emit_alloca("i32")
|
||||
store_at("i32", "0", ix)
|
||||
let cl = lbl("lc_cond"); let bl = lbl("lc_body"); let hit = lbl("lc_hit")
|
||||
let nx = lbl("lc_next"); let en = lbl("lc_end")
|
||||
emit(" br label %"); emit(cl); emit("\n")
|
||||
emit(cl); emit(":\n")
|
||||
let iv = emit_bind(`load i32, ptr {ix}`)
|
||||
let more = emit_bind(`icmp slt i32 {iv}, {l}`)
|
||||
emit(" br i1 "); emit(more); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
|
||||
emit(bl); emit(":\n")
|
||||
let a = list_elem_addr(h, elt, iv)
|
||||
let ev = emit_bind(`load {elt}, ptr {a}`)
|
||||
let eq = emit_bind(`icmp eq {elt} {ev}, {needle.code}`)
|
||||
emit(" br i1 "); emit(eq); emit(", label %"); emit(hit); emit(", label %"); emit(nx); emit("\n")
|
||||
emit(hit); emit(":\n")
|
||||
store_at("i32", iv, res)
|
||||
emit(" br label %"); emit(en); emit("\n")
|
||||
emit(nx); emit(":\n")
|
||||
let iv1 = emit_bind(`add i32 {iv}, 1`)
|
||||
store_at("i32", iv1, ix)
|
||||
emit(" br label %"); emit(cl); emit("\n")
|
||||
emit(en); emit(":\n")
|
||||
let found = emit_bind(`load i32, ptr {res}`)
|
||||
if (meth == "index_of") { return val(found, "int") }
|
||||
let hasit = emit_bind(`icmp sge i32 {found}, 0`) # contains -> found >= 0
|
||||
return val(emit_bind(`zext i1 {hasit} to i32`), "bool")
|
||||
}
|
||||
if (meth == "insert") { # insert v at index i, shifting the rest up
|
||||
let idx = emit_expr(e.kids[1]); let vv = emit_expr(e.kids[2])
|
||||
list_grow_if_full(h, elt)
|
||||
let l = emit_bind(`load i32, ptr {lp}`)
|
||||
let jp = emit_alloca("i32")
|
||||
store_at("i32", l, jp)
|
||||
let cl = lbl("li_c"); let bl = lbl("li_b"); let en = lbl("li_e")
|
||||
emit(" br label %"); emit(cl); emit("\n"); emit(cl); emit(":\n")
|
||||
let j = emit_bind(`load i32, ptr {jp}`)
|
||||
let go = emit_bind(`icmp sgt i32 {j}, {idx.code}`)
|
||||
emit(" br i1 "); emit(go); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
|
||||
emit(bl); emit(":\n")
|
||||
let jm1 = emit_bind(`sub i32 {j}, 1`)
|
||||
let src = list_elem_addr(h, elt, jm1)
|
||||
let sv = emit_bind(`load {elt}, ptr {src}`)
|
||||
let dst = list_elem_addr(h, elt, j)
|
||||
emit(" store "); emit(elt); emit(" "); emit(sv); emit(", ptr "); emit(dst); emit("\n")
|
||||
store_at("i32", jm1, jp)
|
||||
emit(" br label %"); emit(cl); emit("\n")
|
||||
emit(en); emit(":\n")
|
||||
let at = list_elem_addr(h, elt, idx.code)
|
||||
emit(" store "); emit(elt); emit(" "); emit(vv.code); emit(", ptr "); emit(at); emit("\n")
|
||||
let l1 = emit_bind(`add i32 {l}, 1`)
|
||||
emit(" store i32 "); emit(l1); emit(", ptr "); emit(lp); emit("\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if (meth == "remove_at") { # remove index i, shifting the rest down
|
||||
let idx = emit_expr(e.kids[1])
|
||||
let l = emit_bind(`load i32, ptr {lp}`)
|
||||
let lm1 = emit_bind(`sub i32 {l}, 1`)
|
||||
let jp = emit_alloca("i32")
|
||||
store_at("i32", idx.code, jp)
|
||||
let cl = lbl("lr_c"); let bl = lbl("lr_b"); let en = lbl("lr_e")
|
||||
emit(" br label %"); emit(cl); emit("\n"); emit(cl); emit(":\n")
|
||||
let j = emit_bind(`load i32, ptr {jp}`)
|
||||
let go = emit_bind(`icmp slt i32 {j}, {lm1}`)
|
||||
emit(" br i1 "); emit(go); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
|
||||
emit(bl); emit(":\n")
|
||||
let jp1 = emit_bind(`add i32 {j}, 1`)
|
||||
let src = list_elem_addr(h, elt, jp1)
|
||||
let sv = emit_bind(`load {elt}, ptr {src}`)
|
||||
let dst = list_elem_addr(h, elt, j)
|
||||
emit(" store "); emit(elt); emit(" "); emit(sv); emit(", ptr "); emit(dst); emit("\n")
|
||||
store_at("i32", jp1, jp)
|
||||
emit(" br label %"); emit(cl); emit("\n")
|
||||
emit(en); emit(":\n")
|
||||
emit(" store i32 "); emit(lm1); emit(", ptr "); emit(lp); emit("\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if (meth == "remove") { # remove the first element equal to v
|
||||
let needle = emit_expr(e.kids[1])
|
||||
let l = emit_bind(`load i32, ptr {lp}`)
|
||||
# scan for the index of v (-1 if absent)
|
||||
let fp = emit_alloca("i32")
|
||||
store_at("i32", "-1", fp)
|
||||
let ip = emit_alloca("i32")
|
||||
store_at("i32", "0", ip)
|
||||
let sc = lbl("lv_c"); let sb = lbl("lv_b"); let sh = lbl("lv_h"); let sn = lbl("lv_n"); let se = lbl("lv_e")
|
||||
emit(" br label %"); emit(sc); emit("\n"); emit(sc); emit(":\n")
|
||||
let iv = emit_bind(`load i32, ptr {ip}`)
|
||||
let more = emit_bind(`icmp slt i32 {iv}, {l}`)
|
||||
let seen = emit_bind(`load i32, ptr {fp}`)
|
||||
let notyet = emit_bind(`icmp slt i32 {seen}, 0`)
|
||||
let cont = emit_bind(`and i1 {more}, {notyet}`)
|
||||
emit(" br i1 "); emit(cont); emit(", label %"); emit(sb); emit(", label %"); emit(se); emit("\n")
|
||||
emit(sb); emit(":\n")
|
||||
let a = list_elem_addr(h, elt, iv)
|
||||
let ev = emit_bind(`load {elt}, ptr {a}`)
|
||||
let eq = emit_bind(`icmp eq {elt} {ev}, {needle.code}`)
|
||||
emit(" br i1 "); emit(eq); emit(", label %"); emit(sh); emit(", label %"); emit(sn); emit("\n")
|
||||
emit(sh); emit(":\n"); store_at("i32", iv, fp); emit(" br label %"); emit(sc); emit("\n")
|
||||
emit(sn); emit(":\n")
|
||||
let iv1 = emit_bind(`add i32 {iv}, 1`)
|
||||
store_at("i32", iv1, ip)
|
||||
emit(" br label %"); emit(sc); emit("\n")
|
||||
emit(se); emit(":\n")
|
||||
# if found, shift the tail down and shrink
|
||||
let found = emit_bind(`load i32, ptr {fp}`)
|
||||
let has = emit_bind(`icmp sge i32 {found}, 0`)
|
||||
let doit = lbl("lv_do"); let end = lbl("lv_end")
|
||||
emit(" br i1 "); emit(has); emit(", label %"); emit(doit); emit(", label %"); emit(end); emit("\n")
|
||||
emit(doit); emit(":\n")
|
||||
let lm1 = emit_bind(`sub i32 {l}, 1`)
|
||||
let jp = emit_alloca("i32")
|
||||
store_at("i32", found, jp)
|
||||
let dc = lbl("lv_dc"); let db = lbl("lv_db"); let de = lbl("lv_de")
|
||||
emit(" br label %"); emit(dc); emit("\n"); emit(dc); emit(":\n")
|
||||
let j = emit_bind(`load i32, ptr {jp}`)
|
||||
let go = emit_bind(`icmp slt i32 {j}, {lm1}`)
|
||||
emit(" br i1 "); emit(go); emit(", label %"); emit(db); emit(", label %"); emit(de); emit("\n")
|
||||
emit(db); emit(":\n")
|
||||
let jp1 = emit_bind(`add i32 {j}, 1`)
|
||||
let src = list_elem_addr(h, elt, jp1)
|
||||
let sv = emit_bind(`load {elt}, ptr {src}`)
|
||||
let dst = list_elem_addr(h, elt, j)
|
||||
emit(" store "); emit(elt); emit(" "); emit(sv); emit(", ptr "); emit(dst); emit("\n")
|
||||
store_at("i32", jp1, jp)
|
||||
emit(" br label %"); emit(dc); emit("\n")
|
||||
emit(de); emit(":\n")
|
||||
emit(" store i32 "); emit(lm1); emit(", ptr "); emit(lp); emit("\n")
|
||||
emit(" br label %"); emit(end); emit("\n")
|
||||
emit(end); emit(":\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if (meth == "sort") { # stable ascending sort in place
|
||||
# small lists take the simple insertion sort (fast, cache-friendly, and
|
||||
# what nearly-sorted per-frame lists usually are); larger lists take the
|
||||
# O(n log n) stable merge sort. Both are stable, so the result is identical.
|
||||
let n0 = emit_bind(`load i32, ptr {lp}`)
|
||||
let small = emit_bind(`icmp slt i32 {n0}, 32`)
|
||||
let sIns = lbl("sort_ins"); let sMrg = lbl("sort_mrg"); let sEnd = lbl("sort_end")
|
||||
emit(" br i1 "); emit(small); emit(", label %"); emit(sIns); emit(", label %"); emit(sMrg); emit("\n")
|
||||
emit(sIns); emit(":\n")
|
||||
let l = emit_bind(`load i32, ptr {lp}`)
|
||||
let ip = emit_alloca("i32")
|
||||
store_at("i32", "1", ip)
|
||||
let oc = lbl("ls_oc"); let ob = lbl("ls_ob"); let oe = lbl("ls_oe")
|
||||
emit(" br label %"); emit(oc); emit("\n"); emit(oc); emit(":\n")
|
||||
let i = emit_bind(`load i32, ptr {ip}`)
|
||||
let omore = emit_bind(`icmp slt i32 {i}, {l}`)
|
||||
emit(" br i1 "); emit(omore); emit(", label %"); emit(ob); emit(", label %"); emit(oe); emit("\n")
|
||||
emit(ob); emit(":\n")
|
||||
let ai = list_elem_addr(h, elt, i)
|
||||
let key = emit_bind(`load {elt}, ptr {ai}`)
|
||||
let jp = emit_alloca("i32")
|
||||
let im1 = emit_bind(`sub i32 {i}, 1`)
|
||||
store_at("i32", im1, jp)
|
||||
let ic = lbl("ls_ic"); let ib = lbl("ls_ib"); let ie = lbl("ls_ie")
|
||||
emit(" br label %"); emit(ic); emit("\n"); emit(ic); emit(":\n")
|
||||
let j = emit_bind(`load i32, ptr {jp}`)
|
||||
let jok = emit_bind(`icmp sge i32 {j}, 0`)
|
||||
let aj = list_elem_addr(h, elt, j)
|
||||
let ev = emit_bind(`load {elt}, ptr {aj}`)
|
||||
let gt = emit_bind(`icmp sgt {elt} {ev}, {key}`)
|
||||
let shift = emit_bind(`and i1 {jok}, {gt}`)
|
||||
emit(" br i1 "); emit(shift); emit(", label %"); emit(ib); emit(", label %"); emit(ie); emit("\n")
|
||||
emit(ib); emit(":\n")
|
||||
let jp1 = emit_bind(`add i32 {j}, 1`)
|
||||
let dst = list_elem_addr(h, elt, jp1)
|
||||
emit(" store "); emit(elt); emit(" "); emit(ev); emit(", ptr "); emit(dst); emit("\n")
|
||||
let jm1 = emit_bind(`sub i32 {j}, 1`)
|
||||
store_at("i32", jm1, jp)
|
||||
emit(" br label %"); emit(ic); emit("\n")
|
||||
emit(ie); emit(":\n")
|
||||
let j2 = emit_bind(`load i32, ptr {jp}`)
|
||||
let slot = emit_bind(`add i32 {j2}, 1`)
|
||||
let sa = list_elem_addr(h, elt, slot)
|
||||
emit(" store "); emit(elt); emit(" "); emit(key); emit(", ptr "); emit(sa); emit("\n")
|
||||
let i1 = emit_bind(`add i32 {i}, 1`)
|
||||
store_at("i32", i1, ip)
|
||||
emit(" br label %"); emit(oc); emit("\n")
|
||||
emit(oe); emit(":\n")
|
||||
emit(" br label %"); emit(sEnd); emit("\n")
|
||||
emit(sMrg); emit(":\n")
|
||||
emit_merge_sort(h, elt, SORT_SCALAR, "", "")
|
||||
emit(" br label %"); emit(sEnd); emit("\n")
|
||||
emit(sEnd); emit(":\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if (meth == "sort_with") { # full comparator: fn(a, b) -> int (a<b when <0)
|
||||
let fname = sort_fn_arg(meth, e)
|
||||
if (len(param_types(find_fn(fname))) != 2) { perr(`List.sort_with: '{fname}' must take two arguments`) }
|
||||
emit_merge_sort(h, elt, SORT_WITH, fname, "i32")
|
||||
return val("0", "void")
|
||||
}
|
||||
if (meth == "sort_by") or (meth == "sort_desc_by") { # key selector: fn(x) -> number
|
||||
let fname = sort_fn_arg(meth, e)
|
||||
let kf = find_fn(fname)
|
||||
if (len(param_types(kf)) != 1) { perr(`List.{meth}: key '{fname}' must take one argument`) }
|
||||
let keyll = llty(kf.ty)
|
||||
if (keyll == "ptr") or (keyll == "void") { perr(`List.{meth}: key '{fname}' must return a number`) }
|
||||
var m = SORT_KEY_ASC
|
||||
if (meth == "sort_desc_by") { m = SORT_KEY_DESC }
|
||||
emit_merge_sort(h, elt, m, fname, keyll)
|
||||
return val("0", "void")
|
||||
}
|
||||
# reverse: swap ends inward until the cursors meet
|
||||
let l = emit_bind(`load i32, ptr {lp}`)
|
||||
let loi = emit_alloca("i32")
|
||||
store_at("i32", "0", loi)
|
||||
let hii = emit_alloca("i32")
|
||||
let lm1 = emit_bind(`sub i32 {l}, 1`)
|
||||
store_at("i32", lm1, hii)
|
||||
let cl = lbl("lr_cond"); let bl = lbl("lr_body"); let en = lbl("lr_end")
|
||||
emit(" br label %"); emit(cl); emit("\n")
|
||||
emit(cl); emit(":\n")
|
||||
let lo = emit_bind(`load i32, ptr {loi}`)
|
||||
let hi = emit_bind(`load i32, ptr {hii}`)
|
||||
let go = emit_bind(`icmp slt i32 {lo}, {hi}`)
|
||||
emit(" br i1 "); emit(go); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
|
||||
emit(bl); emit(":\n")
|
||||
let alo = list_elem_addr(h, elt, lo)
|
||||
let ahi = list_elem_addr(h, elt, hi)
|
||||
let vlo = emit_bind(`load {elt}, ptr {alo}`)
|
||||
let vhi = emit_bind(`load {elt}, ptr {ahi}`)
|
||||
emit(" store "); emit(elt); emit(" "); emit(vhi); emit(", ptr "); emit(alo); emit("\n")
|
||||
emit(" store "); emit(elt); emit(" "); emit(vlo); emit(", ptr "); emit(ahi); emit("\n")
|
||||
let lo1 = emit_bind(`add i32 {lo}, 1`)
|
||||
store_at("i32", lo1, loi)
|
||||
let hi1 = emit_bind(`sub i32 {hi}, 1`)
|
||||
store_at("i32", hi1, hii)
|
||||
emit(" br label %"); emit(cl); emit("\n")
|
||||
emit(en); emit(":\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
87
selfhost/backend/stdlib/emit_log.ludic
Normal file
87
selfhost/backend/stdlib/emit_log.ludic
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# emit_log.ludic — the Log.* namespace: levelled, structured logging, the default
|
||||
# way to answer "what is my game doing?" and "why did that break?" — better than
|
||||
# scattered `print` calls. Lines go to standard error (so they never pollute a
|
||||
# program's real stdout) with a level tag and optional structured key=value
|
||||
# fields, gated by a runtime threshold so release builds can go quiet.
|
||||
#
|
||||
# Log.trace(msg, [k, v]...) most verbose level 0
|
||||
# Log.debug(msg, [k, v]...) development detail level 1
|
||||
# Log.info(msg, [k, v]...) normal operation level 2
|
||||
# Log.warn(msg, [k, v]...) something looks wrong level 3
|
||||
# Log.error(msg, [k, v]...) a failure level 4
|
||||
# Log.set_level(n) show only level >= n (0 = all, the default)
|
||||
# Log.level() the current threshold -> int
|
||||
#
|
||||
# Structured fields are optional trailing key/value pairs appended as ` key=value`;
|
||||
# values may be strings, ints, or longs (numbers are formatted for you), so
|
||||
# `Log.warn("missing texture", "path", p, "id", n)` is cheap to write and easy to
|
||||
# grep. The level tag is chosen at compile time from the method name, so a
|
||||
# disabled level costs only the threshold comparison at runtime.
|
||||
#
|
||||
# Determinism: logging writes to stderr and never touches the simulation, so it
|
||||
# has no effect on gameplay or replays. This v1 ships the console (stderr) sink;
|
||||
# file-with-rotation and in-engine overlay sinks are planned follow-ups.
|
||||
|
||||
function is_log_ns(meth: pointer) -> bool {
|
||||
if (meth == "trace") or (meth == "debug") or (meth == "info") { return true }
|
||||
if (meth == "warn") or (meth == "error") { return true }
|
||||
if (meth == "set_level") or (meth == "level") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
# format any value as a string for a log field: a string passes through, a long
|
||||
# and an int are converted the same way the `string(...)` builtin does.
|
||||
function log_stringify(v: Val) -> pointer {
|
||||
if (llty(v.ty) == "ptr") { return v.code }
|
||||
if (llty(v.ty) == "i64") { g_uses_longstr = true; return emit_bind(`call ptr @fn_long_str(i64 {v.code})`) }
|
||||
g_uses_intstr = true
|
||||
return emit_bind(`call ptr @fn_int_str(i32 {v.code})`)
|
||||
}
|
||||
|
||||
function emit_log_ns(meth: pointer, e: Node) -> Val {
|
||||
g_uses_logrt = true
|
||||
if (meth == "set_level") { # raise/lower the threshold
|
||||
let n = emit_expr(e.kids[0])
|
||||
emit(` store i32 {n.code}, ptr @L_log_level\n`)
|
||||
return val("0", "void")
|
||||
}
|
||||
if (meth == "level") { # read the current threshold
|
||||
return val(emit_bind("load i32, ptr @L_log_level"), "int")
|
||||
}
|
||||
# a level method: tag + numeric level chosen at compile time from the name.
|
||||
var lvl = "2"; var pfx = "[INFO] "
|
||||
if (meth == "trace") { lvl = "0"; pfx = "[TRACE] " }
|
||||
if (meth == "debug") { lvl = "1"; pfx = "[DEBUG] " }
|
||||
if (meth == "warn") { lvl = "3"; pfx = "[WARN] " }
|
||||
if (meth == "error") { lvl = "4"; pfx = "[ERROR] " }
|
||||
g_uses_str = true
|
||||
# line = "[LEVEL] " + msg, then " key=value" for each trailing pair
|
||||
var line = val(emit_str_const(pfx), "string")
|
||||
let msg = emit_expr(e.kids[0])
|
||||
line = emit_str_op("+", line, val(log_stringify(msg), "string"))
|
||||
var i = 1
|
||||
while (i + 1) < len(e.kids) {
|
||||
let k = emit_expr(e.kids[i])
|
||||
let v = emit_expr(e.kids[i + 1])
|
||||
line = emit_str_op("+", line, val(emit_str_const(" "), "string"))
|
||||
line = emit_str_op("+", line, val(log_stringify(k), "string"))
|
||||
line = emit_str_op("+", line, val(emit_str_const("="), "string"))
|
||||
line = emit_str_op("+", line, val(log_stringify(v), "string"))
|
||||
i = i + 2
|
||||
}
|
||||
emit(` call void @fn_log_emit(i32 {lvl}, ptr {line.code})\n`)
|
||||
return val("0", "void")
|
||||
}
|
||||
|
||||
# emit_log_prelude — the log level register and the console sink, emitted once per
|
||||
# program that uses Log.* (g_uses_logrt). @fn_log_emit checks the threshold and,
|
||||
# if the message is at or above it, writes the line + newline to stderr.
|
||||
function emit_log_prelude() -> void {
|
||||
emith("@L_log_level = global i32 0\n")
|
||||
emith("@.log_nl = private unnamed_addr constant [2 x i8] c\"\\0A\\00\"\n")
|
||||
emith("define void @fn_log_emit(i32 %lvl, ptr %s) {\n")
|
||||
emith("entry:\n %th = load i32, ptr @L_log_level\n %skip = icmp slt i32 %lvl, %th\n br i1 %skip, label %done, label %go\n")
|
||||
emith("go:\n %e = load ptr, ptr @__stderrp\n %n = call i64 @strlen(ptr %s)\n")
|
||||
emith(" %w = call i64 @fwrite(ptr %s, i64 1, i64 %n, ptr %e)\n %w2 = call i64 @fwrite(ptr @.log_nl, i64 1, i64 1, ptr %e)\n br label %done\n")
|
||||
emith("done:\n ret void\n}\n")
|
||||
}
|
||||
405
selfhost/backend/stdlib/emit_math.ludic
Normal file
405
selfhost/backend/stdlib/emit_math.ludic
Normal file
|
|
@ -0,0 +1,405 @@
|
|||
# emit_math.ludic — the Math.* namespace, all deterministic Q16.16 fixed-point.
|
||||
# min/max/abs/clamp lower to inline IR (and stay bare too); sign/floor/ceil/
|
||||
# round/lerp/inverse_lerp/remap and the geometry/interp helpers are inline; and
|
||||
# sqrt/sin/cos/tan call the runtime prelude below (@fn_fx_sqrt is a bit-by-bit
|
||||
# integer root, @fn_fx_sin a 256-entry interpolated sine table). Everything is
|
||||
# plain integer IR, so it is bit-identical on every platform.
|
||||
|
||||
function is_math_builtin(name: pointer) -> bool {
|
||||
return (name == "min") or (name == "max") or (name == "abs") or (name == "clamp")
|
||||
}
|
||||
|
||||
function emit_math_builtin(name: pointer, e: Node) -> Val {
|
||||
if (name == "abs") {
|
||||
let a = emit_expr(e.kids[0])
|
||||
let c = emit_bind(`icmp slt i32 {a.code}, 0`)
|
||||
let n = emit_bind(`sub i32 0, {a.code}`)
|
||||
return val(emit_bind(`select i1 {c}, i32 {n}, i32 {a.code}`), a.ty)
|
||||
}
|
||||
if (name == "min") or (name == "max") {
|
||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
||||
var op = "slt"
|
||||
if (name == "max") { op = "sgt" }
|
||||
let c = emit_bind(`icmp {op} i32 {a.code}, {b.code}`)
|
||||
return val(emit_bind(`select i1 {c}, i32 {a.code}, i32 {b.code}`), a.ty)
|
||||
}
|
||||
# clamp(v, lo, hi) = max(lo, min(v, hi))
|
||||
let v = emit_expr(e.kids[0]); let lo = emit_expr(e.kids[1]); let hi = emit_expr(e.kids[2])
|
||||
let c1 = emit_bind(`icmp slt i32 {v.code}, {hi.code}`)
|
||||
let t = emit_bind(`select i1 {c1}, i32 {v.code}, i32 {hi.code}`)
|
||||
let c2 = emit_bind(`icmp sgt i32 {lo.code}, {t}`)
|
||||
return val(emit_bind(`select i1 {c2}, i32 {lo.code}, i32 {t}`), v.ty)
|
||||
}
|
||||
|
||||
# a * b in Q16.16 (64-bit intermediate, arithmetic shift back) -> code of an i32
|
||||
function fx_mul_code(a: pointer, b: pointer) -> pointer {
|
||||
let a64 = emit_bind(`sext i32 {a} to i64`)
|
||||
let b64 = emit_bind(`sext i32 {b} to i64`)
|
||||
let m = emit_bind(`mul i64 {a64}, {b64}`)
|
||||
let sh = emit_bind(`ashr i64 {m}, 16`)
|
||||
return emit_bind(`trunc i64 {sh} to i32`)
|
||||
}
|
||||
|
||||
# a / b in Q16.16 (shift the numerator up before the divide) -> code of an i32
|
||||
function fx_div_code(a: pointer, b: pointer) -> pointer {
|
||||
let a64 = emit_bind(`sext i32 {a} to i64`)
|
||||
let ash = emit_bind(`shl i64 {a64}, 16`)
|
||||
let b64 = emit_bind(`sext i32 {b} to i64`)
|
||||
let dv = emit_bind(`sdiv i64 {ash}, {b64}`)
|
||||
return emit_bind(`trunc i64 {dv} to i32`)
|
||||
}
|
||||
|
||||
# lerp(a, b, t) = a + (b - a) * t, all Q16.16 -> code of a fixed i32
|
||||
function fx_lerp_code(a: pointer, b: pointer, t: pointer) -> pointer {
|
||||
let d = emit_bind(`sub i32 {b}, {a}`)
|
||||
let dt = fx_mul_code(d, t)
|
||||
return emit_bind(`add i32 {a}, {dt}`)
|
||||
}
|
||||
|
||||
# inverse_lerp(a, b, v) = (v - a) / (b - a), all Q16.16 -> code of a fixed i32
|
||||
function fx_inv_lerp_code(a: pointer, b: pointer, v: pointer) -> pointer {
|
||||
let num = emit_bind(`sub i32 {v}, {a}`)
|
||||
let den = emit_bind(`sub i32 {b}, {a}`)
|
||||
return fx_div_code(num, den)
|
||||
}
|
||||
|
||||
# Math.* — the namespaced surface. min/max/abs/clamp reuse the bare lowering;
|
||||
# the rest are new deterministic fixed-point helpers. Returns g_intrin-style via
|
||||
# a direct Val; callers guard with is_math_ns first.
|
||||
function is_math_ns(meth: pointer) -> bool {
|
||||
if (meth == "min") or (meth == "max") or (meth == "abs") or (meth == "clamp") { return true }
|
||||
if (meth == "sign") or (meth == "floor") or (meth == "ceil") or (meth == "round") { return true }
|
||||
if (meth == "lerp") or (meth == "inverse_lerp") or (meth == "remap") { return true }
|
||||
if (meth == "sqrt") or (meth == "sin") or (meth == "cos") or (meth == "tan") or (meth == "hypot") { return true }
|
||||
if (meth == "atan2") or (meth == "asin") or (meth == "acos") { return true }
|
||||
if (meth == "deg_to_rad") or (meth == "rad_to_deg") or (meth == "posmod") or (meth == "wrap") { return true }
|
||||
if (meth == "ping_pong") or (meth == "snapped") or (meth == "move_toward") or (meth == "smoothstep") { return true }
|
||||
if (meth == "dist") or (meth == "dist2") { return true }
|
||||
if (meth == "exp") or (meth == "log") or (meth == "pow") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
function emit_math_ns(meth: pointer, e: Node) -> Val {
|
||||
if (meth == "min") or (meth == "max") or (meth == "abs") or (meth == "clamp") {
|
||||
return emit_math_builtin(meth, e)
|
||||
}
|
||||
if (meth == "sign") { # sign(x) -> -1 / 0 / 1 (int)
|
||||
let a = emit_expr(e.kids[0])
|
||||
let pos = emit_bind(`icmp sgt i32 {a.code}, 0`)
|
||||
let neg = emit_bind(`icmp slt i32 {a.code}, 0`)
|
||||
let lo = emit_bind(`select i1 {neg}, i32 -1, i32 0`)
|
||||
return val(emit_bind(`select i1 {pos}, i32 1, i32 {lo}`), "int")
|
||||
}
|
||||
if (meth == "floor") { # floor(fixed) -> int
|
||||
let a = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`ashr i32 {a.code}, 16`), "int")
|
||||
}
|
||||
if (meth == "ceil") { # ceil(fixed) -> int
|
||||
let a = emit_expr(e.kids[0])
|
||||
let t = emit_bind(`add i32 {a.code}, 65535`)
|
||||
return val(emit_bind(`ashr i32 {t}, 16`), "int")
|
||||
}
|
||||
if (meth == "round") { # round(fixed) -> nearest int (half up)
|
||||
let a = emit_expr(e.kids[0])
|
||||
let t = emit_bind(`add i32 {a.code}, 32768`)
|
||||
return val(emit_bind(`ashr i32 {t}, 16`), "int")
|
||||
}
|
||||
if (meth == "lerp") { # lerp(a, b, t: fixed) -> fixed
|
||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); let t = emit_expr(e.kids[2])
|
||||
return val(fx_lerp_code(a.code, b.code, t.code), "fixed")
|
||||
}
|
||||
if (meth == "inverse_lerp") { # inverse_lerp(a, b, v: fixed) -> fixed
|
||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); let v = emit_expr(e.kids[2])
|
||||
return val(fx_inv_lerp_code(a.code, b.code, v.code), "fixed")
|
||||
}
|
||||
if (meth == "sqrt") { # sqrt(fixed) -> fixed (deterministic isqrt)
|
||||
g_uses_mathrt = true
|
||||
let a = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call i32 @fn_fx_sqrt(i32 {a.code})`), "fixed")
|
||||
}
|
||||
if (meth == "sin") { # sin(radians: fixed) -> fixed
|
||||
g_uses_mathrt = true
|
||||
let a = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call i32 @fn_fx_sin(i32 {a.code})`), "fixed")
|
||||
}
|
||||
if (meth == "cos") { # cos(x) = sin(x + pi/2), pi/2 = 102944 fixed
|
||||
g_uses_mathrt = true
|
||||
let a = emit_expr(e.kids[0])
|
||||
let sh = emit_bind(`add i32 {a.code}, 102944`)
|
||||
return val(emit_bind(`call i32 @fn_fx_sin(i32 {sh})`), "fixed")
|
||||
}
|
||||
if (meth == "tan") { # tan(x) = sin(x) / cos(x)
|
||||
g_uses_mathrt = true
|
||||
let a = emit_expr(e.kids[0])
|
||||
let s = emit_bind(`call i32 @fn_fx_sin(i32 {a.code})`)
|
||||
let sh = emit_bind(`add i32 {a.code}, 102944`)
|
||||
let c = emit_bind(`call i32 @fn_fx_sin(i32 {sh})`)
|
||||
return val(fx_div_code(s, c), "fixed")
|
||||
}
|
||||
if (meth == "atan2") { # atan2(y, x) -> angle in radians
|
||||
g_uses_mathrt = true
|
||||
let y = emit_expr(e.kids[0]); let x = emit_expr(e.kids[1])
|
||||
return val(emit_bind(`call i32 @fn_fx_atan2(i32 {y.code}, i32 {x.code})`), "fixed")
|
||||
}
|
||||
if (meth == "asin") { # asin(x) = atan2(x, sqrt(1 - x^2))
|
||||
g_uses_mathrt = true
|
||||
let x = emit_expr(e.kids[0])
|
||||
let xx = fx_mul_code(x.code, x.code)
|
||||
let om = emit_bind(`sub i32 65536, {xx}`)
|
||||
let root = emit_bind(`call i32 @fn_fx_sqrt(i32 {om})`)
|
||||
return val(emit_bind(`call i32 @fn_fx_atan2(i32 {x.code}, i32 {root})`), "fixed")
|
||||
}
|
||||
if (meth == "acos") { # acos(x) = atan2(sqrt(1 - x^2), x)
|
||||
g_uses_mathrt = true
|
||||
let x = emit_expr(e.kids[0])
|
||||
let xx = fx_mul_code(x.code, x.code)
|
||||
let om = emit_bind(`sub i32 65536, {xx}`)
|
||||
let root = emit_bind(`call i32 @fn_fx_sqrt(i32 {om})`)
|
||||
return val(emit_bind(`call i32 @fn_fx_atan2(i32 {root}, i32 {x.code})`), "fixed")
|
||||
}
|
||||
if (meth == "hypot") { # hypot(x, y) = sqrt(x*x + y*y)
|
||||
g_uses_mathrt = true
|
||||
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1])
|
||||
let xx = fx_mul_code(x.code, x.code); let yy = fx_mul_code(y.code, y.code)
|
||||
let s = emit_bind(`add i32 {xx}, {yy}`)
|
||||
return val(emit_bind(`call i32 @fn_fx_sqrt(i32 {s})`), "fixed")
|
||||
}
|
||||
if (meth == "dist2") { # dist2(x0,y0,x1,y1) = dx*dx + dy*dy
|
||||
let x0 = emit_expr(e.kids[0]); let y0 = emit_expr(e.kids[1])
|
||||
let x1 = emit_expr(e.kids[2]); let y1 = emit_expr(e.kids[3])
|
||||
let dx = emit_bind(`sub i32 {x1.code}, {x0.code}`)
|
||||
let dy = emit_bind(`sub i32 {y1.code}, {y0.code}`)
|
||||
let xx = fx_mul_code(dx, dx); let yy = fx_mul_code(dy, dy)
|
||||
return val(emit_bind(`add i32 {xx}, {yy}`), "fixed")
|
||||
}
|
||||
if (meth == "dist") { # dist(x0,y0,x1,y1) = sqrt(dist2)
|
||||
g_uses_mathrt = true
|
||||
let x0 = emit_expr(e.kids[0]); let y0 = emit_expr(e.kids[1])
|
||||
let x1 = emit_expr(e.kids[2]); let y1 = emit_expr(e.kids[3])
|
||||
let dx = emit_bind(`sub i32 {x1.code}, {x0.code}`)
|
||||
let dy = emit_bind(`sub i32 {y1.code}, {y0.code}`)
|
||||
let xx = fx_mul_code(dx, dx); let yy = fx_mul_code(dy, dy)
|
||||
let s = emit_bind(`add i32 {xx}, {yy}`)
|
||||
return val(emit_bind(`call i32 @fn_fx_sqrt(i32 {s})`), "fixed")
|
||||
}
|
||||
if (meth == "exp") { # e^x = 2^(x * log2 e), log2 e = 94548 fixed
|
||||
g_uses_mathrt = true
|
||||
let a = emit_expr(e.kids[0])
|
||||
let t = fx_mul_code(a.code, "94548")
|
||||
return val(emit_bind(`call i32 @fn_fx_exp2(i32 {t})`), "fixed")
|
||||
}
|
||||
if (meth == "log") { # natural log: ln(x) = log2(x) * ln 2, ln 2 = 45426 fixed
|
||||
g_uses_mathrt = true
|
||||
let a = emit_expr(e.kids[0])
|
||||
let l2 = emit_bind(`call i32 @fn_fx_log2(i32 {a.code})`)
|
||||
return val(fx_mul_code(l2, "45426"), "fixed")
|
||||
}
|
||||
if (meth == "pow") { # a^b = 2^(b * log2 a); needs a > 0
|
||||
g_uses_mathrt = true
|
||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
||||
let l2 = emit_bind(`call i32 @fn_fx_log2(i32 {a.code})`)
|
||||
let t = fx_mul_code(b.code, l2)
|
||||
return val(emit_bind(`call i32 @fn_fx_exp2(i32 {t})`), "fixed")
|
||||
}
|
||||
if (meth == "deg_to_rad") { # d * (pi/180), pi/180 = 1144 fixed
|
||||
let d = emit_expr(e.kids[0])
|
||||
return val(fx_mul_code(d.code, "1144"), "fixed")
|
||||
}
|
||||
if (meth == "rad_to_deg") { # r * (180/pi), 180/pi = 3754936 fixed
|
||||
let r = emit_expr(e.kids[0])
|
||||
return val(fx_mul_code(r.code, "3754936"), "fixed")
|
||||
}
|
||||
if (meth == "posmod") { # ((a % m) + m) % m, always in [0, m)
|
||||
let a = emit_expr(e.kids[0]); let m = emit_expr(e.kids[1])
|
||||
let r = emit_bind(`srem i32 {a.code}, {m.code}`)
|
||||
let rm = emit_bind(`add i32 {r}, {m.code}`)
|
||||
return val(emit_bind(`srem i32 {rm}, {m.code}`), "int")
|
||||
}
|
||||
if (meth == "wrap") { # wrap(v, lo, hi) into [lo, hi)
|
||||
let v = emit_expr(e.kids[0]); let lo = emit_expr(e.kids[1]); let hi = emit_expr(e.kids[2])
|
||||
let range = emit_bind(`sub i32 {hi.code}, {lo.code}`)
|
||||
let off = emit_bind(`sub i32 {v.code}, {lo.code}`)
|
||||
let r = emit_bind(`srem i32 {off}, {range}`)
|
||||
let rm = emit_bind(`add i32 {r}, {range}`)
|
||||
let pm = emit_bind(`srem i32 {rm}, {range}`)
|
||||
return val(emit_bind(`add i32 {lo.code}, {pm}`), "int")
|
||||
}
|
||||
if (meth == "ping_pong") { # bounce 0..len..0, integer
|
||||
let t = emit_expr(e.kids[0]); let l = emit_expr(e.kids[1])
|
||||
let two = emit_bind(`mul i32 {l.code}, 2`)
|
||||
let r = emit_bind(`srem i32 {t.code}, {two}`)
|
||||
let rm = emit_bind(`add i32 {r}, {two}`)
|
||||
let pm = emit_bind(`srem i32 {rm}, {two}`)
|
||||
let sub = emit_bind(`sub i32 {pm}, {l.code}`)
|
||||
let neg = emit_bind(`sub i32 0, {sub}`)
|
||||
let c = emit_bind(`icmp slt i32 {sub}, 0`)
|
||||
let ab = emit_bind(`select i1 {c}, i32 {neg}, i32 {sub}`)
|
||||
return val(emit_bind(`sub i32 {l.code}, {ab}`), "int")
|
||||
}
|
||||
if (meth == "snapped") { # nearest multiple of step (fixed)
|
||||
let v = emit_expr(e.kids[0]); let step = emit_expr(e.kids[1])
|
||||
let q = fx_div_code(v.code, step.code)
|
||||
let qr = emit_bind(`add i32 {q}, 32768`)
|
||||
let n = emit_bind(`ashr i32 {qr}, 16`)
|
||||
return val(emit_bind(`mul i32 {n}, {step.code}`), "fixed")
|
||||
}
|
||||
if (meth == "move_toward") { # step from -> to by at most delta (fixed)
|
||||
let f = emit_expr(e.kids[0]); let to = emit_expr(e.kids[1]); let d = emit_expr(e.kids[2])
|
||||
let diff = emit_bind(`sub i32 {to.code}, {f.code}`)
|
||||
let dneg = emit_bind(`sub i32 0, {diff}`)
|
||||
let dc = emit_bind(`icmp slt i32 {diff}, 0`)
|
||||
let adiff = emit_bind(`select i1 {dc}, i32 {dneg}, i32 {diff}`)
|
||||
let pos = emit_bind(`icmp sgt i32 {diff}, 0`)
|
||||
let neg = emit_bind(`icmp slt i32 {diff}, 0`)
|
||||
let slo = emit_bind(`select i1 {neg}, i32 -1, i32 0`)
|
||||
let sgn = emit_bind(`select i1 {pos}, i32 1, i32 {slo}`)
|
||||
let stepv = emit_bind(`mul i32 {sgn}, {d.code}`)
|
||||
let moved = emit_bind(`add i32 {f.code}, {stepv}`)
|
||||
let reach = emit_bind(`icmp sle i32 {adiff}, {d.code}`)
|
||||
return val(emit_bind(`select i1 {reach}, i32 {to.code}, i32 {moved}`), "fixed")
|
||||
}
|
||||
if (meth == "smoothstep") { # smooth 0..1 ramp between e0 and e1
|
||||
let e0 = emit_expr(e.kids[0]); let e1 = emit_expr(e.kids[1]); let x = emit_expr(e.kids[2])
|
||||
let tt = fx_inv_lerp_code(e0.code, e1.code, x.code)
|
||||
let c1 = emit_bind(`icmp slt i32 {tt}, 0`)
|
||||
let t0 = emit_bind(`select i1 {c1}, i32 0, i32 {tt}`)
|
||||
let c2 = emit_bind(`icmp sgt i32 {t0}, 65536`)
|
||||
let t = emit_bind(`select i1 {c2}, i32 65536, i32 {t0}`)
|
||||
let twot = emit_bind(`mul i32 {t}, 2`)
|
||||
let poly = emit_bind(`sub i32 196608, {twot}`)
|
||||
let tsq = fx_mul_code(t, t)
|
||||
return val(fx_mul_code(tsq, poly), "fixed")
|
||||
}
|
||||
# remap(v, in0, in1, out0, out1) = lerp(out0, out1, inverse_lerp(in0, in1, v))
|
||||
let v = emit_expr(e.kids[0])
|
||||
let i0 = emit_expr(e.kids[1]); let i1 = emit_expr(e.kids[2])
|
||||
let o0 = emit_expr(e.kids[3]); let o1 = emit_expr(e.kids[4])
|
||||
let t = fx_inv_lerp_code(i0.code, i1.code, v.code)
|
||||
return val(fx_lerp_code(o0.code, o1.code, t), "fixed")
|
||||
}
|
||||
|
||||
# emit_math_prelude — the deterministic fixed-point math runtime, emitted once
|
||||
# per program that uses Math.sqrt/sin/cos/tan/exp/log/pow. @fn_fx_sqrt is a
|
||||
# 64-bit integer square root (bit-by-bit); @fn_fx_sin reads a 256-entry Q16.16
|
||||
# sine table with linear interpolation; @fn_fx_exp2/@fn_fx_log2 are range-reduced
|
||||
# Q16.16 polynomials (base-2 exp and log) that back exp/log/pow. All are pure
|
||||
# integer IR, so bit-identical on every platform.
|
||||
function emit_math_prelude() -> void {
|
||||
emith("@L_sin_tab = private unnamed_addr constant [256 x i32] [i32 0, i32 1608, i32 3216, i32 4821, i32 6424, i32 8022, i32 9616, i32 11204, i32 12785, i32 14359, i32 15924, i32 17479, i32 19024, i32 20557, i32 22078, i32 23586, i32 25080, i32 26558, i32 28020, i32 29466, i32 30893, i32 32303, i32 33692, i32 35062, i32 36410, i32 37736, i32 39040, i32 40320, i32 41576, i32 42806, i32 44011, i32 45190, i32 46341, i32 47464, i32 48559, i32 49624, i32 50660, i32 51665, i32 52639, i32 53581, i32 54491, i32 55368, i32 56212, i32 57022, i32 57798, i32 58538, i32 59244, i32 59914, i32 60547, i32 61145, i32 61705, i32 62228, i32 62714, i32 63162, i32 63572, i32 63944, i32 64277, i32 64571, i32 64827, i32 65043, i32 65220, i32 65358, i32 65457, i32 65516, i32 65536, i32 65516, i32 65457, i32 65358, i32 65220, i32 65043, i32 64827, i32 64571, i32 64277, i32 63944, i32 63572, i32 63162, i32 62714, i32 62228, i32 61705, i32 61145, i32 60547, i32 59914, i32 59244, i32 58538, i32 57798, i32 57022, i32 56212, i32 55368, i32 54491, i32 53581, i32 52639, i32 51665, i32 50660, i32 49624, i32 48559, i32 47464, i32 46341, i32 45190, i32 44011, i32 42806, i32 41576, i32 40320, i32 39040, i32 37736, i32 36410, i32 35062, i32 33692, i32 32303, i32 30893, i32 29466, i32 28020, i32 26558, i32 25080, i32 23586, i32 22078, i32 20557, i32 19024, i32 17479, i32 15924, i32 14359, i32 12785, i32 11204, i32 9616, i32 8022, i32 6424, i32 4821, i32 3216, i32 1608, i32 0, i32 -1608, i32 -3216, i32 -4821, i32 -6424, i32 -8022, i32 -9616, i32 -11204, i32 -12785, i32 -14359, i32 -15924, i32 -17479, i32 -19024, i32 -20557, i32 -22078, i32 -23586, i32 -25080, i32 -26558, i32 -28020, i32 -29466, i32 -30893, i32 -32303, i32 -33692, i32 -35062, i32 -36410, i32 -37736, i32 -39040, i32 -40320, i32 -41576, i32 -42806, i32 -44011, i32 -45190, i32 -46341, i32 -47464, i32 -48559, i32 -49624, i32 -50660, i32 -51665, i32 -52639, i32 -53581, i32 -54491, i32 -55368, i32 -56212, i32 -57022, i32 -57798, i32 -58538, i32 -59244, i32 -59914, i32 -60547, i32 -61145, i32 -61705, i32 -62228, i32 -62714, i32 -63162, i32 -63572, i32 -63944, i32 -64277, i32 -64571, i32 -64827, i32 -65043, i32 -65220, i32 -65358, i32 -65457, i32 -65516, i32 -65536, i32 -65516, i32 -65457, i32 -65358, i32 -65220, i32 -65043, i32 -64827, i32 -64571, i32 -64277, i32 -63944, i32 -63572, i32 -63162, i32 -62714, i32 -62228, i32 -61705, i32 -61145, i32 -60547, i32 -59914, i32 -59244, i32 -58538, i32 -57798, i32 -57022, i32 -56212, i32 -55368, i32 -54491, i32 -53581, i32 -52639, i32 -51665, i32 -50660, i32 -49624, i32 -48559, i32 -47464, i32 -46341, i32 -45190, i32 -44011, i32 -42806, i32 -41576, i32 -40320, i32 -39040, i32 -37736, i32 -36410, i32 -35062, i32 -33692, i32 -32303, i32 -30893, i32 -29466, i32 -28020, i32 -26558, i32 -25080, i32 -23586, i32 -22078, i32 -20557, i32 -19024, i32 -17479, i32 -15924, i32 -14359, i32 -12785, i32 -11204, i32 -9616, i32 -8022, i32 -6424, i32 -4821, i32 -3216, i32 -1608]\n")
|
||||
emith("define i32 @fn_fx_sqrt(i32 %x) {\n")
|
||||
emith("entry:\n %neg = icmp slt i32 %x, 0\n br i1 %neg, label %ret0, label %go\n")
|
||||
emith("ret0:\n ret i32 0\n")
|
||||
emith("go:\n %x64 = sext i32 %x to i64\n %n0 = shl i64 %x64, 16\n")
|
||||
emith(" %np = alloca i64\n %rp = alloca i64\n %bp = alloca i64\n")
|
||||
emith(" store i64 %n0, ptr %np\n store i64 0, ptr %rp\n store i64 4611686018427387904, ptr %bp\n br label %adj\n")
|
||||
emith("adj:\n %b1 = load i64, ptr %bp\n %n1 = load i64, ptr %np\n %tb = icmp ugt i64 %b1, %n1\n br i1 %tb, label %adjb, label %loop\n")
|
||||
emith("adjb:\n %b2 = lshr i64 %b1, 2\n store i64 %b2, ptr %bp\n br label %adj\n")
|
||||
emith("loop:\n %b3 = load i64, ptr %bp\n %bz = icmp eq i64 %b3, 0\n br i1 %bz, label %done, label %body\n")
|
||||
emith("body:\n %r1 = load i64, ptr %rp\n %n2 = load i64, ptr %np\n %rb = add i64 %r1, %b3\n %ge = icmp uge i64 %n2, %rb\n br i1 %ge, label %sub, label %shift\n")
|
||||
emith("sub:\n %n3 = sub i64 %n2, %rb\n store i64 %n3, ptr %np\n %rsh = lshr i64 %r1, 1\n %rnew = add i64 %rsh, %b3\n store i64 %rnew, ptr %rp\n br label %next\n")
|
||||
emith("shift:\n %rsh2 = lshr i64 %r1, 1\n store i64 %rsh2, ptr %rp\n br label %next\n")
|
||||
emith("next:\n %b4 = lshr i64 %b3, 2\n store i64 %b4, ptr %bp\n br label %loop\n")
|
||||
emith("done:\n %rf = load i64, ptr %rp\n %r32 = trunc i64 %rf to i32\n ret i32 %r32\n}\n")
|
||||
emith("define i32 @fn_fx_sin(i32 %x) {\n")
|
||||
emith(" %xe = sext i32 %x to i64\n %m = mul i64 %xe, 2670177\n %idxf = ashr i64 %m, 16\n")
|
||||
emith(" %i0 = ashr i64 %idxf, 16\n %i0m = and i64 %i0, 255\n %frac = and i64 %idxf, 65535\n")
|
||||
emith(" %i1 = add i64 %i0m, 1\n %i1m = and i64 %i1, 255\n")
|
||||
emith(" %p0 = getelementptr [256 x i32], ptr @L_sin_tab, i64 0, i64 %i0m\n %v0 = load i32, ptr %p0\n")
|
||||
emith(" %p1 = getelementptr [256 x i32], ptr @L_sin_tab, i64 0, i64 %i1m\n %v1 = load i32, ptr %p1\n")
|
||||
emith(" %d = sub i32 %v1, %v0\n %de = sext i32 %d to i64\n %dm = mul i64 %de, %frac\n %dsh = ashr i64 %dm, 16\n %dsh32 = trunc i64 %dsh to i32\n %res = add i32 %v0, %dsh32\n ret i32 %res\n}\n")
|
||||
emith("define i32 @fn_fx_atan2(i32 %y, i32 %x) {\n")
|
||||
emith("entry:\n")
|
||||
emith(" %xz = icmp eq i32 %x, 0\n")
|
||||
emith(" %yz = icmp eq i32 %y, 0\n")
|
||||
emith(" %both0 = and i1 %xz, %yz\n")
|
||||
emith(" br i1 %both0, label %z, label %go\n")
|
||||
emith("z:\n")
|
||||
emith(" ret i32 0\n")
|
||||
emith("go:\n")
|
||||
emith(" %yneg = icmp slt i32 %y, 0\n")
|
||||
emith(" %yng = sub i32 0, %y\n")
|
||||
emith(" %ay0 = select i1 %yneg, i32 %yng, i32 %y\n")
|
||||
emith(" %ay = add i32 %ay0, 1\n")
|
||||
emith(" %xpos = icmp sge i32 %x, 0\n")
|
||||
emith(" br i1 %xpos, label %xp, label %xn\n")
|
||||
emith("xp:\n")
|
||||
emith(" %n1 = sub i32 %x, %ay\n")
|
||||
emith(" %d1 = add i32 %x, %ay\n")
|
||||
emith(" br label %dv\n")
|
||||
emith("xn:\n")
|
||||
emith(" %n2 = add i32 %x, %ay\n")
|
||||
emith(" %d2 = sub i32 %ay, %x\n")
|
||||
emith(" br label %dv\n")
|
||||
emith("dv:\n")
|
||||
emith(" %num = phi i32 [ %n1, %xp ], [ %n2, %xn ]\n")
|
||||
emith(" %den = phi i32 [ %d1, %xp ], [ %d2, %xn ]\n")
|
||||
emith(" %base = phi i32 [ 51472, %xp ], [ 154416, %xn ]\n")
|
||||
emith(" %n64 = sext i32 %num to i64\n")
|
||||
emith(" %nsh = shl i64 %n64, 16\n")
|
||||
emith(" %d64 = sext i32 %den to i64\n")
|
||||
emith(" %rdv = sdiv i64 %nsh, %d64\n")
|
||||
emith(" %r = trunc i64 %rdv to i32\n")
|
||||
emith(" %r64a = sext i32 %r to i64\n")
|
||||
emith(" %r64b = sext i32 %r to i64\n")
|
||||
emith(" %rr = mul i64 %r64a, %r64b\n")
|
||||
emith(" %rrs = ashr i64 %rr, 16\n")
|
||||
emith(" %r2 = trunc i64 %rrs to i32\n")
|
||||
emith(" %r2e = sext i32 %r2 to i64\n")
|
||||
emith(" %re = sext i32 %r to i64\n")
|
||||
emith(" %r3m = mul i64 %r2e, %re\n")
|
||||
emith(" %r3s = ashr i64 %r3m, 16\n")
|
||||
emith(" %r3 = trunc i64 %r3s to i32\n")
|
||||
emith(" %r3e = sext i32 %r3 to i64\n")
|
||||
emith(" %c1 = mul i64 %r3e, 12865\n")
|
||||
emith(" %c1s = ashr i64 %c1, 16\n")
|
||||
emith(" %t1 = trunc i64 %c1s to i32\n")
|
||||
emith(" %re2 = sext i32 %r to i64\n")
|
||||
emith(" %c2 = mul i64 %re2, 64337\n")
|
||||
emith(" %c2s = ashr i64 %c2, 16\n")
|
||||
emith(" %t2 = trunc i64 %c2s to i32\n")
|
||||
emith(" %poly = sub i32 %t1, %t2\n")
|
||||
emith(" %angle = add i32 %poly, %base\n")
|
||||
emith(" %angneg = sub i32 0, %angle\n")
|
||||
emith(" %res = select i1 %yneg, i32 %angneg, i32 %angle\n")
|
||||
emith(" ret i32 %res\n")
|
||||
emith("}\n")
|
||||
# @fn_fx_exp2(x) = 2^x, Q16.16. Split x into integer part i and fraction f in
|
||||
# [0,1); 2^f is a 5th-order Taylor polynomial (Horner, coefficients (ln2)^k/k!
|
||||
# in Q16.16), then shift by i. Shift amounts are clamped to a safe [0,31] so a
|
||||
# huge exponent saturates instead of hitting an undefined shift.
|
||||
emith("define i32 @fn_fx_exp2(i32 %x) {\n")
|
||||
emith(" %i = ashr i32 %x, 16\n %f = and i32 %x, 65535\n %fe = sext i32 %f to i64\n")
|
||||
emith(" %m5 = mul i64 %fe, 87\n %s5 = ashr i64 %m5, 16\n %p5 = add i64 %s5, 630\n")
|
||||
emith(" %m4 = mul i64 %fe, %p5\n %s4 = ashr i64 %m4, 16\n %p4 = add i64 %s4, 3638\n")
|
||||
emith(" %m3 = mul i64 %fe, %p4\n %s3 = ashr i64 %m3, 16\n %p3 = add i64 %s3, 15744\n")
|
||||
emith(" %m2 = mul i64 %fe, %p3\n %s2 = ashr i64 %m2, 16\n %p2 = add i64 %s2, 45426\n")
|
||||
emith(" %m1 = mul i64 %fe, %p2\n %s1 = ashr i64 %m1, 16\n %p1 = add i64 %s1, 65536\n")
|
||||
emith(" %p = trunc i64 %p1 to i32\n")
|
||||
emith(" %ipos = icmp sge i32 %i, 0\n")
|
||||
emith(" %sa0 = select i1 %ipos, i32 %i, i32 0\n %sahi = icmp sgt i32 %sa0, 30\n %sa = select i1 %sahi, i32 30, i32 %sa0\n")
|
||||
emith(" %shl = shl i32 %p, %sa\n")
|
||||
emith(" %ni = sub i32 0, %i\n %ra0 = select i1 %ipos, i32 0, i32 %ni\n %rahi = icmp sgt i32 %ra0, 31\n %ra = select i1 %rahi, i32 31, i32 %ra0\n")
|
||||
emith(" %shr = ashr i32 %p, %ra\n")
|
||||
emith(" %res = select i1 %ipos, i32 %shl, i32 %shr\n ret i32 %res\n}\n")
|
||||
# @fn_fx_log2(x) = log2(x), Q16.16, for x > 0 (x <= 0 saturates to the most
|
||||
# negative i32). ctlz finds the MSB, giving the integer part e and a mantissa
|
||||
# m in [1,2); ln(m) uses the fast-converging atanh series on r = (m-1)/(m+1),
|
||||
# then log2(m) = ln(m)/ln2. Result is e + log2(m).
|
||||
emith("declare i32 @llvm.ctlz.i32(i32, i1)\n")
|
||||
emith("define i32 @fn_fx_log2(i32 %x) {\n")
|
||||
emith("entry:\n %pos = icmp sgt i32 %x, 0\n br i1 %pos, label %go, label %neg\n")
|
||||
emith("neg:\n ret i32 -2147483648\n")
|
||||
emith("go:\n %lz = call i32 @llvm.ctlz.i32(i32 %x, i1 true)\n %pmsb = sub i32 31, %lz\n")
|
||||
emith(" %e = sub i32 %pmsb, 16\n %ef = shl i32 %e, 16\n")
|
||||
emith(" %sh = sub i32 16, %pmsb\n %shpos = icmp sge i32 %sh, 0\n %nsh = sub i32 0, %sh\n")
|
||||
emith(" %mL = shl i32 %x, %sh\n %mR = ashr i32 %x, %nsh\n %m = select i1 %shpos, i32 %mL, i32 %mR\n")
|
||||
emith(" %num = sub i32 %m, 65536\n %den = add i32 %m, 65536\n")
|
||||
emith(" %n64 = sext i32 %num to i64\n %nshl = shl i64 %n64, 16\n %d64 = sext i32 %den to i64\n %rdv = sdiv i64 %nshl, %d64\n %r = trunc i64 %rdv to i32\n")
|
||||
emith(" %re = sext i32 %r to i64\n %rr = mul i64 %re, %re\n %r2 = ashr i64 %rr, 16\n")
|
||||
emith(" %c3 = mul i64 %r2, 9362\n %c3s = ashr i64 %c3, 16\n %q3 = add i64 %c3s, 13107\n")
|
||||
emith(" %c2 = mul i64 %r2, %q3\n %c2s = ashr i64 %c2, 16\n %q2 = add i64 %c2s, 21845\n")
|
||||
emith(" %c1 = mul i64 %r2, %q2\n %c1s = ashr i64 %c1, 16\n %q1 = add i64 %c1s, 65536\n")
|
||||
emith(" %rp = mul i64 %re, %q1\n %rps = ashr i64 %rp, 16\n %lnm = shl i64 %rps, 1\n")
|
||||
emith(" %lm = mul i64 %lnm, 94548\n %lms = ashr i64 %lm, 16\n %l2m = trunc i64 %lms to i32\n")
|
||||
emith(" %res = add i32 %ef, %l2m\n ret i32 %res\n}\n")
|
||||
}
|
||||
373
selfhost/backend/stdlib/emit_net.ludic
Normal file
373
selfhost/backend/stdlib/emit_net.ludic
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
# emit_net.ludic — NETWORKING N2–N6 codegen (NETWORKING-DESIGN.md).
|
||||
#
|
||||
# A program that calls net_send/net_poll with no `extern fn` override triggers the
|
||||
# built-in loopback transport; this flag defers its emission to end-of-module.
|
||||
var g_uses_loopback: bool = false
|
||||
|
||||
#
|
||||
# N2 (@Sync): per-model serialize/apply over the replicable-and-participating
|
||||
# fields, by-kind dispatchers a replication runtime calls, and the POD-scalar
|
||||
# compile error + empty-participation warning.
|
||||
# N3 (@Owned): the @L_owner array + owner()/set_owner()/is_owner().
|
||||
# N5 (roles): the @L_role / @L_localid registers + is_server()/local_id().
|
||||
#
|
||||
# Everything here is gated (net_has_sync / net_has_owned / len(g_events) etc.),
|
||||
# so a program that uses none of it is byte-identical to single-player (§8).
|
||||
|
||||
# ---- N2: what replicates -----------------------------------------------------
|
||||
# A field of an entity replicates iff the field is @Sync (field.ival==1) AND its
|
||||
# component participates in the entity's model (the model member is @Sync,
|
||||
# member.ival==1). Participation is decided per model use-site.
|
||||
|
||||
function net_field_ibytes(ty: pointer) -> int { if (llty(ty) == "i8") { return 1 }; return 4 }
|
||||
function net_field_bytes(ty: pointer) -> pointer { if (llty(ty) == "i8") { return "1" }; return "4" }
|
||||
|
||||
# total replicated bytes for model m (compile-time constant)
|
||||
function net_model_bytes(m: Node) -> int {
|
||||
var total = 0
|
||||
var ci = 0
|
||||
while ci < len(m.kids) {
|
||||
if m.kids[ci].ival == 1 {
|
||||
let c = find_comp(m.kids[ci].s)
|
||||
if (c != null) {
|
||||
var fj = 0
|
||||
while fj < len(c.kids) { if c.kids[fj].ival == 1 { total = total + net_field_ibytes(c.kids[fj].ty) }; fj = fj + 1 }
|
||||
}
|
||||
}
|
||||
ci = ci + 1
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
function net_model_syncs(m: Node) -> bool { return net_model_bytes(m) > 0 }
|
||||
|
||||
function net_has_sync() -> bool {
|
||||
var i = 0
|
||||
while i < len(prog) { if prog[i].kind == N_ARCH { if net_model_syncs(prog[i]) { return true } }; i = i + 1 }
|
||||
return false
|
||||
}
|
||||
|
||||
# ---- N3: ownership -----------------------------------------------------------
|
||||
function net_has_owned() -> bool {
|
||||
var i = 0
|
||||
while i < len(prog) { if prog[i].kind == N_ARCH and (prog[i].ival == 1) { return true }; i = i + 1 }
|
||||
return false
|
||||
}
|
||||
|
||||
# ---- N5: role-tagged handlers ------------------------------------------------
|
||||
# A handler tagged @Server (ival==1) or @Predicted (ival==2) has a network role.
|
||||
function net_has_role() -> bool {
|
||||
var i = 0
|
||||
while i < len(prog) { if prog[i].kind == N_SYS and (prog[i].ival != 0) { return true }; i = i + 1 }
|
||||
return false
|
||||
}
|
||||
|
||||
# Any networking feature in use → emit the shared role registers (@L_role /
|
||||
# @L_localid). A runtime sets them; offline they keep their single-player default.
|
||||
function net_any() -> bool { return net_has_sync() or net_has_owned() or net_has_role() }
|
||||
|
||||
# ---- diagnostics -------------------------------------------------------------
|
||||
function net_warn(msg: pointer) -> void {
|
||||
let e = file_stderr()
|
||||
file_write(e, "ludicc(self): warning: ", 23)
|
||||
file_write(e, msg, len(msg))
|
||||
file_write(e, "\n", 1)
|
||||
}
|
||||
|
||||
# Validate @Sync usage: a participating member whose component replicates nothing
|
||||
# is a warning (participation that replicates nothing); a @Sync ptr field is a
|
||||
# hard error (footgun 3 — networked fields must be POD scalars).
|
||||
function net_check() -> void {
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_ARCH {
|
||||
let m = prog[i]
|
||||
var ci = 0
|
||||
while ci < len(m.kids) {
|
||||
if m.kids[ci].ival == 1 {
|
||||
let cn = m.kids[ci].s
|
||||
let c = find_comp(cn)
|
||||
if (c != null) {
|
||||
var any = false
|
||||
var fj = 0
|
||||
while fj < len(c.kids) {
|
||||
if c.kids[fj].ival == 1 {
|
||||
any = true
|
||||
if (llty(c.kids[fj].ty) == "ptr") { perr(`@Sync field {cn}.{c.kids[fj].s} is not a POD scalar (networked fields must be int/bool/fixed/byte)`) }
|
||||
}
|
||||
fj = fj + 1
|
||||
}
|
||||
if not any { net_warn(`model {m.s} @Syncs {cn} but it has no @Sync fields — nothing replicates`) }
|
||||
}
|
||||
}
|
||||
ci = ci + 1
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
# ---- N2: per-model serializer / applier --------------------------------------
|
||||
# serialize_<M>(e, buf) -> bytes written. Copies each replicated field, tightly
|
||||
# packed in member-then-field order, so apply reads the identical layout.
|
||||
function emit_net_serialize(m: Node) -> void {
|
||||
ll_t = 0
|
||||
let me = itoa(MAX_ENT)
|
||||
emit("define i32 @L_serialize_"); emit(m.s); emit("(i32 %e, ptr %buf) {\nentry:\n")
|
||||
var off = 0
|
||||
var ci = 0
|
||||
while ci < len(m.kids) {
|
||||
if m.kids[ci].ival == 1 {
|
||||
let cn = m.kids[ci].s
|
||||
let c = find_comp(cn)
|
||||
if (c != null) {
|
||||
let s = nreg(); emit(" "); emit(s); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(cn); emit("], ptr @S_"); emit(cn); emit(", i32 0, i32 %e\n")
|
||||
var fj = 0
|
||||
while fj < len(c.kids) {
|
||||
if c.kids[fj].ival == 1 {
|
||||
let bytes = net_field_bytes(c.kids[fj].ty)
|
||||
let fa = nreg(); emit(" "); emit(fa); emit(" = getelementptr inbounds %Cmp_"); emit(cn); emit(", ptr "); emit(s); emit(", i32 0, i32 "); emit(itoa(fj)); emit("\n")
|
||||
let dst = nreg(); emit(" "); emit(dst); emit(" = getelementptr inbounds i8, ptr %buf, i32 "); emit(itoa(off)); emit("\n")
|
||||
emit(" call ptr @memcpy(ptr "); emit(dst); emit(", ptr "); emit(fa); emit(", i64 "); emit(bytes); emit(")\n")
|
||||
off = off + net_field_ibytes(c.kids[fj].ty)
|
||||
}
|
||||
fj = fj + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
ci = ci + 1
|
||||
}
|
||||
emit(" ret i32 "); emit(itoa(off)); emit("\n}\n\n")
|
||||
}
|
||||
|
||||
# apply_<M>(e, buf, len): the inverse — copy each replicated field back from the
|
||||
# buffer into component storage. `len` is accepted for symmetry (the runtime's
|
||||
# framing) but the layout is fixed, so it is not consulted.
|
||||
function emit_net_apply(m: Node) -> void {
|
||||
ll_t = 0
|
||||
let me = itoa(MAX_ENT)
|
||||
emit("define void @L_apply_"); emit(m.s); emit("(i32 %e, ptr %buf, i32 %len) {\nentry:\n")
|
||||
var off = 0
|
||||
var ci = 0
|
||||
while ci < len(m.kids) {
|
||||
if m.kids[ci].ival == 1 {
|
||||
let cn = m.kids[ci].s
|
||||
let c = find_comp(cn)
|
||||
if (c != null) {
|
||||
let s = nreg(); emit(" "); emit(s); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(cn); emit("], ptr @S_"); emit(cn); emit(", i32 0, i32 %e\n")
|
||||
var fj = 0
|
||||
while fj < len(c.kids) {
|
||||
if c.kids[fj].ival == 1 {
|
||||
let bytes = net_field_bytes(c.kids[fj].ty)
|
||||
let fa = nreg(); emit(" "); emit(fa); emit(" = getelementptr inbounds %Cmp_"); emit(cn); emit(", ptr "); emit(s); emit(", i32 0, i32 "); emit(itoa(fj)); emit("\n")
|
||||
let src = nreg(); emit(" "); emit(src); emit(" = getelementptr inbounds i8, ptr %buf, i32 "); emit(itoa(off)); emit("\n")
|
||||
emit(" call ptr @memcpy(ptr "); emit(fa); emit(", ptr "); emit(src); emit(", i64 "); emit(bytes); emit(")\n")
|
||||
off = off + net_field_ibytes(c.kids[fj].ty)
|
||||
}
|
||||
fj = fj + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
ci = ci + 1
|
||||
}
|
||||
emit(" ret void\n}\n\n")
|
||||
}
|
||||
|
||||
# ---- N2: by-kind dispatchers (the runtime ABI) -------------------------------
|
||||
# ludic_serialize(e, buf) -> bytes / ludic_apply(e, buf, len) / ludic_sync_size(e)
|
||||
# route on the entity's model kind to the per-model function above, so a
|
||||
# replication runtime replicates any entity without knowing its type.
|
||||
function emit_net_dispatch() -> void {
|
||||
let me = itoa(MAX_ENT)
|
||||
|
||||
emit("define i32 @ludic_serialize(i32 %e, ptr %buf) {\nentry:\n")
|
||||
emit(" %kp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %e\n %k = load i32, ptr %kp\n")
|
||||
var k = 0
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_ARCH and net_model_syncs(prog[i]) {
|
||||
let mn = prog[i].s; let sk = itoa(k)
|
||||
emit(" %c"); emit(sk); emit(" = icmp eq i32 %k, "); emit(itoa(find_arch_id(mn))); emit("\n")
|
||||
emit(" br i1 %c"); emit(sk); emit(", label %h"); emit(sk); emit(", label %n"); emit(sk); emit("\n")
|
||||
emit("h"); emit(sk); emit(":\n %r"); emit(sk); emit(" = call i32 @L_serialize_"); emit(mn); emit("(i32 %e, ptr %buf)\n ret i32 %r"); emit(sk); emit("\n")
|
||||
emit("n"); emit(sk); emit(":\n")
|
||||
k = k + 1
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
emit(" ret i32 0\n}\n\n")
|
||||
|
||||
emit("define void @ludic_apply(i32 %e, ptr %buf, i32 %len) {\nentry:\n")
|
||||
emit(" %kp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %e\n %k = load i32, ptr %kp\n")
|
||||
k = 0; i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_ARCH and net_model_syncs(prog[i]) {
|
||||
let mn = prog[i].s; let sk = itoa(k)
|
||||
emit(" %c"); emit(sk); emit(" = icmp eq i32 %k, "); emit(itoa(find_arch_id(mn))); emit("\n")
|
||||
emit(" br i1 %c"); emit(sk); emit(", label %h"); emit(sk); emit(", label %n"); emit(sk); emit("\n")
|
||||
emit("h"); emit(sk); emit(":\n call void @L_apply_"); emit(mn); emit("(i32 %e, ptr %buf, i32 %len)\n ret void\n")
|
||||
emit("n"); emit(sk); emit(":\n")
|
||||
k = k + 1
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
emit(" ret void\n}\n\n")
|
||||
|
||||
# ludic_sync_size(e): the replicated byte count for the entity's model — a
|
||||
# constant per kind, so a runtime can size a buffer before serialize.
|
||||
emit("define i32 @ludic_sync_size(i32 %e) {\nentry:\n")
|
||||
emit(" %kp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %e\n %k = load i32, ptr %kp\n")
|
||||
k = 0; i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_ARCH and net_model_syncs(prog[i]) {
|
||||
let mn = prog[i].s; let sk = itoa(k)
|
||||
emit(" %c"); emit(sk); emit(" = icmp eq i32 %k, "); emit(itoa(find_arch_id(mn))); emit("\n")
|
||||
emit(" br i1 %c"); emit(sk); emit(", label %h"); emit(sk); emit(", label %n"); emit(sk); emit("\n")
|
||||
emit("h"); emit(sk); emit(":\n ret i32 "); emit(itoa(net_model_bytes(prog[i]))); emit("\n")
|
||||
emit("n"); emit(sk); emit(":\n")
|
||||
k = k + 1
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
emit(" ret i32 0\n}\n\n")
|
||||
}
|
||||
|
||||
# ---- N3: ownership storage + accessors ---------------------------------------
|
||||
# @L_owner: one i32 owner id per entity, -1 = unowned. Only emitted when a model
|
||||
# is @Owned, and L_reset clears it to -1 on alloc/free (see emit_ecs). owner()/
|
||||
# set_owner()/is_owner() read and write it; the authority assigns.
|
||||
function emit_net_owner() -> void {
|
||||
let me = itoa(MAX_ENT)
|
||||
emit("define i32 @L_owner(i32 %e) {\nentry:\n")
|
||||
emit(" %p = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_owner_arr, i32 0, i32 %e\n %v = load i32, ptr %p\n ret i32 %v\n}\n\n")
|
||||
emit("define void @L_set_owner(i32 %e, i32 %id) {\nentry:\n")
|
||||
emit(" %p = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_owner_arr, i32 0, i32 %e\n store i32 %id, ptr %p\n ret void\n}\n\n")
|
||||
# is_owner(e): does the local peer own e? owner(e) == local_id().
|
||||
emit("define i32 @L_is_owner(i32 %e) {\nentry:\n")
|
||||
emit(" %p = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_owner_arr, i32 0, i32 %e\n %o = load i32, ptr %p\n")
|
||||
emit(" %lid = load i32, ptr @L_localid\n %eq = icmp eq i32 %o, %lid\n %r = zext i1 %eq to i32\n ret i32 %r\n}\n\n")
|
||||
}
|
||||
|
||||
# ---- built-in loopback transport (NETWORKING-DESIGN §5 N0) -------------------
|
||||
# The transport seam is net_send/net_poll. A production build binds them to a real
|
||||
# socket via `extern fn` (UDP native, WebRTC/WebSocket wasm). Absent that, the
|
||||
# compiler emits this in-process loopback — a single FIFO of datagrams, send
|
||||
# enqueues a copy and poll dequeues the oldest — so a game is networked end to end
|
||||
# with NO foreign host at all (the Ludic-native default). Datagram-preserving:
|
||||
# one message per poll, matching how replication/RPC frame. Emitted only when a
|
||||
# program actually calls net_send/net_poll without an extern override.
|
||||
function emit_loopback() -> void {
|
||||
emith("@L_netq = internal global [64 x [2048 x i8]] zeroinitializer\n")
|
||||
emith("@L_netlen = internal global [64 x i32] zeroinitializer\n")
|
||||
emith("@L_nethead = internal global i32 0\n")
|
||||
emith("@L_nettail = internal global i32 0\n")
|
||||
|
||||
emit("define void @L_net_send(i32 %peer, ptr %buf, i32 %len) {\nentry:\n")
|
||||
emit(" %l0 = icmp slt i32 %len, 0\n %len1 = select i1 %l0, i32 0, i32 %len\n")
|
||||
emit(" %l1 = icmp sgt i32 %len1, 2048\n %n = select i1 %l1, i32 2048, i32 %len1\n")
|
||||
emit(" %t = load i32, ptr @L_nettail\n %h = load i32, ptr @L_nethead\n")
|
||||
emit(" %t1 = add i32 %t, 1\n %tn = srem i32 %t1, 64\n %full = icmp eq i32 %tn, %h\n")
|
||||
emit(" br i1 %full, label %drop, label %go\n")
|
||||
emit("go:\n")
|
||||
emit(" %row = getelementptr inbounds [64 x [2048 x i8]], ptr @L_netq, i32 0, i32 %t\n")
|
||||
emit(" %nz = zext i32 %n to i64\n call ptr @memcpy(ptr %row, ptr %buf, i64 %nz)\n")
|
||||
emit(" %lp = getelementptr inbounds [64 x i32], ptr @L_netlen, i32 0, i32 %t\n store i32 %n, ptr %lp\n")
|
||||
emit(" store i32 %tn, ptr @L_nettail\n br label %drop\n")
|
||||
emit("drop:\n ret void\n}\n\n")
|
||||
|
||||
emit("define i32 @L_net_poll(ptr %buf, i32 %cap) {\nentry:\n")
|
||||
emit(" %h = load i32, ptr @L_nethead\n %t = load i32, ptr @L_nettail\n %empty = icmp eq i32 %h, %t\n")
|
||||
emit(" br i1 %empty, label %none, label %go\n")
|
||||
emit("go:\n")
|
||||
emit(" %lp = getelementptr inbounds [64 x i32], ptr @L_netlen, i32 0, i32 %h\n %ln = load i32, ptr %lp\n")
|
||||
emit(" %big = icmp sgt i32 %ln, %cap\n %n = select i1 %big, i32 %cap, i32 %ln\n")
|
||||
emit(" %row = getelementptr inbounds [64 x [2048 x i8]], ptr @L_netq, i32 0, i32 %h\n")
|
||||
emit(" %nz = zext i32 %n to i64\n call ptr @memcpy(ptr %buf, ptr %row, i64 %nz)\n")
|
||||
emit(" %h1 = add i32 %h, 1\n %hn = srem i32 %h1, 64\n store i32 %hn, ptr @L_nethead\n ret i32 %n\n")
|
||||
emit("none:\n ret i32 0\n}\n\n")
|
||||
}
|
||||
|
||||
# ---- N4: remote events (RPCs) ------------------------------------------------
|
||||
# An `event` marked @ToServer / @ToClients (ev.ty set) crosses the wire. At an
|
||||
# `emit` site the POD payload is serialized as [i32 event_id][packed fields] and
|
||||
# net_send in the declared direction; net_pump() drains inbound frames and
|
||||
# re-emits each into the ordinary @ev_<E> dispatch on the far side. Reuses the
|
||||
# EV0 payload (already flat) and the transport seam — no new concept.
|
||||
|
||||
function net_has_remote() -> bool {
|
||||
var i = 0
|
||||
while i < len(g_events) { if (g_events[i].ty != null) { return true }; i = i + 1 }
|
||||
return false
|
||||
}
|
||||
# stable wire id for an event = its index in g_events (same program both peers)
|
||||
function net_event_id(name: pointer) -> int {
|
||||
var i = 0
|
||||
while i < len(g_events) { if (g_events[i].s == name) { return i }; i = i + 1 }
|
||||
return 0 - 1
|
||||
}
|
||||
# the transport symbols: an `extern fn` override, else the built-in loopback.
|
||||
function net_send_sym() -> pointer { let x = find_extern("net_send"); if (x != null) { return x.a.s }; return "L_net_send" }
|
||||
function net_poll_sym() -> pointer { let x = find_extern("net_poll"); if (x != null) { return x.a.s }; return "L_net_poll" }
|
||||
|
||||
# net_pump(): poll every pending frame and re-emit it locally. The receive path
|
||||
# of a remote event — the runtime/game calls this each tick.
|
||||
function emit_net_pump() -> void {
|
||||
emith("@L_recvbuf = internal global [2048 x i8] zeroinitializer\n")
|
||||
if (find_extern("net_poll") == null) { g_uses_loopback = true }
|
||||
let psym = net_poll_sym()
|
||||
emit("define void @L_net_pump() {\nentry:\n br label %loop\n")
|
||||
emit("loop:\n %n = call i32 @"); emit(psym); emit("(ptr @L_recvbuf, i32 2048)\n")
|
||||
emit(" %done = icmp eq i32 %n, 0\n br i1 %done, label %fin, label %body\n")
|
||||
emit("body:\n %eid = load i32, ptr @L_recvbuf\n")
|
||||
var e = 0
|
||||
while e < len(g_events) {
|
||||
let ev = g_events[e]
|
||||
if (ev.ty != null) {
|
||||
let sk = itoa(e)
|
||||
emit(" %c"); emit(sk); emit(" = icmp eq i32 %eid, "); emit(itoa(net_event_id(ev.s))); emit("\n")
|
||||
emit(" br i1 %c"); emit(sk); emit(", label %h"); emit(sk); emit(", label %x"); emit(sk); emit("\n")
|
||||
emit("h"); emit(sk); emit(":\n")
|
||||
# decode each field from the frame (offset starts after the i32 event id)
|
||||
var off = 4
|
||||
var f = 0
|
||||
let acc = buf_new()
|
||||
while f < len(ev.kids) {
|
||||
let ft = llty(ev.kids[f].ty)
|
||||
let fk = `{sk}_{itoa(f)}`
|
||||
emit(" %fa"); emit(fk); emit(" = getelementptr inbounds i8, ptr @L_recvbuf, i32 "); emit(itoa(off)); emit("\n")
|
||||
emit(" %fv"); emit(fk); emit(" = load "); emit(ft); emit(", ptr %fa"); emit(fk); emit("\n")
|
||||
if f > 0 { buf_puts(acc, ", ") }
|
||||
buf_puts(acc, ft); buf_puts(acc, " %fv"); buf_puts(acc, fk)
|
||||
off = off + net_field_ibytes(ev.kids[f].ty)
|
||||
f = f + 1
|
||||
}
|
||||
emit(" call void @ev_"); emit(ev.s); emit("("); emit(buf_str(acc)); emit(")\n")
|
||||
emit(" br label %loop\n")
|
||||
emit("x"); emit(sk); emit(":\n")
|
||||
}
|
||||
e = e + 1
|
||||
}
|
||||
emit(" br label %loop\n") # unknown id: skip, keep draining
|
||||
emit("fin:\n ret void\n}\n\n")
|
||||
}
|
||||
|
||||
# ---- driver ------------------------------------------------------------------
|
||||
function emit_net() -> void {
|
||||
if net_has_sync() {
|
||||
net_check()
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_ARCH and net_model_syncs(prog[i]) {
|
||||
emit_net_serialize(prog[i])
|
||||
emit_net_apply(prog[i])
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
emit_net_dispatch()
|
||||
}
|
||||
if net_has_owned() { emit_net_owner() }
|
||||
if net_has_remote() {
|
||||
emith("@L_sendbuf = internal global [2048 x i8] zeroinitializer\n") # RPC send scratch
|
||||
emit_net_pump()
|
||||
}
|
||||
}
|
||||
217
selfhost/backend/stdlib/emit_noise.ludic
Normal file
217
selfhost/backend/stdlib/emit_noise.ludic
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
# emit_noise.ludic — the Noise.* namespace: deterministic, fixed-point procedural
|
||||
# noise for terrain, caves, biomes, textures, clouds, placement — the primitives
|
||||
# procedural generation is built on. Everything is Q16.16 integer IR over an
|
||||
# integer permutation hash seeded from an explicit seed, so a seed reproduces the
|
||||
# EXACT same field on every platform and every run (native, headless, and later
|
||||
# wasm) — the determinism edge over float engines whose worlds drift across CPUs.
|
||||
#
|
||||
# Noise.value2(x, y, seed) value (lattice) noise -> fixed in [-1, 1]
|
||||
# Noise.perlin2(x, y, seed) classic gradient noise -> fixed in [-1, 1]
|
||||
# Noise.simplex2(x, y, seed) organic simplex noise -> fixed in [-1, 1]
|
||||
# Noise.fbm2(x, y, seed, oct) fractal Brownian motion (octaves of simplex)
|
||||
# -> fixed in [-1, 1]
|
||||
# Noise.cellular2(x, y, seed) Worley F1 distance to the nearest cell point
|
||||
# -> fixed, ~[0, 1.5]
|
||||
# Noise.cellular2_id(x, y, seed) the id (hash) of that nearest cell -> int
|
||||
# Noise.unit(n) remap a [-1,1] sample to [0,1] -> fixed
|
||||
#
|
||||
# Coordinates are `fixed` (Q16.16); the integer part selects a lattice cell and
|
||||
# the fraction interpolates within it, so sample at a fractional `frequency` to
|
||||
# scale features. All samplers are pure functions of (x, y, seed): no global
|
||||
# state, no allocation, safe to call from worldgen or a shader-like fill.
|
||||
|
||||
function is_noise_ns(meth: pointer) -> bool {
|
||||
if (meth == "value2") or (meth == "perlin2") or (meth == "simplex2") { return true }
|
||||
if (meth == "fbm2") or (meth == "cellular2") or (meth == "cellular2_id") { return true }
|
||||
if (meth == "unit") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
function emit_noise_ns(meth: pointer, e: Node) -> Val {
|
||||
if (meth == "unit") { # [-1,1] -> [0,1]: n/2 + 0.5
|
||||
let n = emit_expr(e.kids[0])
|
||||
let h = emit_bind(`ashr i32 {n.code}, 1`)
|
||||
return val(emit_bind(`add i32 {h}, 32768`), "fixed")
|
||||
}
|
||||
g_uses_noisert = true
|
||||
if (meth == "value2") {
|
||||
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
|
||||
return val(emit_bind(`call i32 @fn_noise_value2(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "fixed")
|
||||
}
|
||||
if (meth == "perlin2") {
|
||||
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
|
||||
return val(emit_bind(`call i32 @fn_noise_perlin2(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "fixed")
|
||||
}
|
||||
if (meth == "simplex2") {
|
||||
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
|
||||
return val(emit_bind(`call i32 @fn_noise_simplex2(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "fixed")
|
||||
}
|
||||
if (meth == "fbm2") {
|
||||
g_uses_mathrt = true # simplex path is standalone; fbm needs fx_div only (local)
|
||||
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2]); let o = emit_expr(e.kids[3])
|
||||
return val(emit_bind(`call i32 @fn_noise_fbm2(i32 {x.code}, i32 {y.code}, i32 {s.code}, i32 {o.code})`), "fixed")
|
||||
}
|
||||
if (meth == "cellular2") {
|
||||
g_uses_mathrt = true # F1 distance needs @fn_fx_sqrt
|
||||
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
|
||||
return val(emit_bind(`call i32 @fn_noise_cellular2(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "fixed")
|
||||
}
|
||||
# cellular2_id: the hash id of the nearest feature cell (stable per cell -> use
|
||||
# it to pick a biome/material). Distances come from cellular2.
|
||||
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
|
||||
return val(emit_bind(`call i32 @fn_noise_cellular2_id(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "int")
|
||||
}
|
||||
|
||||
# emit_noise_prelude — the noise runtime, emitted once per program that uses
|
||||
# Noise.* (g_uses_noisert). Pure Q16.16 integer IR; cellular/fbm additionally use
|
||||
# the math prelude (@fn_fx_sqrt), pulled in by setting g_uses_mathrt at the call.
|
||||
function emit_noise_prelude() -> void {
|
||||
# Q16.16 helpers (local to noise so the prelude is self-contained for the
|
||||
# gradient/value paths). fx multiply, divide, lerp, and a [-1,1] clamp.
|
||||
emith("define i32 @fn_nfx_mul(i32 %a, i32 %b) {\n")
|
||||
emith(" %a64 = sext i32 %a to i64\n %b64 = sext i32 %b to i64\n %m = mul i64 %a64, %b64\n %s = ashr i64 %m, 16\n %r = trunc i64 %s to i32\n ret i32 %r\n}\n")
|
||||
emith("define i32 @fn_nfx_div(i32 %a, i32 %b) {\n")
|
||||
emith(" %z = icmp eq i32 %b, 0\n br i1 %z, label %zero, label %go\n")
|
||||
emith("zero:\n ret i32 0\n")
|
||||
emith("go:\n %a64 = sext i32 %a to i64\n %ash = shl i64 %a64, 16\n %b64 = sext i32 %b to i64\n %d = sdiv i64 %ash, %b64\n %r = trunc i64 %d to i32\n ret i32 %r\n}\n")
|
||||
emith("define i32 @fn_nfx_lerp(i32 %a, i32 %b, i32 %t) {\n")
|
||||
emith(" %d = sub i32 %b, %a\n %dt = call i32 @fn_nfx_mul(i32 %d, i32 %t)\n %r = add i32 %a, %dt\n ret i32 %r\n}\n")
|
||||
emith("define i32 @fn_noise_clamp(i32 %v) {\n")
|
||||
emith(" %hi = icmp sgt i32 %v, 65536\n %v1 = select i1 %hi, i32 65536, i32 %v\n %lo = icmp slt i32 %v1, -65536\n %r = select i1 %lo, i32 -65536, i32 %v1\n ret i32 %r\n}\n")
|
||||
|
||||
# integer lattice hash: mix seed + cell coords with large odd constants, then a
|
||||
# MurmurHash3-style fmix32 finalizer. Deterministic and well-distributed.
|
||||
emith("define i32 @fn_noise_hash(i32 %seed, i32 %xi, i32 %yi) {\n")
|
||||
emith(" %a = mul i32 %xi, 374761393\n %b = mul i32 %yi, -1028477387\n %c = add i32 %seed, %a\n %d0 = add i32 %c, %b\n")
|
||||
emith(" %e = lshr i32 %d0, 16\n %f = xor i32 %d0, %e\n %g = mul i32 %f, -2048144789\n")
|
||||
emith(" %h = lshr i32 %g, 13\n %i = xor i32 %g, %h\n %j = mul i32 %i, -1028477387\n")
|
||||
emith(" %k = lshr i32 %j, 16\n %l = xor i32 %j, %k\n ret i32 %l\n}\n")
|
||||
|
||||
# quintic fade 6t^5 - 15t^4 + 10t^3 (Q16.16); t in [0,1]
|
||||
emith("define i32 @fn_noise_fade(i32 %t) {\n")
|
||||
emith(" %t2 = call i32 @fn_nfx_mul(i32 %t, i32 %t)\n %t3 = call i32 @fn_nfx_mul(i32 %t2, i32 %t)\n")
|
||||
emith(" %t4 = call i32 @fn_nfx_mul(i32 %t3, i32 %t)\n %t5 = call i32 @fn_nfx_mul(i32 %t4, i32 %t)\n")
|
||||
emith(" %c6 = mul i32 %t5, 6\n %c15 = mul i32 %t4, 15\n %c10 = mul i32 %t3, 10\n")
|
||||
emith(" %s1 = sub i32 %c6, %c15\n %r = add i32 %s1, %c10\n ret i32 %r\n}\n")
|
||||
|
||||
# value noise: bilinear-interpolate the four corner random values (each mapped
|
||||
# to [-1,1]) with the faded fractional coordinates.
|
||||
emith("define i32 @fn_noise_value2(i32 %x, i32 %y, i32 %seed) {\n")
|
||||
emith(" %xi = ashr i32 %x, 16\n %yi = ashr i32 %y, 16\n %xf = and i32 %x, 65535\n %yf = and i32 %y, 65535\n")
|
||||
emith(" %u = call i32 @fn_noise_fade(i32 %xf)\n %v = call i32 @fn_noise_fade(i32 %yf)\n")
|
||||
emith(" %xi1 = add i32 %xi, 1\n %yi1 = add i32 %yi, 1\n")
|
||||
emith(" %h00 = call i32 @fn_noise_hash(i32 %seed, i32 %xi, i32 %yi)\n %m00 = and i32 %h00, 131071\n %n00 = sub i32 %m00, 65536\n")
|
||||
emith(" %h10 = call i32 @fn_noise_hash(i32 %seed, i32 %xi1, i32 %yi)\n %m10 = and i32 %h10, 131071\n %n10 = sub i32 %m10, 65536\n")
|
||||
emith(" %h01 = call i32 @fn_noise_hash(i32 %seed, i32 %xi, i32 %yi1)\n %m01 = and i32 %h01, 131071\n %n01 = sub i32 %m01, 65536\n")
|
||||
emith(" %h11 = call i32 @fn_noise_hash(i32 %seed, i32 %xi1, i32 %yi1)\n %m11 = and i32 %h11, 131071\n %n11 = sub i32 %m11, 65536\n")
|
||||
emith(" %a = call i32 @fn_nfx_lerp(i32 %n00, i32 %n10, i32 %u)\n %b = call i32 @fn_nfx_lerp(i32 %n01, i32 %n11, i32 %u)\n")
|
||||
emith(" %n = call i32 @fn_nfx_lerp(i32 %a, i32 %b, i32 %v)\n %r = call i32 @fn_noise_clamp(i32 %n)\n ret i32 %r\n}\n")
|
||||
|
||||
# 8 gradient directions (axis + diagonal, the diagonals scaled by 1/sqrt2), as
|
||||
# packed (gx, gy) Q16.16 pairs; grad2 dots the selected gradient with (dx, dy).
|
||||
emith("@noise_grad2 = private unnamed_addr constant [16 x i32] [i32 65536, i32 0, i32 -65536, i32 0, i32 0, i32 65536, i32 0, i32 -65536, i32 46341, i32 46341, i32 -46341, i32 46341, i32 46341, i32 -46341, i32 -46341, i32 -46341]\n")
|
||||
emith("define i32 @fn_noise_grad2(i32 %hash, i32 %dx, i32 %dy) {\n")
|
||||
emith(" %h = and i32 %hash, 7\n %idx = shl i32 %h, 1\n %idx64 = sext i32 %idx to i64\n")
|
||||
emith(" %gxp = getelementptr [16 x i32], ptr @noise_grad2, i64 0, i64 %idx64\n %gx = load i32, ptr %gxp\n")
|
||||
emith(" %idy = add i32 %idx, 1\n %idy64 = sext i32 %idy to i64\n %gyp = getelementptr [16 x i32], ptr @noise_grad2, i64 0, i64 %idy64\n %gy = load i32, ptr %gyp\n")
|
||||
emith(" %px = call i32 @fn_nfx_mul(i32 %gx, i32 %dx)\n %py = call i32 @fn_nfx_mul(i32 %gy, i32 %dy)\n %r = add i32 %px, %py\n ret i32 %r\n}\n")
|
||||
|
||||
# Perlin gradient noise: interpolate the four corner gradient dots, then scale
|
||||
# the ~[-0.707,0.707] result by sqrt2 into [-1,1] (and clamp for safety).
|
||||
emith("define i32 @fn_noise_perlin2(i32 %x, i32 %y, i32 %seed) {\n")
|
||||
emith(" %xi = ashr i32 %x, 16\n %yi = ashr i32 %y, 16\n %xf = and i32 %x, 65535\n %yf = and i32 %y, 65535\n")
|
||||
emith(" %xf1 = sub i32 %xf, 65536\n %yf1 = sub i32 %yf, 65536\n")
|
||||
emith(" %u = call i32 @fn_noise_fade(i32 %xf)\n %v = call i32 @fn_noise_fade(i32 %yf)\n")
|
||||
emith(" %xi1 = add i32 %xi, 1\n %yi1 = add i32 %yi, 1\n")
|
||||
emith(" %h00 = call i32 @fn_noise_hash(i32 %seed, i32 %xi, i32 %yi)\n %h10 = call i32 @fn_noise_hash(i32 %seed, i32 %xi1, i32 %yi)\n")
|
||||
emith(" %h01 = call i32 @fn_noise_hash(i32 %seed, i32 %xi, i32 %yi1)\n %h11 = call i32 @fn_noise_hash(i32 %seed, i32 %xi1, i32 %yi1)\n")
|
||||
emith(" %g00 = call i32 @fn_noise_grad2(i32 %h00, i32 %xf, i32 %yf)\n %g10 = call i32 @fn_noise_grad2(i32 %h10, i32 %xf1, i32 %yf)\n")
|
||||
emith(" %g01 = call i32 @fn_noise_grad2(i32 %h01, i32 %xf, i32 %yf1)\n %g11 = call i32 @fn_noise_grad2(i32 %h11, i32 %xf1, i32 %yf1)\n")
|
||||
emith(" %a = call i32 @fn_nfx_lerp(i32 %g00, i32 %g10, i32 %u)\n %b = call i32 @fn_nfx_lerp(i32 %g01, i32 %g11, i32 %u)\n")
|
||||
emith(" %n = call i32 @fn_nfx_lerp(i32 %a, i32 %b, i32 %v)\n %sc = call i32 @fn_nfx_mul(i32 %n, i32 92682)\n %r = call i32 @fn_noise_clamp(i32 %sc)\n ret i32 %r\n}\n")
|
||||
|
||||
# one simplex corner contribution: t = 0.5 - x^2 - y^2; if t <= 0 -> 0, else
|
||||
# t^4 * grad(hash, x, y). Kept as a helper so simplex2 reads as three corners.
|
||||
emith("define i32 @fn_noise_scorner(i32 %hash, i32 %dx, i32 %dy) {\n")
|
||||
emith(" %xx = call i32 @fn_nfx_mul(i32 %dx, i32 %dx)\n %yy = call i32 @fn_nfx_mul(i32 %dy, i32 %dy)\n")
|
||||
emith(" %s0 = sub i32 32768, %xx\n %t = sub i32 %s0, %yy\n %neg = icmp sle i32 %t, 0\n br i1 %neg, label %zero, label %go\n")
|
||||
emith("zero:\n ret i32 0\n")
|
||||
emith("go:\n %t2 = call i32 @fn_nfx_mul(i32 %t, i32 %t)\n %t4 = call i32 @fn_nfx_mul(i32 %t2, i32 %t2)\n")
|
||||
emith(" %g = call i32 @fn_noise_grad2(i32 %hash, i32 %dx, i32 %dy)\n %r = call i32 @fn_nfx_mul(i32 %t4, i32 %g)\n ret i32 %r\n}\n")
|
||||
|
||||
# 2D simplex noise (skewed triangular lattice). F2 = (sqrt3-1)/2 = 23994,
|
||||
# G2 = (3-sqrt3)/6 = 13849 in Q16.16. Sum of three corner contributions, scaled
|
||||
# into [-1,1] and clamped.
|
||||
emith("define i32 @fn_noise_simplex2(i32 %x, i32 %y, i32 %seed) {\n")
|
||||
emith(" %sum0 = add i32 %x, %y\n %skew = call i32 @fn_nfx_mul(i32 %sum0, i32 23994)\n")
|
||||
emith(" %xs = add i32 %x, %skew\n %ys = add i32 %y, %skew\n %i = ashr i32 %xs, 16\n %j = ashr i32 %ys, 16\n")
|
||||
emith(" %ij = add i32 %i, %j\n %tt = mul i32 %ij, 13849\n") # t = (i+j)*G2, fixed
|
||||
emith(" %if0 = shl i32 %i, 16\n %jf0 = shl i32 %j, 16\n")
|
||||
emith(" %X0 = sub i32 %if0, %tt\n %Y0 = sub i32 %jf0, %tt\n %x0 = sub i32 %x, %X0\n %y0 = sub i32 %y, %Y0\n")
|
||||
emith(" %gt = icmp sgt i32 %x0, %y0\n %i1 = select i1 %gt, i32 1, i32 0\n %j1 = select i1 %gt, i32 0, i32 1\n")
|
||||
emith(" %i1f = shl i32 %i1, 16\n %j1f = shl i32 %j1, 16\n")
|
||||
emith(" %x1a = sub i32 %x0, %i1f\n %x1 = add i32 %x1a, 13849\n %y1a = sub i32 %y0, %j1f\n %y1 = add i32 %y1a, 13849\n")
|
||||
emith(" %x2a = sub i32 %x0, 65536\n %x2 = add i32 %x2a, 27698\n %y2a = sub i32 %y0, 65536\n %y2 = add i32 %y2a, 27698\n")
|
||||
emith(" %i1p = add i32 %i, %i1\n %j1p = add i32 %j, %j1\n %i2 = add i32 %i, 1\n %j2 = add i32 %j, 1\n")
|
||||
emith(" %gi0 = call i32 @fn_noise_hash(i32 %seed, i32 %i, i32 %j)\n %gi1 = call i32 @fn_noise_hash(i32 %seed, i32 %i1p, i32 %j1p)\n %gi2 = call i32 @fn_noise_hash(i32 %seed, i32 %i2, i32 %j2)\n")
|
||||
emith(" %n0 = call i32 @fn_noise_scorner(i32 %gi0, i32 %x0, i32 %y0)\n %n1 = call i32 @fn_noise_scorner(i32 %gi1, i32 %x1, i32 %y1)\n %n2 = call i32 @fn_noise_scorner(i32 %gi2, i32 %x2, i32 %y2)\n")
|
||||
emith(" %sa = add i32 %n0, %n1\n %sb = add i32 %sa, %n2\n")
|
||||
emith(" %sc = mul i32 %sb, 45\n %r = call i32 @fn_noise_clamp(i32 %sc)\n ret i32 %r\n}\n")
|
||||
|
||||
# fractal Brownian motion: sum `oct` octaves of simplex at rising frequency
|
||||
# (lacunarity 2.0) and falling amplitude (gain 0.5), normalised by total
|
||||
# amplitude so the result stays in [-1,1]. seed varies per octave.
|
||||
emith("define i32 @fn_noise_fbm2(i32 %x, i32 %y, i32 %seed, i32 %oct) {\n")
|
||||
emith("entry:\n %sump = alloca i32\n %normp = alloca i32\n %ampp = alloca i32\n %freqp = alloca i32\n %op = alloca i32\n")
|
||||
emith(" store i32 0, ptr %sump\n store i32 0, ptr %normp\n store i32 65536, ptr %ampp\n store i32 65536, ptr %freqp\n store i32 0, ptr %op\n br label %cond\n")
|
||||
emith("cond:\n %o = load i32, ptr %op\n %lt = icmp slt i32 %o, %oct\n br i1 %lt, label %body, label %done\n")
|
||||
emith("body:\n %freq = load i32, ptr %freqp\n %amp = load i32, ptr %ampp\n")
|
||||
emith(" %fx = call i32 @fn_nfx_mul(i32 %x, i32 %freq)\n %fy = call i32 @fn_nfx_mul(i32 %y, i32 %freq)\n")
|
||||
emith(" %so = add i32 %seed, %o\n %n = call i32 @fn_noise_simplex2(i32 %fx, i32 %fy, i32 %so)\n")
|
||||
emith(" %na = call i32 @fn_nfx_mul(i32 %n, i32 %amp)\n %sum = load i32, ptr %sump\n %sum2 = add i32 %sum, %na\n store i32 %sum2, ptr %sump\n")
|
||||
emith(" %norm = load i32, ptr %normp\n %norm2 = add i32 %norm, %amp\n store i32 %norm2, ptr %normp\n")
|
||||
emith(" %amp2 = call i32 @fn_nfx_mul(i32 %amp, i32 32768)\n store i32 %amp2, ptr %ampp\n")
|
||||
emith(" %freq2 = call i32 @fn_nfx_mul(i32 %freq, i32 131072)\n store i32 %freq2, ptr %freqp\n")
|
||||
emith(" %o1 = add i32 %o, 1\n store i32 %o1, ptr %op\n br label %cond\n")
|
||||
emith("done:\n %fsum = load i32, ptr %sump\n %fnorm = load i32, ptr %normp\n")
|
||||
emith(" %nz = icmp eq i32 %fnorm, 0\n br i1 %nz, label %z, label %div\n")
|
||||
emith("z:\n ret i32 0\n")
|
||||
emith("div:\n %d = call i32 @fn_nfx_div(i32 %fsum, i32 %fnorm)\n %r = call i32 @fn_noise_clamp(i32 %d)\n ret i32 %r\n}\n")
|
||||
|
||||
# Worley / cellular noise: scan the 3x3 neighbourhood of cells, each holding one
|
||||
# feature point placed by its cell hash, and return the distance to (and id of)
|
||||
# the nearest feature point. cellular2 returns F1 distance; cellular2_id the id.
|
||||
emit_noise_cellular()
|
||||
}
|
||||
|
||||
# split out so no single function is oversized; shares the hash/helpers above.
|
||||
function emit_noise_cellular() -> void {
|
||||
# core scan -> writes the min squared distance to %d2out and the winning id to
|
||||
# %idout (both caller-allocated), so both public entry points share one loop.
|
||||
emith("define void @fn_noise_cell_scan(i32 %x, i32 %y, i32 %seed, ptr %d2out, ptr %idout) {\n")
|
||||
emith("entry:\n %xi = ashr i32 %x, 16\n %yi = ashr i32 %y, 16\n")
|
||||
emith(" %bestp = alloca i32\n %idp = alloca i32\n %dyp = alloca i32\n %dxp = alloca i32\n")
|
||||
emith(" store i32 2147483647, ptr %bestp\n store i32 0, ptr %idp\n store i32 -1, ptr %dyp\n br label %yc\n")
|
||||
emith("yc:\n %oy = load i32, ptr %dyp\n %yok = icmp sle i32 %oy, 1\n br i1 %yok, label %yb, label %ydone\n")
|
||||
emith("yb:\n store i32 -1, ptr %dxp\n br label %xc\n")
|
||||
emith("xc:\n %ox = load i32, ptr %dxp\n %xok = icmp sle i32 %ox, 1\n br i1 %xok, label %xb, label %xdone\n")
|
||||
emith("xb:\n %cx = add i32 %xi, %ox\n %cy = add i32 %yi, %oy\n")
|
||||
emith(" %h = call i32 @fn_noise_hash(i32 %seed, i32 %cx, i32 %cy)\n")
|
||||
emith(" %fxr = and i32 %h, 65535\n %hs = lshr i32 %h, 16\n %fyr = and i32 %hs, 65535\n")
|
||||
emith(" %cxf = shl i32 %cx, 16\n %cyf = shl i32 %cy, 16\n %pxr = add i32 %cxf, %fxr\n %pyr = add i32 %cyf, %fyr\n")
|
||||
emith(" %ddx = sub i32 %pxr, %x\n %ddy = sub i32 %pyr, %y\n")
|
||||
emith(" %dxx = call i32 @fn_nfx_mul(i32 %ddx, i32 %ddx)\n %dyy = call i32 @fn_nfx_mul(i32 %ddy, i32 %ddy)\n %d2 = add i32 %dxx, %dyy\n")
|
||||
emith(" %best = load i32, ptr %bestp\n %less = icmp slt i32 %d2, %best\n br i1 %less, label %upd, label %skip\n")
|
||||
emith("upd:\n store i32 %d2, ptr %bestp\n store i32 %h, ptr %idp\n br label %skip\n")
|
||||
emith("skip:\n %ox1 = add i32 %ox, 1\n store i32 %ox1, ptr %dxp\n br label %xc\n")
|
||||
emith("xdone:\n %oy1 = add i32 %oy, 1\n store i32 %oy1, ptr %dyp\n br label %yc\n")
|
||||
emith("ydone:\n %fb = load i32, ptr %bestp\n store i32 %fb, ptr %d2out\n %fi = load i32, ptr %idp\n store i32 %fi, ptr %idout\n ret void\n}\n")
|
||||
|
||||
emith("define i32 @fn_noise_cellular2(i32 %x, i32 %y, i32 %seed) {\n")
|
||||
emith(" %d2 = alloca i32\n %id = alloca i32\n call void @fn_noise_cell_scan(i32 %x, i32 %y, i32 %seed, ptr %d2, ptr %id)\n")
|
||||
emith(" %v = load i32, ptr %d2\n %r = call i32 @fn_fx_sqrt(i32 %v)\n ret i32 %r\n}\n")
|
||||
|
||||
emith("define i32 @fn_noise_cellular2_id(i32 %x, i32 %y, i32 %seed) {\n")
|
||||
emith(" %d2 = alloca i32\n %id = alloca i32\n call void @fn_noise_cell_scan(i32 %x, i32 %y, i32 %seed, ptr %d2, ptr %id)\n")
|
||||
emith(" %r = load i32, ptr %id\n ret i32 %r\n}\n")
|
||||
}
|
||||
188
selfhost/backend/stdlib/emit_os.ludic
Normal file
188
selfhost/backend/stdlib/emit_os.ludic
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
# emit_os.ludic — the Os.* namespace: the environment *around* the game — the
|
||||
# command line, environment variables, standard streams, process exit, the host
|
||||
# platform, and the per-user known folders a game writes into. Go-flavored and
|
||||
# game-scoped: no process spawning, signals, or permission APIs — just the facts
|
||||
# a launcher, an asset pipeline, or a save system needs.
|
||||
#
|
||||
# Os.args() -> []string every command-line argument (argv[0..])
|
||||
# Os.arg_count() -> int how many arguments there are
|
||||
# Os.arg(i) -> string the i-th argument (0 = the program path)
|
||||
# Os.env(name) -> string an environment variable, or null if unset
|
||||
# Os.env_or(name, fb) -> string ...or `fb` when it is unset/empty-null
|
||||
# Os.has_env(name) -> bool is the variable set?
|
||||
# Os.set_env(name, val) -> bool set it (true on success)
|
||||
# Os.unset_env(name) -> bool remove it (true on success)
|
||||
# Os.exit(code) terminate the process with a status code
|
||||
# Os.platform() -> string "macos" | "linux" | ...(raw uname sysname)
|
||||
# Os.arch() -> string machine arch, e.g. "arm64" | "x86_64"
|
||||
# Os.stdout_write(s) write a string to standard output
|
||||
# Os.stderr_write(s) write a string to standard error
|
||||
# Os.save_dir(app) -> string per-user save directory for `app`
|
||||
# Os.config_dir(app) -> string per-user config directory for `app`
|
||||
# Os.cache_dir(app) -> string per-user cache directory for `app`
|
||||
# Os.temp_dir() -> string the system temporary directory
|
||||
#
|
||||
# Determinism: args/env/platform are non-deterministic host input — read them at
|
||||
# startup to configure the game, but keep them out of the replayable simulation.
|
||||
#
|
||||
# Platform coverage: this v1 targets the native (macOS/BSD) host, the only fully
|
||||
# supported target today. platform() is portable (uname sysname is field 0 on
|
||||
# every Unix); arch() and the known-folder layout assume the macOS/BSD utsname
|
||||
# and directory conventions. Linux/Windows/wasm known-folder resolution and a
|
||||
# target-aware arch() are documented follow-ups (see issue #21).
|
||||
|
||||
function is_os_ns(meth: pointer) -> bool {
|
||||
if (meth == "args") or (meth == "arg_count") or (meth == "arg") { return true }
|
||||
if (meth == "env") or (meth == "env_or") or (meth == "has_env") { return true }
|
||||
if (meth == "set_env") or (meth == "unset_env") { return true }
|
||||
if (meth == "exit") or (meth == "platform") or (meth == "arch") { return true }
|
||||
if (meth == "stdout_write") or (meth == "stderr_write") { return true }
|
||||
if (meth == "save_dir") or (meth == "config_dir") or (meth == "cache_dir") or (meth == "temp_dir") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
function emit_os_ns(meth: pointer, e: Node) -> Val {
|
||||
# arg_count / arg / exit stay light — they mirror the bare intrinsics and need
|
||||
# no Os runtime prelude, so a program using only these emits no extra IR.
|
||||
if (meth == "arg_count") { return val(emit_bind("load i32, ptr @L_argc"), "int") }
|
||||
if (meth == "arg") {
|
||||
let i = emit_expr(e.kids[0])
|
||||
let v = emit_bind("load ptr, ptr @L_argv")
|
||||
let q = emit_bind(`getelementptr ptr, ptr {v}, i32 {i.code}`)
|
||||
return val(emit_bind(`load ptr, ptr {q}`), "string")
|
||||
}
|
||||
if (meth == "exit") {
|
||||
let n = emit_expr(e.kids[0])
|
||||
emit(` call void @exit(i32 {n.code})\n`)
|
||||
emit(" unreachable\n")
|
||||
g_term = true
|
||||
return val("0", "void")
|
||||
}
|
||||
if (meth == "env") { # raw getenv: null when unset
|
||||
let nm = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call ptr @getenv(ptr {nm.code})`), "string")
|
||||
}
|
||||
if (meth == "has_env") {
|
||||
let nm = emit_expr(e.kids[0])
|
||||
let r = emit_bind(`call ptr @getenv(ptr {nm.code})`)
|
||||
let ne = emit_bind(`icmp ne ptr {r}, null`)
|
||||
return val(emit_bind(`zext i1 {ne} to i32`), "bool")
|
||||
}
|
||||
if (meth == "stdout_write") or (meth == "stderr_write") {
|
||||
var strm = "@__stdoutp"
|
||||
if (meth == "stderr_write") { strm = "@__stderrp" }
|
||||
let s = emit_expr(e.kids[0])
|
||||
let f = emit_bind(`load ptr, ptr {strm}`)
|
||||
let n = emit_bind(`call i64 @strlen(ptr {s.code})`)
|
||||
emit(` call i64 @fwrite(ptr {s.code}, i64 1, i64 {n}, ptr {f})\n`)
|
||||
return val("0", "void")
|
||||
}
|
||||
# everything below is served by the Os runtime prelude
|
||||
g_uses_osrt = true
|
||||
if (meth == "args") { return val(emit_bind("call ptr @fn_os_args()"), "[]string") }
|
||||
if (meth == "env_or") {
|
||||
let nm = emit_expr(e.kids[0]); let fb = emit_expr(e.kids[1])
|
||||
return val(emit_bind(`call ptr @fn_os_getenv_or(ptr {nm.code}, ptr {fb.code})`), "string")
|
||||
}
|
||||
if (meth == "set_env") {
|
||||
let nm = emit_expr(e.kids[0]); let v = emit_expr(e.kids[1])
|
||||
let r = emit_bind(`call i32 @setenv(ptr {nm.code}, ptr {v.code}, i32 1)`)
|
||||
let ok = emit_bind(`icmp eq i32 {r}, 0`)
|
||||
return val(emit_bind(`zext i1 {ok} to i32`), "bool")
|
||||
}
|
||||
if (meth == "unset_env") {
|
||||
let nm = emit_expr(e.kids[0])
|
||||
let r = emit_bind(`call i32 @unsetenv(ptr {nm.code})`)
|
||||
let ok = emit_bind(`icmp eq i32 {r}, 0`)
|
||||
return val(emit_bind(`zext i1 {ok} to i32`), "bool")
|
||||
}
|
||||
if (meth == "platform") { return val(emit_bind("call ptr @fn_os_platform()"), "string") }
|
||||
if (meth == "arch") { return val(emit_bind("call ptr @fn_os_arch()"), "string") }
|
||||
if (meth == "save_dir") {
|
||||
let a = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call ptr @fn_os_save_dir(ptr {a.code})`), "string")
|
||||
}
|
||||
if (meth == "config_dir") {
|
||||
let a = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call ptr @fn_os_config_dir(ptr {a.code})`), "string")
|
||||
}
|
||||
if (meth == "cache_dir") {
|
||||
let a = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call ptr @fn_os_cache_dir(ptr {a.code})`), "string")
|
||||
}
|
||||
# temp_dir
|
||||
return val(emit_bind("call ptr @fn_os_temp_dir()"), "string")
|
||||
}
|
||||
|
||||
# emit_os_prelude — the Os runtime, emitted once per program that uses the
|
||||
# prelude-backed Os.* methods (g_uses_osrt). Pure libc over NUL-terminated
|
||||
# strings; declares the few POSIX symbols the header does not already carry.
|
||||
function emit_os_prelude() -> void {
|
||||
emith("declare i32 @setenv(ptr, ptr, i32)\n")
|
||||
emith("declare i32 @unsetenv(ptr)\n")
|
||||
emith("declare i32 @uname(ptr)\n")
|
||||
|
||||
# string constants (escaped + length-counted by emit_str_const)
|
||||
let k_home = emit_str_const("HOME")
|
||||
let k_dot = emit_str_const(".")
|
||||
let k_appsp = emit_str_const("/Library/Application Support/")
|
||||
let k_cache = emit_str_const("/Library/Caches/")
|
||||
let k_tmpk = emit_str_const("TMPDIR")
|
||||
let k_tmp = emit_str_const("/tmp")
|
||||
let k_darw = emit_str_const("Darwin")
|
||||
let k_macos = emit_str_const("macos")
|
||||
let k_linux_k = emit_str_const("Linux")
|
||||
let k_linux = emit_str_const("linux")
|
||||
|
||||
# getenv(name) or a fallback when it is unset
|
||||
emith("define ptr @fn_os_getenv_or(ptr %name, ptr %fb) {\n")
|
||||
emith("entry:\n %r = call ptr @getenv(ptr %name)\n %z = icmp eq ptr %r, null\n br i1 %z, label %use, label %got\n")
|
||||
emith("use:\n ret ptr %fb\n")
|
||||
emith("got:\n ret ptr %r\n}\n")
|
||||
|
||||
# concatenate two NUL-terminated strings into a fresh malloc'd buffer
|
||||
emith("define ptr @fn_os_join2(ptr %a, ptr %b) {\n")
|
||||
emith("entry:\n %la = call i64 @strlen(ptr %a)\n %lb = call i64 @strlen(ptr %b)\n")
|
||||
emith(" %sum = add i64 %la, %lb\n %tot = add i64 %sum, 1\n %m = call ptr @malloc(i64 %tot)\n")
|
||||
emith(" call ptr @memcpy(ptr %m, ptr %a, i64 %la)\n")
|
||||
emith(" %m2 = getelementptr i8, ptr %m, i64 %la\n call ptr @memcpy(ptr %m2, ptr %b, i64 %lb)\n")
|
||||
emith(" %end = getelementptr i8, ptr %m, i64 %sum\n store i8 0, ptr %end\n ret ptr %m\n}\n")
|
||||
|
||||
emith("define ptr @fn_os_join3(ptr %a, ptr %b, ptr %c) {\n")
|
||||
emith("entry:\n %ab = call ptr @fn_os_join2(ptr %a, ptr %b)\n %r = call ptr @fn_os_join2(ptr %ab, ptr %c)\n ret ptr %r\n}\n")
|
||||
|
||||
# the user's home directory, or "." when HOME is unset
|
||||
emith(`define ptr @fn_os_home() {{\n %r = call ptr @fn_os_getenv_or(ptr {k_home}, ptr {k_dot})\n ret ptr %r\n}}\n`)
|
||||
|
||||
# per-user known folders (macOS/BSD layout)
|
||||
emith(`define ptr @fn_os_save_dir(ptr %app) {{\n %h = call ptr @fn_os_home()\n %r = call ptr @fn_os_join3(ptr %h, ptr {k_appsp}, ptr %app)\n ret ptr %r\n}}\n`)
|
||||
emith(`define ptr @fn_os_config_dir(ptr %app) {{\n %h = call ptr @fn_os_home()\n %r = call ptr @fn_os_join3(ptr %h, ptr {k_appsp}, ptr %app)\n ret ptr %r\n}}\n`)
|
||||
emith(`define ptr @fn_os_cache_dir(ptr %app) {{\n %h = call ptr @fn_os_home()\n %r = call ptr @fn_os_join3(ptr %h, ptr {k_cache}, ptr %app)\n ret ptr %r\n}}\n`)
|
||||
emith(`define ptr @fn_os_temp_dir() {{\n %r = call ptr @fn_os_getenv_or(ptr {k_tmpk}, ptr {k_tmp})\n ret ptr %r\n}}\n`)
|
||||
|
||||
# Os.args() -> a %LSlice of the argv strings (data = argv, len = cap = argc), a
|
||||
# snapshot the caller may iterate or index like any other []string.
|
||||
emith("define ptr @fn_os_args() {\n")
|
||||
emith("entry:\n %c = load i32, ptr @L_argc\n %v = load ptr, ptr @L_argv\n")
|
||||
emith(" %h = call ptr @malloc(i64 16)\n")
|
||||
emith(" %d0 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 0\n store ptr %v, ptr %d0\n")
|
||||
emith(" %d1 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 1\n store i32 %c, ptr %d1\n")
|
||||
emith(" %d2 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 2\n store i32 %c, ptr %d2\n")
|
||||
emith(" ret ptr %h\n}\n")
|
||||
|
||||
# platform(): uname sysname (field 0, portable) mapped to a short id
|
||||
emith("define ptr @fn_os_platform() {\n")
|
||||
emith("entry:\n %buf = call ptr @malloc(i64 8192)\n call i32 @uname(ptr %buf)\n")
|
||||
emith(` %cd = call i32 @strncmp(ptr %buf, ptr {k_darw}, i64 6)\n %isd = icmp eq i32 %cd, 0\n br i1 %isd, label %mac, label %chkl\n`)
|
||||
emith(`mac:\n ret ptr {k_macos}\n`)
|
||||
emith(`chkl:\n %cl = call i32 @strncmp(ptr %buf, ptr {k_linux_k}, i64 5)\n %isl = icmp eq i32 %cl, 0\n br i1 %isl, label %lin, label %other\n`)
|
||||
emith(`lin:\n ret ptr {k_linux}\n`)
|
||||
emith("other:\n ret ptr %buf\n}\n")
|
||||
|
||||
# arch(): the uname `machine` field. On macOS/BSD utsname each field is 256
|
||||
# bytes, so `machine` (index 4) sits at offset 1024. Documented BSD-layout
|
||||
# assumption (see the header note); other layouts are a follow-up.
|
||||
emith("define ptr @fn_os_arch() {\n")
|
||||
emith("entry:\n %buf = call ptr @malloc(i64 8192)\n call i32 @uname(ptr %buf)\n")
|
||||
emith(" %m = getelementptr i8, ptr %buf, i64 1024\n ret ptr %m\n}\n")
|
||||
}
|
||||
137
selfhost/backend/stdlib/emit_text.ludic
Normal file
137
selfhost/backend/stdlib/emit_text.ludic
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
# emit_text.ludic — the Text.* namespace over `str` (null-terminated byte
|
||||
# strings). The libc-backed queries (length/char_at/starts_with/ends_with/
|
||||
# contains/index_of/to_int) allocate nothing; slice/from_int/equals/concat reuse
|
||||
# the string preludes that the `+`, `s[a..b]` and string(int) operators emit.
|
||||
|
||||
function is_text_ns(meth: pointer) -> bool {
|
||||
if (meth == "length") or (meth == "char_at") or (meth == "slice") { return true }
|
||||
if (meth == "equals") or (meth == "concat") or (meth == "to_int") or (meth == "from_int") { return true }
|
||||
if (meth == "starts_with") or (meth == "ends_with") { return true }
|
||||
if (meth == "contains") or (meth == "index_of") { return true }
|
||||
if (meth == "upper") or (meth == "lower") or (meth == "trim") or (meth == "repeat") { return true }
|
||||
if (meth == "pad_left") or (meth == "pad_right") { return true }
|
||||
if (meth == "split") or (meth == "join") or (meth == "replace") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
function emit_text_ns(meth: pointer, e: Node) -> Val {
|
||||
if (meth == "from_int") { # int -> string, same as string(n)
|
||||
let n = emit_expr(e.kids[0])
|
||||
g_uses_intstr = true
|
||||
return val(emit_bind(`call ptr @fn_int_str(i32 {n.code})`), "string")
|
||||
}
|
||||
if (meth == "slice") { # s[a..b], same substring helper
|
||||
let s0 = emit_expr(e.kids[0]); let a = emit_expr(e.kids[1]); let b = emit_expr(e.kids[2])
|
||||
g_uses_strslice = true
|
||||
return val(emit_bind(`call ptr @fn_str_slice(ptr {s0.code}, i32 {a.code}, i32 {b.code})`), "string")
|
||||
}
|
||||
if (meth == "equals") { # byte-wise equality, same as ==
|
||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
||||
g_uses_str = true
|
||||
return val(emit_bind(`call i32 @fn_str_eq(ptr {a.code}, ptr {b.code})`), "bool")
|
||||
}
|
||||
if (meth == "concat") { # a + b, same as the + operator
|
||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
||||
g_uses_str = true
|
||||
return val(emit_bind(`call ptr @fn_str_concat(ptr {a.code}, ptr {b.code})`), "string")
|
||||
}
|
||||
|
||||
let s = emit_expr(e.kids[0])
|
||||
if (meth == "length") { # byte length
|
||||
let r = emit_bind(`call i64 @strlen(ptr {s.code})`)
|
||||
return val(emit_bind(`trunc i64 {r} to i32`), "int")
|
||||
}
|
||||
if (meth == "char_at") { # the byte at index i, 0..255
|
||||
let i = emit_expr(e.kids[1])
|
||||
let a = emit_bind(`getelementptr inbounds i8, ptr {s.code}, i32 {i.code}`)
|
||||
let c = emit_bind(`load i8, ptr {a}`)
|
||||
return val(emit_bind(`zext i8 {c} to i32`), "int")
|
||||
}
|
||||
if (meth == "to_int") { # parse a leading integer, 0 if none
|
||||
return val(emit_bind(`call i32 @atoi(ptr {s.code})`), "int")
|
||||
}
|
||||
if (meth == "upper") { # ASCII a-z -> A-Z, fresh string
|
||||
g_uses_textrt = true
|
||||
return val(emit_bind(`call ptr @fn_str_upper(ptr {s.code})`), "string")
|
||||
}
|
||||
if (meth == "lower") { # ASCII A-Z -> a-z, fresh string
|
||||
g_uses_textrt = true
|
||||
return val(emit_bind(`call ptr @fn_str_lower(ptr {s.code})`), "string")
|
||||
}
|
||||
if (meth == "trim") { # drop leading/trailing whitespace
|
||||
g_uses_textrt = true
|
||||
return val(emit_bind(`call ptr @fn_str_trim(ptr {s.code})`), "string")
|
||||
}
|
||||
if (meth == "repeat") { # s repeated n times
|
||||
g_uses_textrt = true
|
||||
let n = emit_expr(e.kids[1])
|
||||
return val(emit_bind(`call ptr @fn_str_repeat(ptr {s.code}, i32 {n.code})`), "string")
|
||||
}
|
||||
if (meth == "pad_left") { # pad with spaces to width, on the left
|
||||
g_uses_textrt = true
|
||||
let w = emit_expr(e.kids[1])
|
||||
return val(emit_bind(`call ptr @fn_str_pad(ptr {s.code}, i32 {w.code}, i1 1)`), "string")
|
||||
}
|
||||
if (meth == "pad_right") { # pad with spaces to width, on the right
|
||||
g_uses_textrt = true
|
||||
let w = emit_expr(e.kids[1])
|
||||
return val(emit_bind(`call ptr @fn_str_pad(ptr {s.code}, i32 {w.code}, i1 0)`), "string")
|
||||
}
|
||||
if (meth == "replace") { # replace every `from` with `to`
|
||||
g_uses_textrt2 = true
|
||||
let from = emit_expr(e.kids[1]); let to = emit_expr(e.kids[2])
|
||||
return val(emit_bind(`call ptr @fn_str_replace(ptr {s.code}, ptr {from.code}, ptr {to.code})`), "string")
|
||||
}
|
||||
if (meth == "split") { # split on a separator -> []string
|
||||
g_uses_textrt2 = true
|
||||
let sep = emit_expr(e.kids[1])
|
||||
return val(emit_bind(`call ptr @fn_str_split(ptr {s.code}, ptr {sep.code})`), "[]string")
|
||||
}
|
||||
if (meth == "join") { # join a []string with a separator (s is the slice)
|
||||
g_uses_textrt2 = true
|
||||
let sep = emit_expr(e.kids[1])
|
||||
return val(emit_bind(`call ptr @fn_str_join(ptr {s.code}, ptr {sep.code})`), "string")
|
||||
}
|
||||
if (meth == "contains") or (meth == "index_of") { # substring search
|
||||
let sub = emit_expr(e.kids[1])
|
||||
let p = emit_bind(`call ptr @strstr(ptr {s.code}, ptr {sub.code})`)
|
||||
if (meth == "contains") {
|
||||
let nn = emit_bind(`icmp ne ptr {p}, null`)
|
||||
return val(emit_bind(`zext i1 {nn} to i32`), "bool")
|
||||
}
|
||||
let isnull = emit_bind(`icmp eq ptr {p}, null`) # index_of -> byte offset or -1
|
||||
let pi = emit_bind(`ptrtoint ptr {p} to i64`)
|
||||
let si = emit_bind(`ptrtoint ptr {s.code} to i64`)
|
||||
let d = emit_bind(`sub i64 {pi}, {si}`)
|
||||
let d32 = emit_bind(`trunc i64 {d} to i32`)
|
||||
return val(emit_bind(`select i1 {isnull}, i32 -1, i32 {d32}`), "int")
|
||||
}
|
||||
|
||||
# starts_with / ends_with: compare against the affix over its own length
|
||||
let affix = emit_expr(e.kids[1])
|
||||
let la = emit_bind(`call i64 @strlen(ptr {affix.code})`)
|
||||
if (meth == "starts_with") { # strncmp of the head is null-safe
|
||||
let cmp = emit_bind(`call i32 @strncmp(ptr {s.code}, ptr {affix.code}, i64 {la})`)
|
||||
let eqz = emit_bind(`icmp eq i32 {cmp}, 0`)
|
||||
return val(emit_bind(`zext i1 {eqz} to i32`), "bool")
|
||||
}
|
||||
# ends_with: compare the tail, but only when the affix fits (else a negative
|
||||
# offset would read before the string) — branch so the strncmp never underflows
|
||||
let ls = emit_bind(`call i64 @strlen(ptr {s.code})`)
|
||||
let off = emit_bind(`sub i64 {ls}, {la}`)
|
||||
let res = emit_alloca("i32")
|
||||
store_at("i32", "0", res)
|
||||
let neg = emit_bind(`icmp slt i64 {off}, 0`)
|
||||
let cmpl = lbl("ew_cmp"); let en = lbl("ew_end")
|
||||
emit(" br i1 "); emit(neg); emit(", label %"); emit(en); emit(", label %"); emit(cmpl); emit("\n")
|
||||
emit(cmpl); emit(":\n")
|
||||
let tail = emit_bind(`getelementptr inbounds i8, ptr {s.code}, i64 {off}`)
|
||||
let cmp = emit_bind(`call i32 @strncmp(ptr {tail}, ptr {affix.code}, i64 {la})`)
|
||||
let eqz = emit_bind(`icmp eq i32 {cmp}, 0`)
|
||||
let z = emit_bind(`zext i1 {eqz} to i32`)
|
||||
store_at("i32", z, res)
|
||||
emit(" br label %"); emit(en); emit("\n")
|
||||
emit(en); emit(":\n")
|
||||
return val(emit_bind(`load i32, ptr {res}`), "bool")
|
||||
}
|
||||
|
||||
422
selfhost/backend/stdlib/emit_text_prelude.ludic
Normal file
422
selfhost/backend/stdlib/emit_text_prelude.ludic
Normal file
|
|
@ -0,0 +1,422 @@
|
|||
# emit_text_prelude.ludic — the emitted Text runtime prelude: the string-builder helpers (upper/lower/trim/repeat/pad and the split/join machinery) written into each program that uses Text.*. Split out of emit_text.ludic (concern: emitted runtime code, vs. emit_text.ludic's Text.* namespace dispatch).
|
||||
# emit_text_prelude — string builders that allocate: upper/lower/trim/
|
||||
# repeat/pad. Emitted once per program that uses them (g_uses_textrt). Plain
|
||||
# libc (strlen/malloc/memcpy), deterministic, C-string in and out.
|
||||
function emit_text_prelude() -> void {
|
||||
emith("define ptr @fn_str_upper(ptr %s) {\n")
|
||||
emith("entry:\n")
|
||||
emith(" %n = call i64 @strlen(ptr %s)\n")
|
||||
emith(" %sz = add i64 %n, 1\n")
|
||||
emith(" %out = call ptr @malloc(i64 %sz)\n")
|
||||
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(" %d = icmp ult i64 %i, %n\n")
|
||||
emith(" br i1 %d, label %body, label %fin\n")
|
||||
emith("body:\n")
|
||||
emith(" %sp = getelementptr i8, ptr %s, i64 %i\n")
|
||||
emith(" %c = load i8, ptr %sp\n")
|
||||
emith(" %ge = icmp uge i8 %c, 97\n")
|
||||
emith(" %le = icmp ule i8 %c, 122\n")
|
||||
emith(" %in = and i1 %ge, %le\n")
|
||||
emith(" %cc = add i8 %c, -32\n")
|
||||
emith(" %oc = select i1 %in, i8 %cc, i8 %c\n")
|
||||
emith(" %op = getelementptr i8, ptr %out, i64 %i\n")
|
||||
emith(" store i8 %oc, ptr %op\n")
|
||||
emith(" %i1 = add i64 %i, 1\n")
|
||||
emith(" store i64 %i1, ptr %ip\n")
|
||||
emith(" br label %cond\n")
|
||||
emith("fin:\n")
|
||||
emith(" %tp = getelementptr i8, ptr %out, i64 %n\n")
|
||||
emith(" store i8 0, ptr %tp\n")
|
||||
emith(" ret ptr %out\n")
|
||||
emith("}\n")
|
||||
emith("define ptr @fn_str_lower(ptr %s) {\n")
|
||||
emith("entry:\n")
|
||||
emith(" %n = call i64 @strlen(ptr %s)\n")
|
||||
emith(" %sz = add i64 %n, 1\n")
|
||||
emith(" %out = call ptr @malloc(i64 %sz)\n")
|
||||
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(" %d = icmp ult i64 %i, %n\n")
|
||||
emith(" br i1 %d, label %body, label %fin\n")
|
||||
emith("body:\n")
|
||||
emith(" %sp = getelementptr i8, ptr %s, i64 %i\n")
|
||||
emith(" %c = load i8, ptr %sp\n")
|
||||
emith(" %ge = icmp uge i8 %c, 65\n")
|
||||
emith(" %le = icmp ule i8 %c, 90\n")
|
||||
emith(" %in = and i1 %ge, %le\n")
|
||||
emith(" %cc = add i8 %c, 32\n")
|
||||
emith(" %oc = select i1 %in, i8 %cc, i8 %c\n")
|
||||
emith(" %op = getelementptr i8, ptr %out, i64 %i\n")
|
||||
emith(" store i8 %oc, ptr %op\n")
|
||||
emith(" %i1 = add i64 %i, 1\n")
|
||||
emith(" store i64 %i1, ptr %ip\n")
|
||||
emith(" br label %cond\n")
|
||||
emith("fin:\n")
|
||||
emith(" %tp = getelementptr i8, ptr %out, i64 %n\n")
|
||||
emith(" store i8 0, ptr %tp\n")
|
||||
emith(" ret ptr %out\n")
|
||||
emith("}\n")
|
||||
emith("define ptr @fn_str_trim(ptr %s) {\n")
|
||||
emith("entry:\n")
|
||||
emith(" %n = call i64 @strlen(ptr %s)\n")
|
||||
emith(" %sp = alloca i64\n")
|
||||
emith(" %ep = alloca i64\n")
|
||||
emith(" store i64 0, ptr %sp\n")
|
||||
emith(" store i64 %n, ptr %ep\n")
|
||||
emith(" br label %lcond\n")
|
||||
emith("lcond:\n")
|
||||
emith(" %a0 = load i64, ptr %sp\n")
|
||||
emith(" %e0 = load i64, ptr %ep\n")
|
||||
emith(" %lt0 = icmp ult i64 %a0, %e0\n")
|
||||
emith(" br i1 %lt0, label %lchk, label %rcond\n")
|
||||
emith("lchk:\n")
|
||||
emith(" %p0 = getelementptr i8, ptr %s, i64 %a0\n")
|
||||
emith(" %c0 = load i8, ptr %p0\n")
|
||||
emith(" %ws0 = call i1 @fn_is_ws(i8 %c0)\n")
|
||||
emith(" br i1 %ws0, label %linc, label %rcond\n")
|
||||
emith("linc:\n")
|
||||
emith(" %a1 = add i64 %a0, 1\n")
|
||||
emith(" store i64 %a1, ptr %sp\n")
|
||||
emith(" br label %lcond\n")
|
||||
emith("rcond:\n")
|
||||
emith(" %a2 = load i64, ptr %sp\n")
|
||||
emith(" %e2 = load i64, ptr %ep\n")
|
||||
emith(" %lt2 = icmp ult i64 %a2, %e2\n")
|
||||
emith(" br i1 %lt2, label %rchk, label %build\n")
|
||||
emith("rchk:\n")
|
||||
emith(" %em1 = sub i64 %e2, 1\n")
|
||||
emith(" %p1 = getelementptr i8, ptr %s, i64 %em1\n")
|
||||
emith(" %c1 = load i8, ptr %p1\n")
|
||||
emith(" %ws1 = call i1 @fn_is_ws(i8 %c1)\n")
|
||||
emith(" br i1 %ws1, label %rdec, label %build\n")
|
||||
emith("rdec:\n")
|
||||
emith(" store i64 %em1, ptr %ep\n")
|
||||
emith(" br label %rcond\n")
|
||||
emith("build:\n")
|
||||
emith(" %st = load i64, ptr %sp\n")
|
||||
emith(" %en = load i64, ptr %ep\n")
|
||||
emith(" %len = sub i64 %en, %st\n")
|
||||
emith(" %sz = add i64 %len, 1\n")
|
||||
emith(" %out = call ptr @malloc(i64 %sz)\n")
|
||||
emith(" %src = getelementptr i8, ptr %s, i64 %st\n")
|
||||
emith(" call ptr @memcpy(ptr %out, ptr %src, i64 %len)\n")
|
||||
emith(" %tp = getelementptr i8, ptr %out, i64 %len\n")
|
||||
emith(" store i8 0, ptr %tp\n")
|
||||
emith(" ret ptr %out\n")
|
||||
emith("}\n")
|
||||
emith("define i1 @fn_is_ws(i8 %c) {\n")
|
||||
emith(" %a = icmp eq i8 %c, 32\n")
|
||||
emith(" %b = icmp eq i8 %c, 9\n")
|
||||
emith(" %d = icmp eq i8 %c, 10\n")
|
||||
emith(" %e = icmp eq i8 %c, 13\n")
|
||||
emith(" %ab = or i1 %a, %b\n")
|
||||
emith(" %de = or i1 %d, %e\n")
|
||||
emith(" %r = or i1 %ab, %de\n")
|
||||
emith(" ret i1 %r\n")
|
||||
emith("}\n")
|
||||
emith("define ptr @fn_str_repeat(ptr %s, i32 %n32) {\n")
|
||||
emith("entry:\n")
|
||||
emith(" %nneg = icmp slt i32 %n32, 0\n")
|
||||
emith(" %nn = select i1 %nneg, i32 0, i32 %n32\n")
|
||||
emith(" %n = zext i32 %nn to i64\n")
|
||||
emith(" %L = call i64 @strlen(ptr %s)\n")
|
||||
emith(" %tot = mul i64 %L, %n\n")
|
||||
emith(" %sz = add i64 %tot, 1\n")
|
||||
emith(" %out = call ptr @malloc(i64 %sz)\n")
|
||||
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(" %d = icmp ult i64 %i, %n\n")
|
||||
emith(" br i1 %d, label %body, label %fin\n")
|
||||
emith("body:\n")
|
||||
emith(" %off = mul i64 %i, %L\n")
|
||||
emith(" %dst = getelementptr i8, ptr %out, i64 %off\n")
|
||||
emith(" call ptr @memcpy(ptr %dst, ptr %s, i64 %L)\n")
|
||||
emith(" %i1 = add i64 %i, 1\n")
|
||||
emith(" store i64 %i1, ptr %ip\n")
|
||||
emith(" br label %cond\n")
|
||||
emith("fin:\n")
|
||||
emith(" %tp = getelementptr i8, ptr %out, i64 %tot\n")
|
||||
emith(" store i8 0, ptr %tp\n")
|
||||
emith(" ret ptr %out\n")
|
||||
emith("}\n")
|
||||
emith("define ptr @fn_str_pad(ptr %s, i32 %w32, i1 %left) {\n")
|
||||
emith("entry:\n")
|
||||
emith(" %L = call i64 @strlen(ptr %s)\n")
|
||||
emith(" %w0 = sext i32 %w32 to i64\n")
|
||||
emith(" %need = icmp ugt i64 %w0, %L\n")
|
||||
emith(" br i1 %need, label %do, label %copy\n")
|
||||
emith("copy:\n")
|
||||
emith(" %sz0 = add i64 %L, 1\n")
|
||||
emith(" %o0 = call ptr @malloc(i64 %sz0)\n")
|
||||
emith(" %e0 = add i64 %L, 0\n")
|
||||
emith(" call ptr @memcpy(ptr %o0, ptr %s, i64 %L)\n")
|
||||
emith(" %t0 = getelementptr i8, ptr %o0, i64 %L\n")
|
||||
emith(" store i8 0, ptr %t0\n")
|
||||
emith(" ret ptr %o0\n")
|
||||
emith("do:\n")
|
||||
emith(" %pad = sub i64 %w0, %L\n")
|
||||
emith(" %sz = add i64 %w0, 1\n")
|
||||
emith(" %out = call ptr @malloc(i64 %sz)\n")
|
||||
emith(" br i1 %left, label %padleft, label %padright\n")
|
||||
emith("padleft:\n")
|
||||
emith(" call void @fn_fill_sp(ptr %out, i64 0, i64 %pad)\n")
|
||||
emith(" %dstL = getelementptr i8, ptr %out, i64 %pad\n")
|
||||
emith(" call ptr @memcpy(ptr %dstL, ptr %s, i64 %L)\n")
|
||||
emith(" br label %term\n")
|
||||
emith("padright:\n")
|
||||
emith(" call ptr @memcpy(ptr %out, ptr %s, i64 %L)\n")
|
||||
emith(" call void @fn_fill_sp(ptr %out, i64 %L, i64 %pad)\n")
|
||||
emith(" br label %term\n")
|
||||
emith("term:\n")
|
||||
emith(" %tp = getelementptr i8, ptr %out, i64 %w0\n")
|
||||
emith(" store i8 0, ptr %tp\n")
|
||||
emith(" ret ptr %out\n")
|
||||
emith("}\n")
|
||||
emith("define void @fn_fill_sp(ptr %buf, i64 %start, i64 %count) {\n")
|
||||
emith("entry:\n")
|
||||
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(" %d = icmp ult i64 %i, %count\n")
|
||||
emith(" br i1 %d, label %body, label %done\n")
|
||||
emith("body:\n")
|
||||
emith(" %off = add i64 %start, %i\n")
|
||||
emith(" %p = getelementptr i8, ptr %buf, i64 %off\n")
|
||||
emith(" store i8 32, ptr %p\n")
|
||||
emith(" %i1 = add i64 %i, 1\n")
|
||||
emith(" store i64 %i1, ptr %ip\n")
|
||||
emith(" br label %cond\n")
|
||||
emith("done:\n")
|
||||
emith(" ret void\n")
|
||||
emith("}\n")
|
||||
}
|
||||
|
||||
# emit_text2_prelude — the allocating Text ops that build strings/slices:
|
||||
# replace, join (over a []string), split (returns a []string). Emitted once per
|
||||
# program that uses them (g_uses_textrt2). Slices are the {data,len,cap}
|
||||
# %LSlice with str (ptr) elements.
|
||||
function emit_text2_prelude() -> void {
|
||||
emith("define ptr @fn_str_replace(ptr %s, ptr %from, ptr %to) {\n")
|
||||
emith("entry:\n")
|
||||
emith(" %lf = call i64 @strlen(ptr %from)\n")
|
||||
emith(" %lz = icmp eq i64 %lf, 0\n")
|
||||
emith(" br i1 %lz, label %copyall, label %scan\n")
|
||||
emith("copyall:\n")
|
||||
emith(" %ls0 = call i64 @strlen(ptr %s)\n")
|
||||
emith(" %sz0 = add i64 %ls0, 1\n")
|
||||
emith(" %o0 = call ptr @malloc(i64 %sz0)\n")
|
||||
emith(" call ptr @memcpy(ptr %o0, ptr %s, i64 %sz0)\n")
|
||||
emith(" ret ptr %o0\n")
|
||||
emith("scan:\n")
|
||||
emith(" %lt = call i64 @strlen(ptr %to)\n")
|
||||
emith(" %ls = call i64 @strlen(ptr %s)\n")
|
||||
emith(" %cntp = alloca i64\n")
|
||||
emith(" store i64 0, ptr %cntp\n")
|
||||
emith(" %curp = alloca ptr\n")
|
||||
emith(" store ptr %s, ptr %curp\n")
|
||||
emith(" br label %ccond\n")
|
||||
emith("ccond:\n")
|
||||
emith(" %cur = load ptr, ptr %curp\n")
|
||||
emith(" %hit = call ptr @strstr(ptr %cur, ptr %from)\n")
|
||||
emith(" %isnull = icmp eq ptr %hit, null\n")
|
||||
emith(" br i1 %isnull, label %csize, label %cinc\n")
|
||||
emith("cinc:\n")
|
||||
emith(" %c0 = load i64, ptr %cntp\n")
|
||||
emith(" %c1 = add i64 %c0, 1\n")
|
||||
emith(" store i64 %c1, ptr %cntp\n")
|
||||
emith(" %adv = getelementptr i8, ptr %hit, i64 %lf\n")
|
||||
emith(" store ptr %adv, ptr %curp\n")
|
||||
emith(" br label %ccond\n")
|
||||
emith("csize:\n")
|
||||
emith(" %cnt = load i64, ptr %cntp\n")
|
||||
emith(" %delta = sub i64 %lt, %lf\n")
|
||||
emith(" %grow = mul i64 %cnt, %delta\n")
|
||||
emith(" %newlen = add i64 %ls, %grow\n")
|
||||
emith(" %osz = add i64 %newlen, 1\n")
|
||||
emith(" %out = call ptr @malloc(i64 %osz)\n")
|
||||
emith(" %srcp = alloca ptr\n")
|
||||
emith(" store ptr %s, ptr %srcp\n")
|
||||
emith(" %dstp = alloca ptr\n")
|
||||
emith(" store ptr %out, ptr %dstp\n")
|
||||
emith(" br label %bcond\n")
|
||||
emith("bcond:\n")
|
||||
emith(" %src = load ptr, ptr %srcp\n")
|
||||
emith(" %h2 = call ptr @strstr(ptr %src, ptr %from)\n")
|
||||
emith(" %n2 = icmp eq ptr %h2, null\n")
|
||||
emith(" br i1 %n2, label %tail, label %seg\n")
|
||||
emith("seg:\n")
|
||||
emith(" %si = ptrtoint ptr %src to i64\n")
|
||||
emith(" %hi = ptrtoint ptr %h2 to i64\n")
|
||||
emith(" %seglen = sub i64 %hi, %si\n")
|
||||
emith(" %dst0 = load ptr, ptr %dstp\n")
|
||||
emith(" call ptr @memcpy(ptr %dst0, ptr %src, i64 %seglen)\n")
|
||||
emith(" %dst1 = getelementptr i8, ptr %dst0, i64 %seglen\n")
|
||||
emith(" call ptr @memcpy(ptr %dst1, ptr %to, i64 %lt)\n")
|
||||
emith(" %dst2 = getelementptr i8, ptr %dst1, i64 %lt\n")
|
||||
emith(" store ptr %dst2, ptr %dstp\n")
|
||||
emith(" %src2 = getelementptr i8, ptr %h2, i64 %lf\n")
|
||||
emith(" store ptr %src2, ptr %srcp\n")
|
||||
emith(" br label %bcond\n")
|
||||
emith("tail:\n")
|
||||
emith(" %src3 = load ptr, ptr %srcp\n")
|
||||
emith(" %rem = call i64 @strlen(ptr %src3)\n")
|
||||
emith(" %dst3 = load ptr, ptr %dstp\n")
|
||||
emith(" %remp1 = add i64 %rem, 1\n")
|
||||
emith(" call ptr @memcpy(ptr %dst3, ptr %src3, i64 %remp1)\n")
|
||||
emith(" ret ptr %out\n")
|
||||
emith("}\n")
|
||||
emith("define ptr @fn_str_join(ptr %h, ptr %sep) {\n")
|
||||
emith("entry:\n")
|
||||
emith(" %lp = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 1\n")
|
||||
emith(" %len32 = load i32, ptr %lp\n")
|
||||
emith(" %len = sext i32 %len32 to i64\n")
|
||||
emith(" %dp = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 0\n")
|
||||
emith(" %data = load ptr, ptr %dp\n")
|
||||
emith(" %lsep = call i64 @strlen(ptr %sep)\n")
|
||||
emith(" %totp = alloca i64\n")
|
||||
emith(" store i64 0, ptr %totp\n")
|
||||
emith(" %ip = alloca i64\n")
|
||||
emith(" store i64 0, ptr %ip\n")
|
||||
emith(" br label %scond\n")
|
||||
emith("scond:\n")
|
||||
emith(" %i = load i64, ptr %ip\n")
|
||||
emith(" %d = icmp ult i64 %i, %len\n")
|
||||
emith(" br i1 %d, label %sbody, label %alloc\n")
|
||||
emith("sbody:\n")
|
||||
emith(" %ep = getelementptr ptr, ptr %data, i64 %i\n")
|
||||
emith(" %es = load ptr, ptr %ep\n")
|
||||
emith(" %el = call i64 @strlen(ptr %es)\n")
|
||||
emith(" %t0 = load i64, ptr %totp\n")
|
||||
emith(" %t1 = add i64 %t0, %el\n")
|
||||
emith(" store i64 %t1, ptr %totp\n")
|
||||
emith(" %i1 = add i64 %i, 1\n")
|
||||
emith(" store i64 %i1, ptr %ip\n")
|
||||
emith(" br label %scond\n")
|
||||
emith("alloc:\n")
|
||||
emith(" %nz = icmp ugt i64 %len, 0\n")
|
||||
emith(" %lm1 = sub i64 %len, 1\n")
|
||||
emith(" %seps = mul i64 %lm1, %lsep\n")
|
||||
emith(" %sepsz = select i1 %nz, i64 %seps, i64 0\n")
|
||||
emith(" %tot0 = load i64, ptr %totp\n")
|
||||
emith(" %tot = add i64 %tot0, %sepsz\n")
|
||||
emith(" %osz = add i64 %tot, 1\n")
|
||||
emith(" %out = call ptr @malloc(i64 %osz)\n")
|
||||
emith(" %dstp = alloca ptr\n")
|
||||
emith(" store ptr %out, ptr %dstp\n")
|
||||
emith(" %jp = alloca i64\n")
|
||||
emith(" store i64 0, ptr %jp\n")
|
||||
emith(" br label %jcond\n")
|
||||
emith("jcond:\n")
|
||||
emith(" %j = load i64, ptr %jp\n")
|
||||
emith(" %d2 = icmp ult i64 %j, %len\n")
|
||||
emith(" br i1 %d2, label %jbody, label %jfin\n")
|
||||
emith("jbody:\n")
|
||||
emith(" %jnz = icmp ugt i64 %j, 0\n")
|
||||
emith(" br i1 %jnz, label %putsep, label %putel\n")
|
||||
emith("putsep:\n")
|
||||
emith(" %ds0 = load ptr, ptr %dstp\n")
|
||||
emith(" call ptr @memcpy(ptr %ds0, ptr %sep, i64 %lsep)\n")
|
||||
emith(" %ds1 = getelementptr i8, ptr %ds0, i64 %lsep\n")
|
||||
emith(" store ptr %ds1, ptr %dstp\n")
|
||||
emith(" br label %putel\n")
|
||||
emith("putel:\n")
|
||||
emith(" %ep2 = getelementptr ptr, ptr %data, i64 %j\n")
|
||||
emith(" %es2 = load ptr, ptr %ep2\n")
|
||||
emith(" %el2 = call i64 @strlen(ptr %es2)\n")
|
||||
emith(" %ds2 = load ptr, ptr %dstp\n")
|
||||
emith(" call ptr @memcpy(ptr %ds2, ptr %es2, i64 %el2)\n")
|
||||
emith(" %ds3 = getelementptr i8, ptr %ds2, i64 %el2\n")
|
||||
emith(" store ptr %ds3, ptr %dstp\n")
|
||||
emith(" %j1 = add i64 %j, 1\n")
|
||||
emith(" store i64 %j1, ptr %jp\n")
|
||||
emith(" br label %jcond\n")
|
||||
emith("jfin:\n")
|
||||
emith(" %dsf = load ptr, ptr %dstp\n")
|
||||
emith(" store i8 0, ptr %dsf\n")
|
||||
emith(" ret ptr %out\n")
|
||||
emith("}\n")
|
||||
emith("define ptr @fn_str_split(ptr %s, ptr %sep) {\n")
|
||||
emith("entry:\n")
|
||||
emith(" %lsep = call i64 @strlen(ptr %sep)\n")
|
||||
emith(" %cntp = alloca i64\n")
|
||||
emith(" store i64 1, ptr %cntp\n")
|
||||
emith(" %curp = alloca ptr\n")
|
||||
emith(" store ptr %s, ptr %curp\n")
|
||||
emith(" br label %ccond\n")
|
||||
emith("ccond:\n")
|
||||
emith(" %cur = load ptr, ptr %curp\n")
|
||||
emith(" %hit = call ptr @strstr(ptr %cur, ptr %sep)\n")
|
||||
emith(" %isnull = icmp eq ptr %hit, null\n")
|
||||
emith(" br i1 %isnull, label %mk, label %cinc\n")
|
||||
emith("cinc:\n")
|
||||
emith(" %c0 = load i64, ptr %cntp\n")
|
||||
emith(" %c1 = add i64 %c0, 1\n")
|
||||
emith(" store i64 %c1, ptr %cntp\n")
|
||||
emith(" %adv = getelementptr i8, ptr %hit, i64 %lsep\n")
|
||||
emith(" store ptr %adv, ptr %curp\n")
|
||||
emith(" br label %ccond\n")
|
||||
emith("mk:\n")
|
||||
emith(" %cnt = load i64, ptr %cntp\n")
|
||||
emith(" %hdr = call ptr @malloc(i64 16)\n")
|
||||
emith(" %arrsz = mul i64 %cnt, 8\n")
|
||||
emith(" %arr = call ptr @malloc(i64 %arrsz)\n")
|
||||
emith(" %d0 = getelementptr inbounds %LSlice, ptr %hdr, i32 0, i32 0\n")
|
||||
emith(" store ptr %arr, ptr %d0\n")
|
||||
emith(" %cnt32 = trunc i64 %cnt to i32\n")
|
||||
emith(" %d1 = getelementptr inbounds %LSlice, ptr %hdr, i32 0, i32 1\n")
|
||||
emith(" store i32 %cnt32, ptr %d1\n")
|
||||
emith(" %d2 = getelementptr inbounds %LSlice, ptr %hdr, i32 0, i32 2\n")
|
||||
emith(" store i32 %cnt32, ptr %d2\n")
|
||||
emith(" %srcp = alloca ptr\n")
|
||||
emith(" store ptr %s, ptr %srcp\n")
|
||||
emith(" %ip = alloca i64\n")
|
||||
emith(" store i64 0, ptr %ip\n")
|
||||
emith(" br label %fcond\n")
|
||||
emith("fcond:\n")
|
||||
emith(" %i = load i64, ptr %ip\n")
|
||||
emith(" %lt = icmp ult i64 %i, %cnt\n")
|
||||
emith(" br i1 %lt, label %fbody, label %done\n")
|
||||
emith("fbody:\n")
|
||||
emith(" %src = load ptr, ptr %srcp\n")
|
||||
emith(" %h2 = call ptr @strstr(ptr %src, ptr %sep)\n")
|
||||
emith(" %n2 = icmp eq ptr %h2, null\n")
|
||||
emith(" br i1 %n2, label %last, label %mid\n")
|
||||
emith("mid:\n")
|
||||
emith(" %si = ptrtoint ptr %src to i64\n")
|
||||
emith(" %hi = ptrtoint ptr %h2 to i64\n")
|
||||
emith(" %seglen = sub i64 %hi, %si\n")
|
||||
emith(" br label %store\n")
|
||||
emith("last:\n")
|
||||
emith(" %rem = call i64 @strlen(ptr %src)\n")
|
||||
emith(" br label %store\n")
|
||||
emith("store:\n")
|
||||
emith(" %seg = phi i64 [ %seglen, %mid ], [ %rem, %last ]\n")
|
||||
emith(" %ssz = add i64 %seg, 1\n")
|
||||
emith(" %sub = call ptr @malloc(i64 %ssz)\n")
|
||||
emith(" %src2 = load ptr, ptr %srcp\n")
|
||||
emith(" call ptr @memcpy(ptr %sub, ptr %src2, i64 %seg)\n")
|
||||
emith(" %tp = getelementptr i8, ptr %sub, i64 %seg\n")
|
||||
emith(" store i8 0, ptr %tp\n")
|
||||
emith(" %slot = getelementptr ptr, ptr %arr, i64 %i\n")
|
||||
emith(" store ptr %sub, ptr %slot\n")
|
||||
emith(" %adv2 = getelementptr i8, ptr %h2, i64 %lsep\n")
|
||||
emith(" store ptr %adv2, ptr %srcp\n")
|
||||
emith(" %i1 = add i64 %i, 1\n")
|
||||
emith(" store i64 %i1, ptr %ip\n")
|
||||
emith(" br label %fcond\n")
|
||||
emith("done:\n")
|
||||
emith(" ret ptr %hdr\n")
|
||||
emith("}\n")
|
||||
}
|
||||
33
selfhost/backend/stdlib/emit_time.ludic
Normal file
33
selfhost/backend/stdlib/emit_time.ludic
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# emit_time.ludic — the Time.* namespace. frame/elapsed/delta are deterministic
|
||||
# (driven by @L_frame, the per-frame counter the game loop increments); now() is
|
||||
# the wall clock and is explicitly non-deterministic. The frame clock ticks at a
|
||||
# fixed 60 per second, so delta is the constant 1/60 s = 1092 in Q16.16.
|
||||
|
||||
function is_time_ns(meth: pointer) -> bool {
|
||||
if (meth == "frame") or (meth == "delta") or (meth == "elapsed") or (meth == "now") { return true }
|
||||
if (meth == "since") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
function emit_time_ns(meth: pointer, e: Node) -> Val {
|
||||
if (meth == "frame") { # completed frames since start
|
||||
return val(emit_bind("load i32, ptr @L_frame"), "int")
|
||||
}
|
||||
if (meth == "delta") { # seconds per frame, 1/60 (fixed)
|
||||
return val("1092", "fixed")
|
||||
}
|
||||
if (meth == "elapsed") { # seconds since start = frame / 60 (fixed)
|
||||
let f = emit_bind("load i32, ptr @L_frame")
|
||||
return val(emit_bind(`mul i32 {f}, 1092`), "fixed")
|
||||
}
|
||||
if (meth == "since") { # seconds elapsed from a past instant to now
|
||||
let past = emit_expr(e.kids[0]) # NON-DETERMINISTIC (reads the wall clock)
|
||||
let nw = emit_bind("call i64 @time(ptr null)")
|
||||
let n32 = emit_bind(`trunc i64 {nw} to i32`)
|
||||
return val(emit_bind(`sub i32 {n32}, {past.code}`), "int")
|
||||
}
|
||||
# now: wall-clock seconds since the epoch — NON-DETERMINISTIC, for seeding /
|
||||
# telemetry only, never the lockstep simulation.
|
||||
let t = emit_bind("call i64 @time(ptr null)")
|
||||
return val(emit_bind(`trunc i64 {t} to i32`), "int")
|
||||
}
|
||||
318
selfhost/backend/stdlib/emit_unicode.ludic
Normal file
318
selfhost/backend/stdlib/emit_unicode.ludic
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
# emit_unicode.ludic — the Unicode.* namespace: correct-by-default text over
|
||||
# UTF-8, so player names, translated UI, and chat behave for every language
|
||||
# instead of counting bytes and splitting characters in half. The byte-oriented
|
||||
# Text.* stays for speed; Unicode.* is the layer that understands code points and
|
||||
# (approximately) grapheme clusters.
|
||||
#
|
||||
# Unicode.len(s) -> int number of code points (not bytes)
|
||||
# Unicode.byte_len(s) -> int number of bytes (the contrast to len)
|
||||
# Unicode.is_valid_utf8(s) -> bool strict UTF-8 validation of untrusted input
|
||||
# Unicode.char_at(s, i) -> int the i-th code point (-1 if out of range)
|
||||
# Unicode.chars(s) -> []int every code point, in order
|
||||
# Unicode.upper(s) -> string uppercased (ASCII + Latin-1)
|
||||
# Unicode.lower(s) -> string lowercased (ASCII + Latin-1)
|
||||
# Unicode.truncate(s, n) -> string first n code points, never a half-char
|
||||
# Unicode.grapheme_len(s) -> int user-perceived characters (approx UAX#29)
|
||||
#
|
||||
# Unicode version / coverage: this v1 decodes and validates the full UTF-8 range.
|
||||
# Case mapping covers ASCII and the Latin-1 Supplement letters (correct for
|
||||
# Western-European text); Latin-Extended/Greek/Cyrillic case, locale rules (e.g.
|
||||
# Turkish i, German ß->SS), and normalization (NFC) are documented follow-ups.
|
||||
# grapheme_len is an approximation of UAX#29 that handles combining marks,
|
||||
# variation selectors, ZWJ sequences (e.g. family emoji), and regional-indicator
|
||||
# flag pairs — enough for measuring and truncating real player text; full
|
||||
# segmentation with its data tables is a follow-up.
|
||||
#
|
||||
# Every function is pure over a NUL-terminated UTF-8 buffer: no global state, no
|
||||
# platform dependence, so results are identical on native, headless, and wasm.
|
||||
|
||||
function is_unicode_ns(meth: pointer) -> bool {
|
||||
if (meth == "len") or (meth == "byte_len") or (meth == "is_valid_utf8") { return true }
|
||||
if (meth == "char_at") or (meth == "chars") { return true }
|
||||
if (meth == "upper") or (meth == "lower") or (meth == "truncate") { return true }
|
||||
if (meth == "grapheme_len") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
function emit_unicode_ns(meth: pointer, e: Node) -> Val {
|
||||
g_uses_unicodert = true
|
||||
if (meth == "byte_len") { # bytes, the contrast to len()
|
||||
let s = emit_expr(e.kids[0])
|
||||
let r = emit_bind(`call i64 @strlen(ptr {s.code})`)
|
||||
return val(emit_bind(`trunc i64 {r} to i32`), "int")
|
||||
}
|
||||
if (meth == "len") {
|
||||
let s = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call i32 @fn_uni_len(ptr {s.code})`), "int")
|
||||
}
|
||||
if (meth == "is_valid_utf8") {
|
||||
let s = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call i32 @fn_uni_valid(ptr {s.code})`), "bool")
|
||||
}
|
||||
if (meth == "char_at") {
|
||||
let s = emit_expr(e.kids[0]); let i = emit_expr(e.kids[1])
|
||||
return val(emit_bind(`call i32 @fn_uni_char_at(ptr {s.code}, i32 {i.code})`), "int")
|
||||
}
|
||||
if (meth == "chars") {
|
||||
let s = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call ptr @fn_uni_chars(ptr {s.code})`), "[]int")
|
||||
}
|
||||
if (meth == "upper") {
|
||||
let s = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call ptr @fn_uni_case(ptr {s.code}, i32 1)`), "string")
|
||||
}
|
||||
if (meth == "lower") {
|
||||
let s = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call ptr @fn_uni_case(ptr {s.code}, i32 0)`), "string")
|
||||
}
|
||||
if (meth == "truncate") {
|
||||
let s = emit_expr(e.kids[0]); let n = emit_expr(e.kids[1])
|
||||
return val(emit_bind(`call ptr @fn_uni_truncate(ptr {s.code}, i32 {n.code})`), "string")
|
||||
}
|
||||
# grapheme_len
|
||||
let s = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call i32 @fn_uni_grapheme_len(ptr {s.code})`), "int")
|
||||
}
|
||||
|
||||
# emit_unicode_prelude — the UTF-8 runtime, emitted once per program that uses
|
||||
# Unicode.* (g_uses_unicodert). Pure integer/byte IR over NUL-terminated buffers.
|
||||
function emit_unicode_prelude() -> void {
|
||||
emit_uni_core()
|
||||
emit_uni_case_fns()
|
||||
emit_uni_str_fns()
|
||||
}
|
||||
|
||||
# ---- decode + length + validation ------------------------------------------
|
||||
function emit_uni_core() -> void {
|
||||
# code-point count: every byte that is NOT a UTF-8 continuation byte
|
||||
# (0b10xxxxxx) begins a new code point. Walks byte-by-byte, so it is safe on
|
||||
# truncated/invalid input and stops exactly at the NUL.
|
||||
emith("define i32 @fn_uni_len(ptr %s) {\n")
|
||||
emith("entry:\n %ip = alloca i32\n %np = alloca i32\n store i32 0, ptr %ip\n store i32 0, ptr %np\n br label %lp\n")
|
||||
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
||||
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %done, label %go\n")
|
||||
emith("go:\n %m = and i32 %c, 192\n %cont = icmp eq i32 %m, 128\n br i1 %cont, label %skip, label %count\n")
|
||||
emith("count:\n %n = load i32, ptr %np\n %n1 = add i32 %n, 1\n store i32 %n1, ptr %np\n br label %skip\n")
|
||||
emith("skip:\n %i1 = add i32 %i, 1\n store i32 %i1, ptr %ip\n br label %lp\n")
|
||||
emith("done:\n %r = load i32, ptr %np\n ret i32 %r\n}\n")
|
||||
|
||||
# decode one code point at byte offset %i; store it to %cpout; return the byte
|
||||
# offset just past it. Lenient and overrun-safe: a truncated multibyte sequence
|
||||
# (a continuation byte that is NUL) or an invalid lead byte decodes as a single
|
||||
# byte, so the walk always makes progress and never reads past the terminator.
|
||||
emith("define i32 @fn_uni_decode(ptr %s, i32 %i, ptr %cpout) {\n")
|
||||
emith("entry:\n %p0 = getelementptr i8, ptr %s, i32 %i\n %b0 = load i8, ptr %p0\n %c0 = zext i8 %b0 to i32\n")
|
||||
emith(" %a1 = icmp ult i32 %c0, 128\n br i1 %a1, label %one, label %multi\n")
|
||||
emith("one:\n store i32 %c0, ptr %cpout\n %oi = add i32 %i, 1\n ret i32 %oi\n")
|
||||
emith("multi:\n %h2 = and i32 %c0, 224\n %is2 = icmp eq i32 %h2, 192\n br i1 %is2, label %two, label %c3\n")
|
||||
emith("c3:\n %h3 = and i32 %c0, 240\n %is3 = icmp eq i32 %h3, 224\n br i1 %is3, label %three, label %c4\n")
|
||||
emith("c4:\n %h4 = and i32 %c0, 248\n %is4 = icmp eq i32 %h4, 240\n br i1 %is4, label %four, label %bad\n")
|
||||
emith("bad:\n store i32 %c0, ptr %cpout\n %bi = add i32 %i, 1\n ret i32 %bi\n")
|
||||
# two-byte
|
||||
emith("two:\n %t1i = add i32 %i, 1\n %t1p = getelementptr i8, ptr %s, i32 %t1i\n %t1b = load i8, ptr %t1p\n %t1 = zext i8 %t1b to i32\n")
|
||||
emith(" %t1z = icmp eq i32 %t1, 0\n br i1 %t1z, label %tbad, label %tok\n")
|
||||
emith("tbad:\n store i32 %c0, ptr %cpout\n %tbi = add i32 %i, 1\n ret i32 %tbi\n")
|
||||
emith("tok:\n %tm = and i32 %c0, 31\n %tsh = shl i32 %tm, 6\n %tl = and i32 %t1, 63\n %tcp = or i32 %tsh, %tl\n store i32 %tcp, ptr %cpout\n %ti = add i32 %i, 2\n ret i32 %ti\n")
|
||||
# three-byte
|
||||
emith("three:\n %r1i = add i32 %i, 1\n %r1p = getelementptr i8, ptr %s, i32 %r1i\n %r1b = load i8, ptr %r1p\n %r1 = zext i8 %r1b to i32\n")
|
||||
emith(" %r1z = icmp eq i32 %r1, 0\n br i1 %r1z, label %rbad, label %rc2\n")
|
||||
emith("rbad:\n store i32 %c0, ptr %cpout\n %rbi = add i32 %i, 1\n ret i32 %rbi\n")
|
||||
emith("rc2:\n %r2i = add i32 %i, 2\n %r2p = getelementptr i8, ptr %s, i32 %r2i\n %r2b = load i8, ptr %r2p\n %r2 = zext i8 %r2b to i32\n")
|
||||
emith(" %r2z = icmp eq i32 %r2, 0\n br i1 %r2z, label %rbad, label %rok\n")
|
||||
emith("rok:\n %rm = and i32 %c0, 15\n %rsh = shl i32 %rm, 12\n %ram = and i32 %r1, 63\n %rash = shl i32 %ram, 6\n %rbm = and i32 %r2, 63\n")
|
||||
emith(" %rp1 = or i32 %rsh, %rash\n %rcp = or i32 %rp1, %rbm\n store i32 %rcp, ptr %cpout\n %ri = add i32 %i, 3\n ret i32 %ri\n")
|
||||
# four-byte
|
||||
emith("four:\n %f1i = add i32 %i, 1\n %f1p = getelementptr i8, ptr %s, i32 %f1i\n %f1b = load i8, ptr %f1p\n %f1 = zext i8 %f1b to i32\n")
|
||||
emith(" %f1z = icmp eq i32 %f1, 0\n br i1 %f1z, label %fbad, label %fc2\n")
|
||||
emith("fbad:\n store i32 %c0, ptr %cpout\n %fbi = add i32 %i, 1\n ret i32 %fbi\n")
|
||||
emith("fc2:\n %f2i = add i32 %i, 2\n %f2p = getelementptr i8, ptr %s, i32 %f2i\n %f2b = load i8, ptr %f2p\n %f2 = zext i8 %f2b to i32\n")
|
||||
emith(" %f2z = icmp eq i32 %f2, 0\n br i1 %f2z, label %fbad, label %fc3\n")
|
||||
emith("fc3:\n %f3i = add i32 %i, 3\n %f3p = getelementptr i8, ptr %s, i32 %f3i\n %f3b = load i8, ptr %f3p\n %f3 = zext i8 %f3b to i32\n")
|
||||
emith(" %f3z = icmp eq i32 %f3, 0\n br i1 %f3z, label %fbad, label %fok\n")
|
||||
emith("fok:\n %fm = and i32 %c0, 7\n %fsh = shl i32 %fm, 18\n %fam = and i32 %f1, 63\n %fash = shl i32 %fam, 12\n")
|
||||
emith(" %fbm = and i32 %f2, 63\n %fbsh = shl i32 %fbm, 6\n %fcm = and i32 %f3, 63\n")
|
||||
emith(" %fp1 = or i32 %fsh, %fash\n %fp2 = or i32 %fp1, %fbsh\n %fcp = or i32 %fp2, %fcm\n store i32 %fcp, ptr %cpout\n %fi = add i32 %i, 4\n ret i32 %fi\n}\n")
|
||||
|
||||
# strict UTF-8 validation: correct continuation bytes, no overlong encodings,
|
||||
# no surrogates (U+D800..U+DFFF), and nothing above U+10FFFF. Returns 1/0.
|
||||
emith("define i32 @fn_uni_valid(ptr %s) {\n")
|
||||
emith("entry:\n %ip = alloca i32\n store i32 0, ptr %ip\n br label %lp\n")
|
||||
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
||||
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %good, label %g\n")
|
||||
emith("g:\n %a1 = icmp ult i32 %c, 128\n br i1 %a1, label %adv1, label %m2\n")
|
||||
emith("adv1:\n %n1 = add i32 %i, 1\n store i32 %n1, ptr %ip\n br label %lp\n")
|
||||
emith("m2:\n %h2 = and i32 %c, 224\n %is2 = icmp eq i32 %h2, 192\n br i1 %is2, label %do2, label %m3\n")
|
||||
emith("m3:\n %h3 = and i32 %c, 240\n %is3 = icmp eq i32 %h3, 224\n br i1 %is3, label %do3, label %m4\n")
|
||||
emith("m4:\n %h4 = and i32 %c, 248\n %is4 = icmp eq i32 %h4, 240\n br i1 %is4, label %do4, label %bad\n")
|
||||
emith("bad:\n ret i32 0\n")
|
||||
# 2-byte: cp in [0x80,0x7FF]
|
||||
emith("do2:\n %v1i = add i32 %i, 1\n %v1p = getelementptr i8, ptr %s, i32 %v1i\n %v1b = load i8, ptr %v1p\n %v1 = zext i8 %v1b to i32\n")
|
||||
emith(" %v1m = and i32 %v1, 192\n %v1ok = icmp eq i32 %v1m, 128\n br i1 %v1ok, label %do2b, label %bad\n")
|
||||
emith("do2b:\n %q2m = and i32 %c, 31\n %q2s = shl i32 %q2m, 6\n %q2l = and i32 %v1, 63\n %cp2 = or i32 %q2s, %q2l\n")
|
||||
emith(" %ov2 = icmp ult i32 %cp2, 128\n br i1 %ov2, label %bad, label %adv2\n")
|
||||
emith("adv2:\n %n2 = add i32 %i, 2\n store i32 %n2, ptr %ip\n br label %lp\n")
|
||||
# 3-byte: cp in [0x800,0xFFFF], excluding surrogates
|
||||
emith("do3:\n %w1i = add i32 %i, 1\n %w1p = getelementptr i8, ptr %s, i32 %w1i\n %w1b = load i8, ptr %w1p\n %w1 = zext i8 %w1b to i32\n")
|
||||
emith(" %w1m = and i32 %w1, 192\n %w1ok = icmp eq i32 %w1m, 128\n br i1 %w1ok, label %do3b, label %bad\n")
|
||||
emith("do3b:\n %w2i = add i32 %i, 2\n %w2p = getelementptr i8, ptr %s, i32 %w2i\n %w2b = load i8, ptr %w2p\n %w2 = zext i8 %w2b to i32\n")
|
||||
emith(" %w2m = and i32 %w2, 192\n %w2ok = icmp eq i32 %w2m, 128\n br i1 %w2ok, label %do3c, label %bad\n")
|
||||
emith("do3c:\n %e3m = and i32 %c, 15\n %e3s = shl i32 %e3m, 12\n %e3am = and i32 %w1, 63\n %e3as = shl i32 %e3am, 6\n %e3bm = and i32 %w2, 63\n")
|
||||
emith(" %cp3p = or i32 %e3s, %e3as\n %cp3 = or i32 %cp3p, %e3bm\n")
|
||||
emith(" %ov3 = icmp ult i32 %cp3, 2048\n br i1 %ov3, label %bad, label %do3d\n")
|
||||
emith("do3d:\n %sg1 = icmp uge i32 %cp3, 55296\n %sg2 = icmp ule i32 %cp3, 57343\n %sg = and i1 %sg1, %sg2\n br i1 %sg, label %bad, label %adv3\n")
|
||||
emith("adv3:\n %n3 = add i32 %i, 3\n store i32 %n3, ptr %ip\n br label %lp\n")
|
||||
# 4-byte: cp in [0x10000,0x10FFFF]
|
||||
emith("do4:\n %x1i = add i32 %i, 1\n %x1p = getelementptr i8, ptr %s, i32 %x1i\n %x1b = load i8, ptr %x1p\n %x1 = zext i8 %x1b to i32\n")
|
||||
emith(" %x1m = and i32 %x1, 192\n %x1ok = icmp eq i32 %x1m, 128\n br i1 %x1ok, label %do4b, label %bad\n")
|
||||
emith("do4b:\n %x2i = add i32 %i, 2\n %x2p = getelementptr i8, ptr %s, i32 %x2i\n %x2b = load i8, ptr %x2p\n %x2 = zext i8 %x2b to i32\n")
|
||||
emith(" %x2m = and i32 %x2, 192\n %x2ok = icmp eq i32 %x2m, 128\n br i1 %x2ok, label %do4c, label %bad\n")
|
||||
emith("do4c:\n %x3i = add i32 %i, 3\n %x3p = getelementptr i8, ptr %s, i32 %x3i\n %x3b = load i8, ptr %x3p\n %x3 = zext i8 %x3b to i32\n")
|
||||
emith(" %x3m = and i32 %x3, 192\n %x3ok = icmp eq i32 %x3m, 128\n br i1 %x3ok, label %do4d, label %bad\n")
|
||||
emith("do4d:\n %y0 = and i32 %c, 7\n %y0s = shl i32 %y0, 18\n %y1m = and i32 %x1, 63\n %y1s = shl i32 %y1m, 12\n %y2m = and i32 %x2, 63\n %y2s = shl i32 %y2m, 6\n %y3m = and i32 %x3, 63\n")
|
||||
emith(" %cp4p = or i32 %y0s, %y1s\n %cp4q = or i32 %cp4p, %y2s\n %cp4 = or i32 %cp4q, %y3m\n")
|
||||
emith(" %ov4 = icmp ult i32 %cp4, 65536\n br i1 %ov4, label %bad, label %do4e\n")
|
||||
emith("do4e:\n %hi4 = icmp ugt i32 %cp4, 1114111\n br i1 %hi4, label %bad, label %adv4\n")
|
||||
emith("adv4:\n %n4 = add i32 %i, 4\n store i32 %n4, ptr %ip\n br label %lp\n")
|
||||
emith("good:\n ret i32 1\n}\n")
|
||||
}
|
||||
|
||||
# ---- case mapping + encode helpers -----------------------------------------
|
||||
function emit_uni_case_fns() -> void {
|
||||
# uppercase one code point: ASCII a-z and Latin-1 a-with-diacritic .. thorn
|
||||
# (0xE0..0xFE except 0xF7). Other code points pass through unchanged (v1).
|
||||
emith("define i32 @fn_uni_upcp(i32 %c) {\n")
|
||||
emith(" %la = icmp uge i32 %c, 97\n %lb = icmp ule i32 %c, 122\n %asc = and i1 %la, %lb\n")
|
||||
emith(" %da = icmp uge i32 %c, 224\n %db = icmp ule i32 %c, 254\n %dd = icmp ne i32 %c, 247\n %d1 = and i1 %da, %db\n %d2 = and i1 %d1, %dd\n")
|
||||
emith(" %map = or i1 %asc, %d2\n %up = sub i32 %c, 32\n %r = select i1 %map, i32 %up, i32 %c\n ret i32 %r\n}\n")
|
||||
|
||||
# lowercase one code point: ASCII A-Z and Latin-1 A-with-diacritic .. Thorn
|
||||
# (0xC0..0xDE except 0xD7). Other code points pass through unchanged (v1).
|
||||
emith("define i32 @fn_uni_locp(i32 %c) {\n")
|
||||
emith(" %ua = icmp uge i32 %c, 65\n %ub = icmp ule i32 %c, 90\n %asc = and i1 %ua, %ub\n")
|
||||
emith(" %da = icmp uge i32 %c, 192\n %db = icmp ule i32 %c, 222\n %dd = icmp ne i32 %c, 215\n %d1 = and i1 %da, %db\n %d2 = and i1 %d1, %dd\n")
|
||||
emith(" %map = or i1 %asc, %d2\n %lo = add i32 %c, 32\n %r = select i1 %map, i32 %lo, i32 %c\n ret i32 %r\n}\n")
|
||||
|
||||
# UTF-8 byte width needed to encode a code point
|
||||
emith("define i32 @fn_uni_cpwidth(i32 %cp) {\n")
|
||||
emith(" %a = icmp ult i32 %cp, 128\n %b = icmp ult i32 %cp, 2048\n %c = icmp ult i32 %cp, 65536\n")
|
||||
emith(" %w34 = select i1 %c, i32 3, i32 4\n %w234 = select i1 %b, i32 2, i32 %w34\n %w = select i1 %a, i32 1, i32 %w234\n ret i32 %w\n}\n")
|
||||
|
||||
# encode a code point into %dst at byte offset %off; return the new offset
|
||||
emith("define i32 @fn_uni_encode(ptr %dst, i32 %off, i32 %cp) {\n")
|
||||
emith("entry:\n %w = call i32 @fn_uni_cpwidth(i32 %cp)\n %is1 = icmp eq i32 %w, 1\n br i1 %is1, label %e1, label %k2\n")
|
||||
emith("e1:\n %d0 = getelementptr i8, ptr %dst, i32 %off\n %b0 = trunc i32 %cp to i8\n store i8 %b0, ptr %d0\n %o1 = add i32 %off, 1\n ret i32 %o1\n")
|
||||
emith("k2:\n %is2 = icmp eq i32 %w, 2\n br i1 %is2, label %e2, label %k3\n")
|
||||
emith("e2:\n %hi2 = lshr i32 %cp, 6\n %by0 = or i32 %hi2, 192\n %lo2 = and i32 %cp, 63\n %by1 = or i32 %lo2, 128\n")
|
||||
emith(" %p20 = getelementptr i8, ptr %dst, i32 %off\n %t20 = trunc i32 %by0 to i8\n store i8 %t20, ptr %p20\n")
|
||||
emith(" %o21 = add i32 %off, 1\n %p21 = getelementptr i8, ptr %dst, i32 %o21\n %t21 = trunc i32 %by1 to i8\n store i8 %t21, ptr %p21\n %o2 = add i32 %off, 2\n ret i32 %o2\n")
|
||||
emith("k3:\n %is3 = icmp eq i32 %w, 3\n br i1 %is3, label %e3, label %e4\n")
|
||||
emith("e3:\n %hi3 = lshr i32 %cp, 12\n %by30 = or i32 %hi3, 224\n %m31 = lshr i32 %cp, 6\n %m31b = and i32 %m31, 63\n %by31 = or i32 %m31b, 128\n %m32 = and i32 %cp, 63\n %by32 = or i32 %m32, 128\n")
|
||||
emith(" %p30 = getelementptr i8, ptr %dst, i32 %off\n %t30 = trunc i32 %by30 to i8\n store i8 %t30, ptr %p30\n")
|
||||
emith(" %o31 = add i32 %off, 1\n %p31 = getelementptr i8, ptr %dst, i32 %o31\n %t31 = trunc i32 %by31 to i8\n store i8 %t31, ptr %p31\n")
|
||||
emith(" %o32 = add i32 %off, 2\n %p32 = getelementptr i8, ptr %dst, i32 %o32\n %t32 = trunc i32 %by32 to i8\n store i8 %t32, ptr %p32\n %o3 = add i32 %off, 3\n ret i32 %o3\n")
|
||||
emith("e4:\n %hi4 = lshr i32 %cp, 18\n %by40 = or i32 %hi4, 240\n %m41 = lshr i32 %cp, 12\n %m41b = and i32 %m41, 63\n %by41 = or i32 %m41b, 128\n")
|
||||
emith(" %m42 = lshr i32 %cp, 6\n %m42b = and i32 %m42, 63\n %by42 = or i32 %m42b, 128\n %m43 = and i32 %cp, 63\n %by43 = or i32 %m43, 128\n")
|
||||
emith(" %p40 = getelementptr i8, ptr %dst, i32 %off\n %t40 = trunc i32 %by40 to i8\n store i8 %t40, ptr %p40\n")
|
||||
emith(" %o41 = add i32 %off, 1\n %p41 = getelementptr i8, ptr %dst, i32 %o41\n %t41 = trunc i32 %by41 to i8\n store i8 %t41, ptr %p41\n")
|
||||
emith(" %o42 = add i32 %off, 2\n %p42 = getelementptr i8, ptr %dst, i32 %o42\n %t42 = trunc i32 %by42 to i8\n store i8 %t42, ptr %p42\n")
|
||||
emith(" %o43 = add i32 %off, 3\n %p43 = getelementptr i8, ptr %dst, i32 %o43\n %t43 = trunc i32 %by43 to i8\n store i8 %t43, ptr %p43\n %o4 = add i32 %off, 4\n ret i32 %o4\n}\n")
|
||||
|
||||
# is this code point a grapheme "extend" (a combining mark or variation
|
||||
# selector that joins the preceding base)? An approximation of the common
|
||||
# ranges; ZWJ and regional indicators are handled by grapheme_len itself.
|
||||
emith("define i32 @fn_uni_is_extend(i32 %c) {\n")
|
||||
emith(" %r1 = call i32 @fn_uni_inrange(i32 %c, i32 768, i32 879)\n") # 0300-036F combining diacritics
|
||||
emith(" %r2 = call i32 @fn_uni_inrange(i32 %c, i32 6832, i32 6911)\n") # 1AB0-1AFF
|
||||
emith(" %r3 = call i32 @fn_uni_inrange(i32 %c, i32 7616, i32 7679)\n") # 1DC0-1DFF
|
||||
emith(" %r4 = call i32 @fn_uni_inrange(i32 %c, i32 8400, i32 8447)\n") # 20D0-20FF
|
||||
emith(" %r5 = call i32 @fn_uni_inrange(i32 %c, i32 65056, i32 65071)\n") # FE20-FE2F
|
||||
emith(" %r6 = call i32 @fn_uni_inrange(i32 %c, i32 65024, i32 65039)\n") # FE00-FE0F variation selectors
|
||||
emith(" %r7 = call i32 @fn_uni_inrange(i32 %c, i32 917760, i32 917999)\n") # E0100-E01EF
|
||||
emith(" %o1 = or i32 %r1, %r2\n %o2 = or i32 %o1, %r3\n %o3 = or i32 %o2, %r4\n %o4 = or i32 %o3, %r5\n %o5 = or i32 %o4, %r6\n %o6 = or i32 %o5, %r7\n ret i32 %o6\n}\n")
|
||||
|
||||
emith("define i32 @fn_uni_inrange(i32 %c, i32 %lo, i32 %hi) {\n")
|
||||
emith(" %a = icmp uge i32 %c, %lo\n %b = icmp ule i32 %c, %hi\n %x = and i1 %a, %b\n %r = zext i1 %x to i32\n ret i32 %r\n}\n")
|
||||
}
|
||||
|
||||
# ---- code-point access + string builders -----------------------------------
|
||||
function emit_uni_str_fns() -> void {
|
||||
# the idx-th code point, or -1 when idx is past the end
|
||||
emith("define i32 @fn_uni_char_at(ptr %s, i32 %idx) {\n")
|
||||
emith("entry:\n %ip = alloca i32\n %kp = alloca i32\n %cp = alloca i32\n store i32 0, ptr %ip\n store i32 0, ptr %kp\n br label %lp\n")
|
||||
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
||||
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %none, label %go\n")
|
||||
emith("go:\n %ni = call i32 @fn_uni_decode(ptr %s, i32 %i, ptr %cp)\n %k = load i32, ptr %kp\n %hit = icmp eq i32 %k, %idx\n br i1 %hit, label %found, label %next\n")
|
||||
emith("found:\n %v = load i32, ptr %cp\n ret i32 %v\n")
|
||||
emith("next:\n %k1 = add i32 %k, 1\n store i32 %k1, ptr %kp\n store i32 %ni, ptr %ip\n br label %lp\n")
|
||||
emith("none:\n ret i32 -1\n}\n")
|
||||
|
||||
# chars(s) -> []int : a fresh %LSlice of every code point, in order
|
||||
emith("define ptr @fn_uni_chars(ptr %s) {\n")
|
||||
emith("entry:\n %n = call i32 @fn_uni_len(ptr %s)\n %h = call ptr @malloc(i64 16)\n")
|
||||
emith(" %nz = zext i32 %n to i64\n %bytes = mul i64 %nz, 4\n %data = call ptr @malloc(i64 %bytes)\n")
|
||||
emith(" %d0 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 0\n store ptr %data, ptr %d0\n")
|
||||
emith(" %d1 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 1\n store i32 %n, ptr %d1\n")
|
||||
emith(" %d2 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 2\n store i32 %n, ptr %d2\n")
|
||||
emith(" %ip = alloca i32\n %kp = alloca i32\n %cp = alloca i32\n store i32 0, ptr %ip\n store i32 0, ptr %kp\n br label %lp\n")
|
||||
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
||||
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %done, label %go\n")
|
||||
emith("go:\n %ni = call i32 @fn_uni_decode(ptr %s, i32 %i, ptr %cp)\n %v = load i32, ptr %cp\n %k = load i32, ptr %kp\n")
|
||||
emith(" %slot = getelementptr i32, ptr %data, i32 %k\n store i32 %v, ptr %slot\n %k1 = add i32 %k, 1\n store i32 %k1, ptr %kp\n store i32 %ni, ptr %ip\n br label %lp\n")
|
||||
emith("done:\n ret ptr %h\n}\n")
|
||||
|
||||
# case map the whole string. %up != 0 -> uppercase, else lowercase. Decodes,
|
||||
# maps each code point, and re-encodes into a fresh buffer (worst case 4 bytes
|
||||
# per code point, though ASCII/Latin-1 mapping preserves byte length).
|
||||
emith("define ptr @fn_uni_case(ptr %s, i32 %up) {\n")
|
||||
emith("entry:\n %bl = call i64 @strlen(ptr %s)\n %cap0 = mul i64 %bl, 4\n %cap = add i64 %cap0, 4\n %out = call ptr @malloc(i64 %cap)\n")
|
||||
emith(" %ip = alloca i32\n %op = alloca i32\n %cp = alloca i32\n store i32 0, ptr %ip\n store i32 0, ptr %op\n br label %lp\n")
|
||||
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
||||
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %done, label %go\n")
|
||||
emith("go:\n %ni = call i32 @fn_uni_decode(ptr %s, i32 %i, ptr %cp)\n %v = load i32, ptr %cp\n")
|
||||
emith(" %mu = call i32 @fn_uni_upcp(i32 %v)\n %ml = call i32 @fn_uni_locp(i32 %v)\n %isup = icmp ne i32 %up, 0\n %m = select i1 %isup, i32 %mu, i32 %ml\n")
|
||||
emith(" %o = load i32, ptr %op\n %no = call i32 @fn_uni_encode(ptr %out, i32 %o, i32 %m)\n store i32 %no, ptr %op\n store i32 %ni, ptr %ip\n br label %lp\n")
|
||||
emith("done:\n %fo = load i32, ptr %op\n %endp = getelementptr i8, ptr %out, i32 %fo\n store i8 0, ptr %endp\n ret ptr %out\n}\n")
|
||||
|
||||
# truncate(s, n) -> the first n code points as a fresh string (never splits a
|
||||
# multibyte character). n <= 0 yields the empty string.
|
||||
emith("define ptr @fn_uni_truncate(ptr %s, i32 %n) {\n")
|
||||
emith("entry:\n %ip = alloca i32\n %kp = alloca i32\n %cp = alloca i32\n store i32 0, ptr %ip\n store i32 0, ptr %kp\n br label %lp\n")
|
||||
emith("lp:\n %k = load i32, ptr %kp\n %enough = icmp sge i32 %k, %n\n br i1 %enough, label %cut, label %chk\n")
|
||||
emith("chk:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
||||
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %cut, label %go\n")
|
||||
emith("go:\n %ni = call i32 @fn_uni_decode(ptr %s, i32 %i, ptr %cp)\n %k1 = add i32 %k, 1\n store i32 %k1, ptr %kp\n store i32 %ni, ptr %ip\n br label %lp\n")
|
||||
emith("cut:\n %len = load i32, ptr %ip\n %lz = zext i32 %len to i64\n %cap = add i64 %lz, 1\n %out = call ptr @malloc(i64 %cap)\n %lz2 = zext i32 %len to i64\n call ptr @memcpy(ptr %out, ptr %s, i64 %lz2)\n")
|
||||
emith(" %endp = getelementptr i8, ptr %out, i32 %len\n store i8 0, ptr %endp\n ret ptr %out\n}\n")
|
||||
|
||||
emit_uni_grapheme()
|
||||
}
|
||||
|
||||
# grapheme_len: an approximate UAX#29 count. A new cluster starts on each code
|
||||
# point except: a combining/variation "extend"; the code point after a ZWJ (so
|
||||
# ZWJ emoji sequences count as one); and the second regional indicator of a flag
|
||||
# pair. Simple state carried in allocas.
|
||||
function emit_uni_grapheme() -> void {
|
||||
emith("define i32 @fn_uni_grapheme_len(ptr %s) {\n")
|
||||
emith("entry:\n %ip = alloca i32\n %np = alloca i32\n %zp = alloca i32\n %rp = alloca i32\n %cp = alloca i32\n")
|
||||
emith(" store i32 0, ptr %ip\n store i32 0, ptr %np\n store i32 0, ptr %zp\n store i32 0, ptr %rp\n br label %lp\n")
|
||||
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
||||
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %done, label %go\n")
|
||||
emith("go:\n %ni = call i32 @fn_uni_decode(ptr %s, i32 %i, ptr %cp)\n %v = load i32, ptr %cp\n store i32 %ni, ptr %ip\n")
|
||||
# ZWJ (U+200D): extends the cluster and arms the join for the next code point
|
||||
emith(" %iszwj = icmp eq i32 %v, 8205\n br i1 %iszwj, label %zwj, label %notzwj\n")
|
||||
emith("zwj:\n %n0 = load i32, ptr %np\n %n0z = icmp eq i32 %n0, 0\n %n0b = zext i1 %n0z to i32\n %n0n = add i32 %n0, %n0b\n store i32 %n0n, ptr %np\n") # leading ZWJ still opens one cluster
|
||||
emith(" store i32 1, ptr %zp\n store i32 0, ptr %rp\n br label %lp\n")
|
||||
emith("notzwj:\n %n = load i32, ptr %np\n %first = icmp eq i32 %n, 0\n br i1 %first, label %open, label %cont\n")
|
||||
# first cluster
|
||||
emith("open:\n store i32 1, ptr %np\n store i32 0, ptr %zp\n %ri0 = call i32 @fn_uni_inrange(i32 %v, i32 127462, i32 127487)\n store i32 %ri0, ptr %rp\n br label %lp\n")
|
||||
emith("cont:\n %zj = load i32, ptr %zp\n %afterz = icmp ne i32 %zj, 0\n br i1 %afterz, label %joinz, label %chkext\n")
|
||||
# code point right after a ZWJ joins the current cluster
|
||||
emith("joinz:\n store i32 0, ptr %zp\n store i32 0, ptr %rp\n br label %lp\n")
|
||||
emith("chkext:\n %ext = call i32 @fn_uni_is_extend(i32 %v)\n %isext = icmp ne i32 %ext, 0\n br i1 %isext, label %joinext, label %chkri\n")
|
||||
emith("joinext:\n store i32 0, ptr %rp\n br label %lp\n")
|
||||
# regional indicator: joins only as the second of a pair
|
||||
emith("chkri:\n %ri = call i32 @fn_uni_inrange(i32 %v, i32 127462, i32 127487)\n %isri = icmp ne i32 %ri, 0\n %ropen = load i32, ptr %rp\n %ropenb = icmp ne i32 %ropen, 0\n %pair = and i1 %isri, %ropenb\n br i1 %pair, label %joinri, label %newcl\n")
|
||||
emith("joinri:\n store i32 0, ptr %rp\n br label %lp\n")
|
||||
emith("newcl:\n %nn = load i32, ptr %np\n %nn1 = add i32 %nn, 1\n store i32 %nn1, ptr %np\n store i32 0, ptr %zp\n %riset = select i1 %isri, i32 1, i32 0\n store i32 %riset, ptr %rp\n br label %lp\n")
|
||||
emith("done:\n %r = load i32, ptr %np\n ret i32 %r\n}\n")
|
||||
}
|
||||
164
selfhost/backend/stdlib/emit_uuid.ludic
Normal file
164
selfhost/backend/stdlib/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")
|
||||
}
|
||||
146
selfhost/backend/stdlib/emit_vector.ludic
Normal file
146
selfhost/backend/stdlib/emit_vector.ludic
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# emit_vector.ludic — the Vector.* namespace: a 2D vector value type. A Vector is
|
||||
# a pair of Q16.16 fixed components (x, y) packed into a single i64 — x in the
|
||||
# high 32 bits, y in the low 32 — so it is a true by-value type (assignment
|
||||
# copies, no heap allocation) that lives in one register. All arithmetic is the
|
||||
# same deterministic integer fixed-point the rest of the runtime uses, reusing
|
||||
# fx_mul_code / fx_div_code / fx_lerp_code and the @fn_fx_* prelude. llty maps the
|
||||
# `Vector` type to i64 (see emit_core.ludic).
|
||||
#
|
||||
# NOTE: helper results are bound to a `let` before interpolation — a function
|
||||
# call inside a backtick `{...}` hole would nest backticks and break.
|
||||
|
||||
# pack two fixed i32 codes (x, y) into the i64 Vector representation -> i64 code
|
||||
function vec_pack(x: pointer, y: pointer) -> pointer {
|
||||
let xe = emit_bind(`sext i32 {x} to i64`)
|
||||
let xs = emit_bind(`shl i64 {xe}, 32`)
|
||||
let ye = emit_bind(`zext i32 {y} to i64`)
|
||||
return emit_bind(`or i64 {xs}, {ye}`)
|
||||
}
|
||||
|
||||
# the x component (high 32 bits) of an i64 Vector code -> i32 fixed code
|
||||
function vec_x(v: pointer) -> pointer {
|
||||
let s = emit_bind(`lshr i64 {v}, 32`)
|
||||
return emit_bind(`trunc i64 {s} to i32`)
|
||||
}
|
||||
|
||||
# the y component (low 32 bits) of an i64 Vector code -> i32 fixed code
|
||||
function vec_y(v: pointer) -> pointer {
|
||||
return emit_bind(`trunc i64 {v} to i32`)
|
||||
}
|
||||
|
||||
function is_vector_ns(meth: pointer) -> bool {
|
||||
if (meth == "make") or (meth == "zero") or (meth == "x") or (meth == "y") { return true }
|
||||
if (meth == "add") or (meth == "sub") or (meth == "scale") or (meth == "dot") { return true }
|
||||
if (meth == "length") or (meth == "distance") or (meth == "normalize") or (meth == "lerp") { return true }
|
||||
if (meth == "rotate") or (meth == "angle") or (meth == "from_angle") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
function emit_vector_ns(meth: pointer, e: Node) -> Val {
|
||||
if (meth == "zero") { # the origin, (0, 0)
|
||||
return val("0", "Vector")
|
||||
}
|
||||
if (meth == "make") { # make(x, y: fixed) -> Vector
|
||||
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1])
|
||||
return val(vec_pack(x.code, y.code), "Vector")
|
||||
}
|
||||
if (meth == "x") { # the x component -> fixed
|
||||
let v = emit_expr(e.kids[0])
|
||||
return val(vec_x(v.code), "fixed")
|
||||
}
|
||||
if (meth == "y") { # the y component -> fixed
|
||||
let v = emit_expr(e.kids[0])
|
||||
return val(vec_y(v.code), "fixed")
|
||||
}
|
||||
if (meth == "add") { # component-wise a + b
|
||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
||||
let ax = vec_x(a.code); let ay = vec_y(a.code); let bx = vec_x(b.code); let by = vec_y(b.code)
|
||||
let sx = emit_bind(`add i32 {ax}, {bx}`)
|
||||
let sy = emit_bind(`add i32 {ay}, {by}`)
|
||||
return val(vec_pack(sx, sy), "Vector")
|
||||
}
|
||||
if (meth == "sub") { # component-wise a - b
|
||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
||||
let ax = vec_x(a.code); let ay = vec_y(a.code); let bx = vec_x(b.code); let by = vec_y(b.code)
|
||||
let sx = emit_bind(`sub i32 {ax}, {bx}`)
|
||||
let sy = emit_bind(`sub i32 {ay}, {by}`)
|
||||
return val(vec_pack(sx, sy), "Vector")
|
||||
}
|
||||
if (meth == "scale") { # v * s (s: fixed)
|
||||
let v = emit_expr(e.kids[0]); let s = emit_expr(e.kids[1])
|
||||
let vx = vec_x(v.code); let vy = vec_y(v.code)
|
||||
let sx = fx_mul_code(vx, s.code)
|
||||
let sy = fx_mul_code(vy, s.code)
|
||||
return val(vec_pack(sx, sy), "Vector")
|
||||
}
|
||||
if (meth == "dot") { # ax*bx + ay*by -> fixed
|
||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
||||
let ax = vec_x(a.code); let ay = vec_y(a.code); let bx = vec_x(b.code); let by = vec_y(b.code)
|
||||
let px = fx_mul_code(ax, bx); let py = fx_mul_code(ay, by)
|
||||
return val(emit_bind(`add i32 {px}, {py}`), "fixed")
|
||||
}
|
||||
if (meth == "length") { # sqrt(x*x + y*y) -> fixed
|
||||
g_uses_mathrt = true
|
||||
let v = emit_expr(e.kids[0])
|
||||
let vx = vec_x(v.code); let vy = vec_y(v.code)
|
||||
let xx = fx_mul_code(vx, vx); let yy = fx_mul_code(vy, vy)
|
||||
let s = emit_bind(`add i32 {xx}, {yy}`)
|
||||
return val(emit_bind(`call i32 @fn_fx_sqrt(i32 {s})`), "fixed")
|
||||
}
|
||||
if (meth == "distance") { # length(a - b) -> fixed
|
||||
g_uses_mathrt = true
|
||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
||||
let ax = vec_x(a.code); let ay = vec_y(a.code); let bx = vec_x(b.code); let by = vec_y(b.code)
|
||||
let dx = emit_bind(`sub i32 {ax}, {bx}`)
|
||||
let dy = emit_bind(`sub i32 {ay}, {by}`)
|
||||
let xx = fx_mul_code(dx, dx); let yy = fx_mul_code(dy, dy)
|
||||
let s = emit_bind(`add i32 {xx}, {yy}`)
|
||||
return val(emit_bind(`call i32 @fn_fx_sqrt(i32 {s})`), "fixed")
|
||||
}
|
||||
if (meth == "normalize") { # v / length(v); the zero vector maps to itself
|
||||
g_uses_mathrt = true
|
||||
let v = emit_expr(e.kids[0])
|
||||
let vx = vec_x(v.code); let vy = vec_y(v.code)
|
||||
let xx = fx_mul_code(vx, vx); let yy = fx_mul_code(vy, vy)
|
||||
let s = emit_bind(`add i32 {xx}, {yy}`)
|
||||
let len = emit_bind(`call i32 @fn_fx_sqrt(i32 {s})`)
|
||||
let zero = emit_bind(`icmp eq i32 {len}, 0`)
|
||||
let denom = emit_bind(`select i1 {zero}, i32 65536, i32 {len}`) # avoid divide-by-zero
|
||||
let inv = fx_div_code("65536", denom)
|
||||
let nx = fx_mul_code(vx, inv); let ny = fx_mul_code(vy, inv)
|
||||
return val(vec_pack(nx, ny), "Vector")
|
||||
}
|
||||
if (meth == "rotate") { # rotate by angle (radians, fixed)
|
||||
g_uses_mathrt = true
|
||||
let v = emit_expr(e.kids[0]); let ang = emit_expr(e.kids[1])
|
||||
let sn = emit_bind(`call i32 @fn_fx_sin(i32 {ang.code})`)
|
||||
let ca = emit_bind(`add i32 {ang.code}, 102944`) # cos(a) = sin(a + pi/2)
|
||||
let cs = emit_bind(`call i32 @fn_fx_sin(i32 {ca})`)
|
||||
let vx = vec_x(v.code); let vy = vec_y(v.code)
|
||||
let xc = fx_mul_code(vx, cs); let ys = fx_mul_code(vy, sn)
|
||||
let xs = fx_mul_code(vx, sn); let yc = fx_mul_code(vy, cs)
|
||||
let rx = emit_bind(`sub i32 {xc}, {ys}`) # x*cos - y*sin
|
||||
let ry = emit_bind(`add i32 {xs}, {yc}`) # x*sin + y*cos
|
||||
return val(vec_pack(rx, ry), "Vector")
|
||||
}
|
||||
if (meth == "angle") { # atan2(y, x) -> fixed radians
|
||||
g_uses_mathrt = true
|
||||
let v = emit_expr(e.kids[0])
|
||||
let vx = vec_x(v.code); let vy = vec_y(v.code)
|
||||
return val(emit_bind(`call i32 @fn_fx_atan2(i32 {vy}, i32 {vx})`), "fixed")
|
||||
}
|
||||
if (meth == "from_angle") { # unit vector at angle a: (cos a, sin a)
|
||||
g_uses_mathrt = true
|
||||
let ang = emit_expr(e.kids[0])
|
||||
let sn = emit_bind(`call i32 @fn_fx_sin(i32 {ang.code})`)
|
||||
let ca = emit_bind(`add i32 {ang.code}, 102944`)
|
||||
let cs = emit_bind(`call i32 @fn_fx_sin(i32 {ca})`)
|
||||
return val(vec_pack(cs, sn), "Vector")
|
||||
}
|
||||
# lerp(a, b, t: fixed) -> Vector — component-wise linear interpolation
|
||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); let t = emit_expr(e.kids[2])
|
||||
let ax = vec_x(a.code); let ay = vec_y(a.code); let bx = vec_x(b.code); let by = vec_y(b.code)
|
||||
let lx = fx_lerp_code(ax, bx, t.code)
|
||||
let ly = fx_lerp_code(ay, by, t.code)
|
||||
return val(vec_pack(lx, ly), "Vector")
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue