kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 4b4f64638f1b09154ac2edb68f1e6bf987db2ab1
parent c7893199344fdf3de97ea5730ba3b272329a92b8
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon,  1 Jun 2026 17:15:07 -0700

arch/c_target: lower 128-bit runtime-helper calls to native __int128

The portable C backend re-expresses the i128 helper calls that src/cg/arith.c
emits -- compiler-rt names (__multi3/__divti3/__ashlti3/...) and cfree-specific
ones (__cfree_addti3/__cfree_cmpti2/__cfree_sext64ti/...) -- as native
__int128 operators. The generated source then needs neither cfree-rt nor the
host compiler-rt builtins. cfree_cmpti2/ucmpti2 reproduce the -1/0/1 sign
convention with nested ternaries.

Diffstat:
Msrc/arch/c_target/c_emit.c | 130+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 130 insertions(+), 0 deletions(-)

diff --git a/src/arch/c_target/c_emit.c b/src/arch/c_target/c_emit.c @@ -2043,8 +2043,138 @@ static void c_emit_call_arg(CTarget* t, const CgType* fty, const CGCallDesc* d, c_emit_operand(t, c_op_local(d->args[i], ty)); } +/* Render call operand `i`, optionally cast to unsigned __int128 first (used by + * the unsigned i128 helpers below). */ +static void c_emit_ti_operand(CTarget* t, const CgType* fty, + const CGCallDesc* d, u32 i, int as_unsigned) { + if (as_unsigned) cbuf_puts(&t->body, "(unsigned __int128)("); + c_emit_call_arg(t, fty, d, i); + if (as_unsigned) cbuf_puts(&t->body, ")"); +} + +/* The CG arithmetic layer (src/cg/arith.c) lowers 128-bit integer operations + * into calls to runtime helpers: compiler-rt-standard names for mul/div/mod/ + * shift/neg, and __cfree_*-prefixed ones for add/sub/bitwise/not/extend/compare + * — operations that real toolchains inline (so have no compiler-rt symbol), or + * that use cfree's own -1/0/1 compare convention. A C compiler has native + * __int128, so the portable C backend re-expresses every such call as a native + * operator: the emitted source then needs neither cfree's runtime nor the host's + * compiler-rt builtins. Returns 1 if it emitted the intrinsic, 0 to fall + * through to a normal call. */ +static int c_try_emit_ti_intrinsic(CTarget* t, const CgType* fty, + const CGCallDesc* d) { + if (d->callee.kind != OPK_GLOBAL) return 0; + const char* n = c_sym_name(t, d->callee.v.global.sym); + if (!n) return 0; + + /* Symmetric binary ops over two i128 operands: (a) OP (b). `u` casts both + * operands to unsigned __int128 first (unsigned divide/remainder). */ + static const struct { + const char* name; + const char* op; + int u; + } kBin[] = { + {"__cfree_addti3", "+", 0}, {"__cfree_subti3", "-", 0}, + {"__multi3", "*", 0}, {"__cfree_andti3", "&", 0}, + {"__cfree_orti3", "|", 0}, {"__cfree_xorti3", "^", 0}, + {"__divti3", "/", 0}, {"__modti3", "%", 0}, + {"__udivti3", "/", 1}, {"__umodti3", "%", 1}, + }; + if (d->nargs == 2) { + for (size_t i = 0; i < sizeof kBin / sizeof kBin[0]; ++i) { + if (strcmp(n, kBin[i].name) != 0) continue; + cbuf_puts(&t->body, "("); + c_emit_ti_operand(t, fty, d, 0, kBin[i].u); + cbuf_puts(&t->body, " "); + cbuf_puts(&t->body, kBin[i].op); + cbuf_puts(&t->body, " "); + c_emit_ti_operand(t, fty, d, 1, kBin[i].u); + cbuf_puts(&t->body, ")"); + return 1; + } + } + + /* Shifts: (value) OP (count). The count is a plain int operand, never cast; + * logical right shift takes an unsigned value. */ + if (d->nargs == 2) { + const char* sop = NULL; + int uval = 0; + if (strcmp(n, "__ashlti3") == 0) { + sop = "<<"; + } else if (strcmp(n, "__ashrti3") == 0) { + sop = ">>"; + } else if (strcmp(n, "__lshrti3") == 0) { + sop = ">>"; + uval = 1; + } + if (sop) { + cbuf_puts(&t->body, "("); + c_emit_ti_operand(t, fty, d, 0, uval); + cbuf_puts(&t->body, " "); + cbuf_puts(&t->body, sop); + cbuf_puts(&t->body, " "); + c_emit_call_arg(t, fty, d, 1); + cbuf_puts(&t->body, ")"); + return 1; + } + } + + /* Unary ops and i64 -> i128 widening. */ + if (d->nargs == 1) { + const char* uop = NULL; + if (strcmp(n, "__negti2") == 0) + uop = "-"; + else if (strcmp(n, "__cfree_notti3") == 0) + uop = "~"; + if (uop) { + cbuf_puts(&t->body, "("); + cbuf_puts(&t->body, uop); + cbuf_puts(&t->body, "("); + c_emit_call_arg(t, fty, d, 0); + cbuf_puts(&t->body, "))"); + return 1; + } + if (strcmp(n, "__cfree_sext64ti") == 0) { + cbuf_puts(&t->body, "((__int128)(int64_t)("); + c_emit_call_arg(t, fty, d, 0); + cbuf_puts(&t->body, "))"); + return 1; + } + if (strcmp(n, "__cfree_zext64ti") == 0) { + cbuf_puts(&t->body, "((unsigned __int128)(uint64_t)("); + c_emit_call_arg(t, fty, d, 0); + cbuf_puts(&t->body, "))"); + return 1; + } + } + + /* Compare: cfree's helpers return -1/0/1 (the CG layer compares the result + * against zero), so reproduce that sign convention with native operators. */ + if (d->nargs == 2) { + int usign = -1; + if (strcmp(n, "__cfree_cmpti2") == 0) + usign = 0; + else if (strcmp(n, "__cfree_ucmpti2") == 0) + usign = 1; + if (usign >= 0) { + cbuf_puts(&t->body, "("); + c_emit_ti_operand(t, fty, d, 0, usign); + cbuf_puts(&t->body, " < "); + c_emit_ti_operand(t, fty, d, 1, usign); + cbuf_puts(&t->body, " ? -1 : ("); + c_emit_ti_operand(t, fty, d, 0, usign); + cbuf_puts(&t->body, " > "); + c_emit_ti_operand(t, fty, d, 1, usign); + cbuf_puts(&t->body, " ? 1 : 0))"); + return 1; + } + } + return 0; +} + static void c_emit_call_expr(CTarget* t, const CgType* fty, const CGCallDesc* d, const CfreeCgTypeId* result_types) { + if (c_try_emit_ti_intrinsic(t, fty, d)) return; if (d->callee.kind == OPK_GLOBAL) { c_ensure_forward_decl_with_results(t, d->callee.v.global.sym, d->fn_type, d->nresults > 1 ? result_types : NULL,