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:
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,