kit

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

commit 37ad8f0e64ce819ad11ede23f6f2ff7e97a696fe
parent d364b563010b67b4db8b36087a75ab7a850db57a
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 20 May 2026 08:21:56 -0700

Enable runtime printf float formatting

Diffstat:
Mrt/include/stdio.h | 2++
Mrt/lib/stdio/printf.c | 325+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Msrc/cg/call.c | 38++++++++++++++++++++++++++++++++++++++
Msrc/cg/memory.c | 17+++++++++++++++++
Mtest/parse/CORPUS.md | 2++
Atest/parse/cases/cg_postdec_while_count.c | 11+++++++++++
Atest/parse/cases/cg_postdec_while_count.expected | 1+
Atest/parse/cases/cg_x64_fp_format_fixed_digits.c | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/parse/cases/cg_x64_fp_format_fixed_digits.expected | 1+
Mtest/rt/cases/freestanding_lib.c | 45++++++++++++++++++++++++++++++++++++++-------
10 files changed, 476 insertions(+), 38 deletions(-)

diff --git a/rt/include/stdio.h b/rt/include/stdio.h @@ -11,5 +11,7 @@ int snprintf(char* s, size_t n, const char* fmt, ...); int vsnprintf(char* s, size_t n, const char* fmt, va_list ap); int sprintf(char* s, const char* fmt, ...); int vsprintf(char* s, const char* fmt, va_list ap); +int fctprintf(void (*out)(char ch, void* arg), void* arg, + const char* fmt, ...); #endif diff --git a/rt/lib/stdio/printf.c b/rt/lib/stdio/printf.c @@ -5,34 +5,28 @@ // Floating-point formats are intentionally omitted until cfree's backends can // compile the original mpaland conversion path portably. // -// TODO: -// - Re-enable mpaland float/exponential formats once all cfree backends can -// lower the formatter's FP-to-integer conversion path. -// - Re-enable the mpaland output-callback form after the x64 backend reliably -// handles the original indirect output dispatch and larger helper signatures. -// Upstream shape: -// typedef void (*out_fct_type)(char ch, void* buffer, -// size_t idx, size_t maxlen); -// typedef struct { void (*fct)(char ch, void* arg); void* arg; } -// out_fct_wrap_type; -// int fctprintf(void (*out)(char ch, void* arg), void* arg, -// const char* format, ...); -// fctprintf creates an out_fct_wrap_type on its stack and calls the shared -// formatter with an _out_fct adapter that invokes wrap->fct(ch, wrap->arg). //===----------------------------------------------------------------------===// #include <stdarg.h> #include <stddef.h> #include <stdint.h> +typedef void (*CfreePrintfCallback)(char ch, void* arg); + typedef struct { char* buf; size_t cap; size_t len; + CfreePrintfCallback fct; + void* arg; } CfreePrintfOut; static void cfree_out_ch(CfreePrintfOut* out, char ch) { - if (out->cap && out->len + 1U < out->cap) out->buf[out->len] = ch; + if (out->fct) { + out->fct(ch, out->arg); + } else if (out->cap && out->len + 1U < out->cap) { + out->buf[out->len] = ch; + } out->len++; } @@ -86,6 +80,228 @@ static void cfree_out_uint(CfreePrintfOut* out, unsigned long long value, } } +static void cfree_out_padded_string(CfreePrintfOut* out, const char* s, + unsigned len, int neg, int plus, + int space, int left, int zero, + unsigned width) { + unsigned prefix = (neg || plus || space) ? 1U : 0U; + char sign = neg ? '-' : (plus ? '+' : ' '); + char pad = (zero && !left) ? '0' : ' '; + + if (!left && pad == ' ' && width > len + prefix) { + cfree_out_repeat(out, ' ', width - len - prefix); + } + if (prefix) cfree_out_ch(out, sign); + if (!left && pad == '0' && width > len + prefix) { + cfree_out_repeat(out, '0', width - len - prefix); + } + for (unsigned i = 0; i < len; i++) cfree_out_ch(out, s[i]); + if (left && width > len + prefix) { + cfree_out_repeat(out, ' ', width - len - prefix); + } +} + +static int cfree_double_negative(double v) { + union { + double d; + uint64_t u; + } bits; + bits.d = v; + return (int)(bits.u >> 63); +} + +static double cfree_double_abs(double v) { return v < 0.0 ? -v : v; } + +static double cfree_pow10_u(unsigned n) { + double value = 1.0; + while (n-- > 0) value *= 10.0; + return value; +} + +static void cfree_format_fixed_abs(CfreePrintfOut* out, double value, + unsigned prec, int alt) { + double rounded; + double pow10 = 1.0; + int emitted = 0; + + if (prec > 18U) prec = 18U; + rounded = value + 0.5 / cfree_pow10_u(prec); + + if (rounded >= 1.0) { + for (;;) { + double next = pow10 * 10.0; + if (!(next > pow10) || next > rounded) break; + pow10 = next; + } + } + + while (pow10 >= 1.0) { + int digit = (int)(rounded / pow10); + if (digit < 0) digit = 0; + if (digit > 9) digit = 9; + cfree_out_ch(out, (char)('0' + digit)); + rounded -= (double)digit * pow10; + pow10 /= 10.0; + emitted = 1; + } + if (!emitted) cfree_out_ch(out, '0'); + + if (prec != 0 || alt) cfree_out_ch(out, '.'); + for (unsigned i = 0; i < prec; i++) { + int digit; + rounded *= 10.0; + digit = (int)rounded; + if (digit < 0) digit = 0; + if (digit > 9) digit = 9; + cfree_out_ch(out, (char)('0' + digit)); + rounded -= (double)digit; + } +} + +static int cfree_decimal_exp(double value) { + int exp = 0; + if (value == 0.0) return 0; + while (value >= 10.0) { + value /= 10.0; + exp++; + } + while (value < 1.0) { + value *= 10.0; + exp--; + } + return exp; +} + +static double cfree_normalize_decimal(double value, int* exp) { + *exp = 0; + if (value == 0.0) return 0.0; + while (value >= 10.0) { + value /= 10.0; + (*exp)++; + } + while (value < 1.0) { + value *= 10.0; + (*exp)--; + } + return value; +} + +static void cfree_format_exp_abs(CfreePrintfOut* out, double value, + unsigned prec, int upper, int alt) { + int exp; + unsigned e; + char digits[12]; + unsigned len = 0; + double norm; + + if (prec > 18U) prec = 18U; + norm = cfree_normalize_decimal(value, &exp); + if (norm + 0.5 / cfree_pow10_u(prec) >= 10.0) { + norm /= 10.0; + exp++; + } + + cfree_format_fixed_abs(out, norm, prec, alt); + cfree_out_ch(out, upper ? 'E' : 'e'); + if (exp < 0) { + cfree_out_ch(out, '-'); + e = (unsigned)-exp; + } else { + cfree_out_ch(out, '+'); + e = (unsigned)exp; + } + + do { + digits[len++] = (char)('0' + (e % 10U)); + e /= 10U; + } while (e != 0U); + while (len < 2U) digits[len++] = '0'; + while (len > 0) cfree_out_ch(out, digits[--len]); +} + +static void cfree_trim_float(char* s, unsigned* len, int alt) { + unsigned end = *len; + unsigned exp_start = end; + unsigned trim_end; + unsigned dot_pos = end; + unsigned i; + + if (alt) return; + for (i = 0; i < end; i++) { + if (s[i] == 'e' || s[i] == 'E') { + exp_start = i; + break; + } + } + trim_end = exp_start; + for (i = 0; i < exp_start; i++) { + if (s[i] == '.') { + dot_pos = i; + break; + } + } + if (dot_pos == end) return; + + while (trim_end > dot_pos + 1U && s[trim_end - 1U] == '0') trim_end--; + if (trim_end > dot_pos && s[trim_end - 1U] == '.') trim_end--; + + if (exp_start < end) { + unsigned src = exp_start; + while (src < end) s[trim_end++] = s[src++]; + } + *len = trim_end; + s[*len] = '\0'; +} + +static void cfree_format_float(CfreePrintfOut* out, double value, char spec, + int left, int zero, int plus, int space, + int alt, unsigned width, int has_prec, + unsigned prec) { + char tmp[384]; + CfreePrintfOut tmp_out; + unsigned len; + int neg = cfree_double_negative(value); + double mag = cfree_double_abs(value); + + if (!has_prec) prec = 6U; + tmp_out.buf = tmp; + tmp_out.cap = sizeof(tmp); + tmp_out.len = 0U; + tmp_out.fct = NULL; + tmp_out.arg = NULL; + + if (value != value) { + cfree_out_string(&tmp_out, (spec >= 'A' && spec <= 'Z') ? "NAN" : "nan", + 0, 0, 0, 0); + neg = 0; + } else if (mag > 1.7976931348623157e308) { + cfree_out_string(&tmp_out, (spec >= 'A' && spec <= 'Z') ? "INF" : "inf", + 0, 0, 0, 0); + } else if (spec == 'e' || spec == 'E') { + cfree_format_exp_abs(&tmp_out, mag, prec, spec == 'E', alt); + } else if (spec == 'g' || spec == 'G') { + int exp = cfree_decimal_exp(mag); + if (prec == 0U) prec = 1U; + if (exp < -4 || exp >= (int)prec) { + cfree_format_exp_abs(&tmp_out, mag, prec - 1U, spec == 'G', alt); + } else { + unsigned fprec = exp >= 0 ? prec - (unsigned)exp - 1U + : prec + (unsigned)(-exp) - 1U; + cfree_format_fixed_abs(&tmp_out, mag, fprec, alt); + } + } else { + cfree_format_fixed_abs(&tmp_out, mag, prec, alt); + } + + len = tmp_out.len < sizeof(tmp) ? (unsigned)tmp_out.len + : (unsigned)sizeof(tmp) - 1U; + tmp[len] = '\0'; + if (spec == 'g' || spec == 'G') cfree_trim_float(tmp, &len, alt); + if (value != value) neg = 0; + cfree_out_padded_string(out, tmp, len, neg, plus && !neg, space && !neg, + left, zero, width); +} + static int cfree_is_digit(char ch) { return ch >= '0' && ch <= '9'; } static unsigned cfree_parse_uint(const char** p) { @@ -96,16 +312,15 @@ static unsigned cfree_parse_uint(const char** p) { return value; } -static int cfree_vsnprintf_impl(char* s, size_t n, const char* fmt, - va_list ap) { - CfreePrintfOut out; - out.buf = s; - out.cap = s ? n : 0U; - out.len = 0U; +static int cfree_vprintf_impl(CfreePrintfOut* out, const char* fmt, + va_list ap) { while (*fmt) { int left = 0; int zero = 0; + int plus = 0; + int space = 0; + int alt = 0; int long_mod = 0; int long_long_mod = 0; int has_prec = 0; @@ -113,12 +328,12 @@ static int cfree_vsnprintf_impl(char* s, size_t n, const char* fmt, unsigned prec = 0; if (*fmt != '%') { - cfree_out_ch(&out, *fmt++); + cfree_out_ch(out, *fmt++); continue; } fmt++; if (*fmt == '%') { - cfree_out_ch(&out, *fmt++); + cfree_out_ch(out, *fmt++); continue; } @@ -129,6 +344,15 @@ static int cfree_vsnprintf_impl(char* s, size_t n, const char* fmt, } else if (*fmt == '0') { zero = 1; fmt++; + } else if (*fmt == '+') { + plus = 1; + fmt++; + } else if (*fmt == ' ') { + space = 1; + fmt++; + } else if (*fmt == '#') { + alt = 1; + fmt++; } else { break; } @@ -165,7 +389,7 @@ static int cfree_vsnprintf_impl(char* s, size_t n, const char* fmt, v = va_arg(ap, int); } mag = v < 0 ? 0ULL - (unsigned long long)v : (unsigned long long)v; - cfree_out_uint(&out, mag, 10U, 0, v < 0, left, zero, width); + cfree_out_uint(out, mag, 10U, 0, v < 0, left, zero, width); break; } case 'u': @@ -181,34 +405,57 @@ static int cfree_vsnprintf_impl(char* s, size_t n, const char* fmt, } else { v = va_arg(ap, unsigned int); } - cfree_out_uint(&out, v, base, *fmt == 'X', 0, left, zero, width); + cfree_out_uint(out, v, base, *fmt == 'X', 0, left, zero, width); break; } case 'p': { uintptr_t v = (uintptr_t)va_arg(ap, void*); - cfree_out_string(&out, "0x", 0, 0, 0, 0); - cfree_out_uint(&out, (unsigned long long)v, 16U, 0, 0, 0, 0, 0); + cfree_out_string(out, "0x", 0, 0, 0, 0); + cfree_out_uint(out, (unsigned long long)v, 16U, 0, 0, 0, 0, 0); break; } case 'c': - cfree_out_ch(&out, (char)va_arg(ap, int)); + cfree_out_ch(out, (char)va_arg(ap, int)); break; case 's': - cfree_out_string(&out, va_arg(ap, const char*), left, width, has_prec, + cfree_out_string(out, va_arg(ap, const char*), left, width, has_prec, prec); break; + case 'f': + case 'F': + case 'e': + case 'E': + case 'g': + case 'G': + cfree_format_float(out, va_arg(ap, double), *fmt, left, zero, plus, + space, alt, width, has_prec, prec); + break; default: - if (*fmt) cfree_out_ch(&out, *fmt); + if (*fmt) cfree_out_ch(out, *fmt); break; } if (*fmt) fmt++; } + return (int)out->len; +} + +static int cfree_vsnprintf_impl(char* s, size_t n, const char* fmt, + va_list ap) { + CfreePrintfOut out; + int rc; + out.buf = s; + out.cap = s ? n : 0U; + out.len = 0U; + out.fct = NULL; + out.arg = NULL; + + rc = cfree_vprintf_impl(&out, fmt, ap); if (out.cap) { size_t pos = out.len < out.cap ? out.len : out.cap - 1U; out.buf[pos] = '\0'; } - return (int)out.len; + return rc; } int vsnprintf(char* s, size_t n, const char* fmt, va_list ap) { @@ -241,3 +488,19 @@ int sprintf(char* s, const char* fmt, ...) { va_end(ap); return rc; } + +int fctprintf(void (*out_fct)(char ch, void* arg), void* arg, + const char* fmt, ...) { + CfreePrintfOut out; + va_list ap; + int rc; + out.buf = NULL; + out.cap = 0U; + out.len = 0U; + out.fct = out_fct; + out.arg = arg; + va_start(ap, fmt); + rc = cfree_vprintf_impl(&out, fmt, ap); + va_end(ap); + return rc; +} diff --git a/src/cg/call.c b/src/cg/call.c @@ -77,6 +77,42 @@ void api_push_call_result(CfreeCg* g, Operand ret_storage, } } +static void api_spill_call_clobbered_stack(CfreeCg* g, const CGCallDesc* d) { + CGTarget* T = g->target; + u32 masks[3]; + if (cg_simple_regalloc_is_virtual(&g->regalloc)) return; + if (!T->call_clobber_mask) return; + for (u32 c = 0; c < 3u; ++c) { + masks[c] = T->call_clobber_mask(T, d, (RegClass)c); + } + for (u32 i = 0; i < g->sp; ++i) { + ApiSValue* sv = &g->stack[i]; + u8 cls; + Reg reg; + FrameSlot slot; + Operand src; + if (sv->res != RES_REG) continue; + cls = api_class_of_sv(sv); + if (cls >= 3u) continue; + reg = api_reg_of_sv(sv); + if (reg == (Reg)REG_NONE || reg >= 32u) continue; + if ((masks[cls] & (1u << reg)) == 0) continue; + if (sv->pinned) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: call clobbers pinned register %u class %u", + (unsigned)reg, (unsigned)cls); + continue; + } + slot = api_take_spill_slot(g, cls); + src = api_op_reg(reg, api_owned_reg_type(g, sv)); + T->spill_reg(T, src, slot, api_mem_for_spill(g, sv)); + api_free_reg(g, reg, cls); + api_set_owned_reg(sv, (Reg)REG_NONE); + sv->spill_slot = slot; + sv->res = RES_SPILLED; + } +} + void cfree_cg_call(CfreeCg* g, uint32_t nargs, CfreeCgTypeId fn_type, CfreeCgCallAttrs attrs) { CGTarget* T; @@ -133,6 +169,7 @@ void cfree_cg_call(CfreeCg* g, uint32_t nargs, CfreeCgTypeId fn_type, } if (tail) api_regalloc_finish(g); + if (!tail) api_spill_call_clobbered_stack(g, &desc); T->call(T, &desc); api_release_call_args(g, avs, nargs); @@ -248,6 +285,7 @@ void api_call_symbol_common(CfreeCg* g, CfreeCgSym sym, uint32_t nargs, desc.ret.storage = api_op_imm(0, builtin_id(CFREE_CG_BUILTIN_VOID)); } if (tail) api_regalloc_finish(g); + if (!tail) api_spill_call_clobbered_stack(g, &desc); T->call(T, &desc); api_release_call_args(g, avs, nargs); if (has_result) { diff --git a/src/cg/memory.c b/src/cg/memory.c @@ -530,6 +530,23 @@ void cfree_cg_dup(CfreeCg* g) { api_ensure_reg(g, top); v = *top; if (v.res != RES_REG) { + if (v.res == RES_FIXED_REG && !api_is_lvalue_sv(&v) && + v.op.kind == OPK_REG) { + ty = api_owned_reg_type(g, &v); + r = api_alloc_reg_or_spill(g, api_class_of_sv(&v), ty); + dst = api_op_reg(r, ty); + g->target->copy(g->target, dst, + api_op_reg((Reg)api_reg_of_sv(&v), ty)); + dup = v; + api_set_owned_reg(&dup, r); + dup.res = RES_REG; + dup.pinned = 0; + dup.spill_slot = FRAME_SLOT_NONE; + dup.source_local = CFREE_CG_LOCAL_NONE; + g->stack[g->sp - 1] = dup; + api_push(g, v); + return; + } api_push(g, v); return; } diff --git a/test/parse/CORPUS.md b/test/parse/CORPUS.md @@ -169,6 +169,8 @@ here for completeness once they're real cases. | `6_5_31_subscript_commute`| ★ | `int a[5]={0,0,42,0,0}; return 2[a];` | 42 | | `6_5_32_string_subscript` | ★ | `return "*"[0];` | 42 | | `6_5_33_regalloc_spill` | ★ | 12-arg `sum12(x1+0, ..., x12+0)` — exceeds the 10-INT scratch pool, exercises `spill_reg`/`reload_reg` and the cg_call avs-in-flight fallback | 78 | +| `cg_postdec_while_count` | ★ | `while (n-- > 0)` must test the old value after duplicating a register-backed local | 42 | +| `cg_x64_fp_format_fixed_digits` | ★ | fixed decimal digit extraction from `12.375` with a helper call and output calls; repro for x64 caller-saved FP values and post-decrement rounding | 42 | | `6_5_36_fp_arith` | ★ | `(a + b) / b * c - 36.0` over `double` — pins parser dispatch to `BO_FADD`/`FSUB`/`FMUL`/`FDIV` | 42 | | `6_5_37_fp_int_promote` | ★ | `int + double` — usual arithmetic conversion promotes the int side to `double` before BO_FADD | 42 | | `6_5_38_fp_float_widen` | ★ | `float + double` — float widens to double before BO_FADD | 42 | diff --git a/test/parse/cases/cg_postdec_while_count.c b/test/parse/cases/cg_postdec_while_count.c @@ -0,0 +1,11 @@ +static unsigned pow10_count(unsigned n) { + unsigned value = 1; + while (n-- > 0) value *= 10; + return value; +} + +int test_main(void) { + unsigned got = pow10_count(2); + if (got != 100U) return (int)got; + return 42; +} diff --git a/test/parse/cases/cg_postdec_while_count.expected b/test/parse/cases/cg_postdec_while_count.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/cg_x64_fp_format_fixed_digits.c b/test/parse/cases/cg_x64_fp_format_fixed_digits.c @@ -0,0 +1,72 @@ +typedef unsigned long size_t; + +typedef struct { + char* buf; + size_t cap; + size_t len; +} Out; + +static void out_ch(Out* out, char ch) { + if (out->cap && out->len + 1U < out->cap) out->buf[out->len] = ch; + out->len++; +} + +static double pow10_u(unsigned n) { + double value = 1.0; + while (n-- > 0) value *= 10.0; + return value; +} + +static void format_fixed_abs(Out* out, double value, unsigned prec) { + double rounded; + double pow10 = 1.0; + int emitted = 0; + + rounded = value + 0.5 / pow10_u(prec); + if (rounded >= 1.0) { + for (;;) { + double next = pow10 * 10.0; + if (!(next > pow10) || next > rounded) break; + pow10 = next; + } + } + + while (pow10 >= 1.0) { + int digit = (int)(rounded / pow10); + if (digit < 0) digit = 0; + if (digit > 9) digit = 9; + out_ch(out, (char)('0' + digit)); + rounded -= (double)digit * pow10; + pow10 /= 10.0; + emitted = 1; + } + if (!emitted) out_ch(out, '0'); + + out_ch(out, '.'); + for (unsigned i = 0; i < prec; i++) { + int digit; + rounded *= 10.0; + digit = (int)rounded; + if (digit < 0) digit = 0; + if (digit > 9) digit = 9; + out_ch(out, (char)('0' + digit)); + rounded -= (double)digit; + } +} + +int test_main(void) { + char buf[16]; + Out out; + out.buf = buf; + out.cap = sizeof(buf); + out.len = 0; + format_fixed_abs(&out, 12.375, 2); + buf[out.len] = '\0'; + if (buf[0] != '1') return 20; + if (buf[1] != '2') return 21; + if (buf[2] != '.') return 22; + if (buf[3] != '3') return 23; + if (buf[4] != '8') return 24; + if (out.len != 5) return 25; + return 42; +} diff --git a/test/parse/cases/cg_x64_fp_format_fixed_digits.expected b/test/parse/cases/cg_x64_fp_format_fixed_digits.expected @@ -0,0 +1 @@ +42 diff --git a/test/rt/cases/freestanding_lib.c b/test/rt/cases/freestanding_lib.c @@ -29,6 +29,19 @@ static int check_vsprintf(char* out, const char* fmt, ...) { return rc; } +struct CallbackOut { + char buf[32]; + int len; +}; + +static void capture_ch(char ch, void* arg) { + struct CallbackOut* out = (struct CallbackOut*)arg; + if (out->len < (int)sizeof(out->buf) - 1) { + out->buf[out->len] = ch; + } + out->len++; +} + static int strings_ok(void) { char buf[32]; char small[8]; @@ -99,26 +112,44 @@ static int stdio_ok(void) { int rc; rc = snprintf(buf, sizeof(buf), "%s:%d:%04x:%c", "id", -7, 26, '!'); - if (rc != 12 || strcmp(buf, "id:-7:001a:!") != 0) return 0; + if (rc != 12 || strcmp(buf, "id:-7:001a:!") != 0) return 31; rc = snprintf(buf, 6, "abcdef"); - if (rc != 6 || strcmp(buf, "abcde") != 0) return 0; + if (rc != 6 || strcmp(buf, "abcde") != 0) return 32; rc = sprintf(buf, "%u/%ld/%p", 12U, 34L, (void*)0x1234); - if (rc <= 0 || strstr(buf, "12/34/") != buf) return 0; + if (rc <= 0 || strstr(buf, "12/34/") != buf) return 33; rc = check_vsnprintf(buf, sizeof(buf), "%s %02d", "v", 3); - if (rc != 4 || strcmp(buf, "v 03") != 0) return 0; + if (rc != 4 || strcmp(buf, "v 03") != 0) return 34; rc = check_vsprintf(buf, "%lld", 123456789LL); - if (rc != 9 || strcmp(buf, "123456789") != 0) return 0; - return 1; + if (rc != 9 || strcmp(buf, "123456789") != 0) return 35; + + rc = snprintf(buf, sizeof(buf), "%.2f/%+.1f", 12.375, 4.26); + if (rc != 10 || strcmp(buf, "12.38/+4.3") != 0) return 36; + + rc = snprintf(buf, sizeof(buf), "%.2e %.4g", 1234.0, 12.340); + if (rc != 14 || strcmp(buf, "1.23e+03 12.34") != 0) return 37; + + { + struct CallbackOut out; + out.len = 0; + memset(out.buf, 0, sizeof(out.buf)); + rc = fctprintf(capture_ch, &out, "%s:%04d", "cb", 9); + if (out.len >= (int)sizeof(out.buf)) return 38; + out.buf[out.len] = '\0'; + if (rc != 7 || strcmp(out.buf, "cb:0009") != 0) return 39; + } + return 0; } int test_main(void) { + int rc; assert(1); if (!strings_ok()) return 1; if (!stdlib_ok()) return 2; - if (!stdio_ok()) return 3; + rc = stdio_ok(); + if (rc != 0) return rc; return 42; }